66import pytest
77from PIL import Image
88
9- from vesskel ._batch import (
10- _load_image ,
11- _sanitize_for_csv ,
12- _save_radius ,
13- _save_skeleton ,
14- _write_csv ,
9+ from vesskel ._io import (
10+ load_image ,
11+ sanitize_for_csv ,
12+ save_radius ,
13+ save_skeleton ,
14+ write_csv ,
1515)
1616from vesskel .cli import _discover_input_paths
1717from vesskel .config import CONFIG_SCHEMA_VERSION , PipelineConfig
1818
1919HEAVY_MODULES = frozenset (
20- {"numpy" , "PIL" , "PIL.Image" , "vesskel.pipeline" , "vesskel._batch" }
20+ {"numpy" , "PIL" , "PIL.Image" , "vesskel.pipeline" , "vesskel._batch" , "vesskel._io" }
2121)
2222
2323
@@ -116,27 +116,27 @@ class TestLoadImage:
116116 def test_load_png (self , tmp_path ):
117117 path = tmp_path / "test.png"
118118 Image .fromarray (np .zeros ((3 , 3 ), dtype = np .uint8 )).save (path )
119- arr = _load_image (path )
119+ arr = load_image (path )
120120 assert arr .shape == (3 , 3 )
121121
122122 def test_load_jpg (self , tmp_path ):
123123 path = tmp_path / "test.jpg"
124124 Image .fromarray (np .ones ((4 , 4 ), dtype = np .uint8 ) * 128 ).save (path )
125- arr = _load_image (path )
125+ arr = load_image (path )
126126 assert arr .shape == (4 , 4 )
127127
128128 def test_load_npy (self , tmp_path ):
129129 path = tmp_path / "test.npy"
130130 np .save (path , np .ones ((5 , 5 ), dtype = np .uint8 ))
131- arr = _load_image (path )
131+ arr = load_image (path )
132132 assert arr .shape == (5 , 5 )
133133
134134 def test_load_rgb_collapses_to_grayscale (self , tmp_path ):
135135 path = tmp_path / "rgb.png"
136136 rgb = np .zeros ((8 , 8 , 3 ), dtype = np .uint8 )
137137 rgb [2 :6 , 2 :6 , 0 ] = 255
138138 Image .fromarray (rgb ).save (path )
139- arr = _load_image (path )
139+ arr = load_image (path )
140140 assert arr .ndim == 2
141141 assert arr .shape == (8 , 8 )
142142 assert arr [4 , 4 ] == 255
@@ -147,19 +147,19 @@ def test_load_rgba_collapses_to_grayscale(self, tmp_path):
147147 rgba = np .zeros ((6 , 6 , 4 ), dtype = np .uint8 )
148148 rgba [:, :, :3 ] = 100
149149 Image .fromarray (rgba ).save (path )
150- arr = _load_image (path )
150+ arr = load_image (path )
151151 assert arr .ndim == 2
152152
153153 def test_load_2d_passes_through (self , tmp_path ):
154154 path = tmp_path / "gray.png"
155155 Image .fromarray (np .eye (10 , dtype = np .uint8 ) * 255 ).save (path )
156- arr = _load_image (path )
156+ arr = load_image (path )
157157 assert arr .ndim == 2
158158
159159 def test_load_3d_npy (self , tmp_path ):
160160 path = tmp_path / "vol.npy"
161161 np .save (path , np .ones ((3 , 4 , 5 ), dtype = np .uint8 ))
162- arr = _load_image (path )
162+ arr = load_image (path )
163163 assert arr .shape == (3 , 4 , 5 )
164164
165165 def test_load_invalid_dimension_raises (self , tmp_path ):
@@ -168,40 +168,40 @@ def test_load_invalid_dimension_raises(self, tmp_path):
168168 with pytest .raises (
169169 ValueError , match = r"Expected 2D or 3D image, got shape=\(2, 2, 2, 2\)"
170170 ):
171- _load_image (path )
171+ load_image (path )
172172
173173
174174class TestSanitizeForCsv :
175175 def test_numpy_int (self ):
176- assert _sanitize_for_csv (np .int64 (42 )) == 42
176+ assert sanitize_for_csv (np .int64 (42 )) == 42
177177
178178 def test_numpy_float (self ):
179- result = _sanitize_for_csv (np .float64 (3.14 ))
179+ result = sanitize_for_csv (np .float64 (3.14 ))
180180 assert isinstance (result , float )
181181 assert result == 3.14
182182
183183 def test_python_int_passes_through (self ):
184- assert _sanitize_for_csv (42 ) == 42
184+ assert sanitize_for_csv (42 ) == 42
185185
186186 def test_python_str_passes_through (self ):
187- assert _sanitize_for_csv ("hello" ) == "hello"
187+ assert sanitize_for_csv ("hello" ) == "hello"
188188
189189 def test_none_passes_through (self ):
190- assert _sanitize_for_csv (None ) is None
190+ assert sanitize_for_csv (None ) is None
191191
192192 def test_bool_passes_through (self ):
193- assert _sanitize_for_csv (True ) is True
194- assert _sanitize_for_csv (False ) is False
193+ assert sanitize_for_csv (True ) is True
194+ assert sanitize_for_csv (False ) is False
195195
196196 def test_list_passes_through (self ):
197- assert _sanitize_for_csv ([1 , "a" ]) == [1 , "a" ]
197+ assert sanitize_for_csv ([1 , "a" ]) == [1 , "a" ]
198198
199199
200200class TestWriteCsv :
201201 def test_basic_write (self , tmp_path ):
202202 path = tmp_path / "out.csv"
203203 rows = [{"a" : 1 , "b" : 2 }, {"a" : 3 , "b" : 4 }]
204- _write_csv (path , rows )
204+ write_csv (path , rows )
205205 with path .open () as f :
206206 reader = csv .DictReader (f )
207207 data = list (reader )
@@ -211,22 +211,22 @@ def test_basic_write(self, tmp_path):
211211
212212 def test_empty_rows_writes_nothing (self , tmp_path ):
213213 path = tmp_path / "empty.csv"
214- _write_csv (path , [])
214+ write_csv (path , [])
215215 assert not path .exists ()
216216
217217
218218class TestSaveSkeleton :
219219 def test_save_npy (self , tmp_path ):
220220 path = tmp_path / "skel"
221221 skeleton = np .eye (10 , dtype = np .uint8 )
222- _save_skeleton (path , skeleton , npy = True , png = False )
222+ save_skeleton (path , skeleton , npy = True , png = False )
223223 loaded = np .load (path .with_suffix (".npy" ))
224224 assert np .array_equal (loaded , skeleton )
225225
226226 def test_save_png_2d (self , tmp_path ):
227227 path = tmp_path / "skel"
228228 skeleton = np .eye (8 , dtype = np .uint8 )
229- _save_skeleton (path , skeleton , npy = False , png = True )
229+ save_skeleton (path , skeleton , npy = False , png = True )
230230 loaded = np .array (Image .open (path .with_suffix (".png" )))
231231 assert loaded .shape == (8 , 8 )
232232 assert np .array_equal (loaded > 0 , skeleton > 0 )
@@ -235,12 +235,12 @@ def test_save_png_3d_raises(self, tmp_path):
235235 path = tmp_path / "skel"
236236 skeleton = np .eye (4 , dtype = np .uint8 ).reshape (4 , 1 , 4 )
237237 with pytest .raises (ValueError , match = "PNG skeleton output" ):
238- _save_skeleton (path , skeleton , npy = False , png = True )
238+ save_skeleton (path , skeleton , npy = False , png = True )
239239
240240 def test_save_both_formats (self , tmp_path ):
241241 path = tmp_path / "skel"
242242 skeleton = np .eye (6 , dtype = np .uint8 )
243- _save_skeleton (path , skeleton , npy = True , png = True )
243+ save_skeleton (path , skeleton , npy = True , png = True )
244244 assert path .with_suffix (".npy" ).exists ()
245245 assert path .with_suffix (".png" ).exists ()
246246 npy_loaded = np .load (path .with_suffix (".npy" ))
@@ -249,14 +249,14 @@ def test_save_both_formats(self, tmp_path):
249249
250250 def test_save_neither_does_nothing (self , tmp_path ):
251251 path = tmp_path / "skel"
252- _save_skeleton (path , np .eye (3 , dtype = np .uint8 ), npy = False , png = False )
252+ save_skeleton (path , np .eye (3 , dtype = np .uint8 ), npy = False , png = False )
253253 assert not path .with_suffix (".npy" ).exists ()
254254 assert not path .with_suffix (".png" ).exists ()
255255
256256 def test_npy_output_is_uint8 (self , tmp_path ):
257257 path = tmp_path / "skel"
258258 skeleton = np .ones ((4 , 4 ), dtype = np .int32 )
259- _save_skeleton (path , skeleton , npy = True , png = False )
259+ save_skeleton (path , skeleton , npy = True , png = False )
260260 loaded = np .load (path .with_suffix (".npy" ))
261261 assert loaded .dtype == np .uint8
262262
@@ -265,14 +265,14 @@ class TestSaveRadius:
265265 def test_save_and_load (self , tmp_path ):
266266 path = tmp_path / "radius"
267267 radius = np .random .default_rng (7 ).random ((4 , 4 ))
268- _save_radius (path , radius )
268+ save_radius (path , radius )
269269 loaded = np .load (path .with_suffix (".npy" ))
270270 np .testing .assert_array_equal (loaded , radius )
271271
272272 def test_saved_as_float64 (self , tmp_path ):
273273 path = tmp_path / "radius"
274274 radius = np .array ([[1.5 , 2.5 ], [3.5 , 4.5 ]], dtype = np .float64 )
275- _save_radius (path , radius )
275+ save_radius (path , radius )
276276 loaded = np .load (path .with_suffix (".npy" ))
277277 assert loaded .dtype == np .float64
278278
0 commit comments