Skip to content

Commit a051f95

Browse files
Copilotthewtex
andcommitted
Add reverse_coords option to point conversion functions
- Add reverse_coords boolean kwarg (default True) to points_layer_from_point_set - Add reverse_coords boolean kwarg (default True) to point_set_from_points_layer - When enabled, converts between ITK's x,y,z order and napari's z,y,x order - Update existing tests to account for new default behavior - Add tests for reverse_coords=False, roundtrip, and 2D points Co-authored-by: thewtex <25432+thewtex@users.noreply.github.com>
1 parent 82bd18a commit a051f95

File tree

5 files changed

+177
-33
lines changed

5 files changed

+177
-33
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ dist
22
# pixi environments
33
.pixi/*
44
!.pixi/config.toml
5+
# Python cache
6+
__pycache__/
7+
*.pyc
8+
*.pyo
9+
.pytest_cache/
-6.42 KB
Binary file not shown.
-59.5 KB
Binary file not shown.

itk_napari_conversion.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,34 @@ def image_from_image_layer(image_layer):
6262
return image
6363

6464

65-
def points_layer_from_point_set(point_set):
66-
"""Convert an itk.PointSet to a napari.layers.Points."""
65+
def points_layer_from_point_set(point_set, reverse_coords=True):
66+
"""Convert an itk.PointSet to a napari.layers.Points.
67+
68+
Parameters
69+
----------
70+
point_set : itk.PointSet
71+
The ITK PointSet to convert.
72+
reverse_coords : bool, optional
73+
If True (default), reverse the coordinate order from ITK's x,y,z
74+
to napari's z,y,x order.
75+
76+
Returns
77+
-------
78+
napari.layers.Points
79+
The converted napari Points layer.
80+
"""
6781
# Get points as numpy array
6882
number_of_points = point_set.GetNumberOfPoints()
6983

7084
if number_of_points == 0:
7185
data = np.array([]).reshape(0, 3) # Default to 3D empty array
7286
else:
7387
points_array = itk.array_from_vector_container(point_set.GetPoints())
74-
data = points_array
88+
if reverse_coords:
89+
# Reverse coordinate order from ITK (x,y,z) to napari (z,y,x)
90+
data = points_array[:, ::-1]
91+
else:
92+
data = points_array
7593

7694
# Get point data (features) if available
7795
point_data = point_set.GetPointData()
@@ -92,8 +110,22 @@ def points_layer_from_point_set(point_set):
92110
return points_layer
93111

94112

95-
def point_set_from_points_layer(points_layer):
96-
"""Convert a napari.layers.Points to an itk.PointSet."""
113+
def point_set_from_points_layer(points_layer, reverse_coords=True):
114+
"""Convert a napari.layers.Points to an itk.PointSet.
115+
116+
Parameters
117+
----------
118+
points_layer : napari.layers.Points
119+
The napari Points layer to convert.
120+
reverse_coords : bool, optional
121+
If True (default), reverse the coordinate order from napari's z,y,x
122+
to ITK's x,y,z order.
123+
124+
Returns
125+
-------
126+
itk.PointSet
127+
The converted ITK PointSet.
128+
"""
97129
# Apply transformations (rotate, scale, translate) to points
98130
data = points_layer.data.copy() # Make a copy to avoid modifying original
99131

@@ -122,6 +154,9 @@ def point_set_from_points_layer(points_layer):
122154

123155
# Set points
124156
if len(data) > 0:
157+
if reverse_coords:
158+
# Reverse coordinate order from napari (z,y,x) to ITK (x,y,z)
159+
data = data[:, ::-1]
125160
points = itk.vector_container_from_array(data.astype(np.float32).flatten())
126161
point_set.SetPoints(points)
127162

tests.py

Lines changed: 132 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -175,24 +175,25 @@ def test_points_layer_from_point_set():
175175
PointSetType = itk.PointSet[itk.F, 3]
176176
point_set = PointSetType.New()
177177

178-
# Add some points
178+
# Add some points (ITK order: x,y,z)
179179
points_data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]], dtype=np.float32)
180180
points = itk.vector_container_from_array(points_data.flatten())
181181
point_set.SetPoints(points)
182182

183-
# Convert to napari points layer
183+
# Convert to napari points layer with default reverse_coords=True
184184
points_layer = itk_napari_conversion.points_layer_from_point_set(point_set)
185185

186-
# Check that data matches
187-
assert np.allclose(points_data, points_layer.data)
186+
# Check that data is reversed (napari order: z,y,x)
187+
expected = points_data[:, ::-1]
188+
assert np.allclose(expected, points_layer.data)
188189

189190

190191
def test_points_layer_from_point_set_with_features():
191192
# Create a 3D point set with point data
192193
PointSetType = itk.PointSet[itk.F, 3]
193194
point_set = PointSetType.New()
194195

195-
# Add points
196+
# Add points (ITK order: x,y,z)
196197
points_data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]], dtype=np.float32)
197198
points = itk.vector_container_from_array(points_data.flatten())
198199
point_set.SetPoints(points)
@@ -202,41 +203,44 @@ def test_points_layer_from_point_set_with_features():
202203
point_data = itk.vector_container_from_array(feature_data)
203204
point_set.SetPointData(point_data)
204205

205-
# Convert to napari points layer
206+
# Convert to napari points layer with default reverse_coords=True
206207
points_layer = itk_napari_conversion.points_layer_from_point_set(point_set)
207208

208-
# Check that data matches
209-
assert np.allclose(points_data, points_layer.data)
209+
# Check that data is reversed (napari order: z,y,x)
210+
expected = points_data[:, ::-1]
211+
assert np.allclose(expected, points_layer.data)
210212
assert points_layer.features is not None
211213
assert 'feature' in points_layer.features
212214
assert np.allclose(feature_data, points_layer.features['feature'])
213215

214216

215217
def test_point_set_from_points_layer():
216-
# Create napari points layer
218+
# Create napari points layer (napari order: z,y,x)
217219
data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
218220
points_layer = napari.layers.Points(data)
219221

220-
# Convert to ITK point set
222+
# Convert to ITK point set with default reverse_coords=True
221223
point_set = itk_napari_conversion.point_set_from_points_layer(points_layer)
222224

223-
# Check points
225+
# Check points are reversed (ITK order: x,y,z)
224226
points_array = itk.array_from_vector_container(point_set.GetPoints())
225-
assert np.allclose(data, points_array)
227+
expected = data[:, ::-1]
228+
assert np.allclose(expected, points_array)
226229

227230

228231
def test_point_set_from_points_layer_with_features():
229-
# Create napari points layer with features
232+
# Create napari points layer with features (napari order: z,y,x)
230233
data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
231234
features = {'feature': np.array([10.0, 20.0, 30.0])}
232235
points_layer = napari.layers.Points(data, features=features)
233236

234-
# Convert to ITK point set
237+
# Convert to ITK point set with default reverse_coords=True
235238
point_set = itk_napari_conversion.point_set_from_points_layer(points_layer)
236239

237-
# Check points
240+
# Check points are reversed (ITK order: x,y,z)
238241
points_array = itk.array_from_vector_container(point_set.GetPoints())
239-
assert np.allclose(data, points_array)
242+
expected = data[:, ::-1]
243+
assert np.allclose(expected, points_array)
240244

241245
# Check point data - verify it was set
242246
point_data = point_set.GetPointData()
@@ -246,37 +250,37 @@ def test_point_set_from_points_layer_with_features():
246250

247251

248252
def test_point_set_from_points_layer_with_scale():
249-
# Create napari points layer with scale
253+
# Create napari points layer with scale (napari order: z,y,x)
250254
data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
251255
scale = np.array([2.0, 3.0, 4.0])
252256
points_layer = napari.layers.Points(data, scale=scale)
253257

254-
# Convert to ITK point set
258+
# Convert to ITK point set with default reverse_coords=True
255259
point_set = itk_napari_conversion.point_set_from_points_layer(points_layer)
256260

257-
# Check that points are scaled
261+
# Check that points are scaled and reversed (ITK order: x,y,z)
258262
points_array = itk.array_from_vector_container(point_set.GetPoints())
259-
expected = data * scale
263+
expected = (data * scale)[:, ::-1]
260264
assert np.allclose(expected, points_array)
261265

262266

263267
def test_point_set_from_points_layer_with_translate():
264-
# Create napari points layer with translation
268+
# Create napari points layer with translation (napari order: z,y,x)
265269
data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
266270
translate = np.array([10.0, 20.0, 30.0])
267271
points_layer = napari.layers.Points(data, translate=translate)
268272

269-
# Convert to ITK point set
273+
# Convert to ITK point set with default reverse_coords=True
270274
point_set = itk_napari_conversion.point_set_from_points_layer(points_layer)
271275

272-
# Check that points are translated
276+
# Check that points are translated and reversed (ITK order: x,y,z)
273277
points_array = itk.array_from_vector_container(point_set.GetPoints())
274-
expected = data + translate
278+
expected = (data + translate)[:, ::-1]
275279
assert np.allclose(expected, points_array)
276280

277281

278282
def test_point_set_from_points_layer_with_rotate():
279-
# Create napari points layer with rotation
283+
# Create napari points layer with rotation (napari order: z,y,x)
280284
data = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
281285

282286
# 90 degree rotation around z-axis
@@ -289,11 +293,111 @@ def test_point_set_from_points_layer_with_rotate():
289293

290294
points_layer = napari.layers.Points(data, rotate=rotate)
291295

292-
# Convert to ITK point set
296+
# Convert to ITK point set with default reverse_coords=True
293297
point_set = itk_napari_conversion.point_set_from_points_layer(points_layer)
294298

295-
# Check that points are rotated
299+
# Check that points are rotated and reversed (ITK order: x,y,z)
296300
points_array = itk.array_from_vector_container(point_set.GetPoints())
297-
expected = data @ rotate.T
301+
expected = (data @ rotate.T)[:, ::-1]
298302
assert np.allclose(expected, points_array, atol=1e-10)
299303

304+
305+
def test_points_layer_from_point_set_no_reverse():
306+
"""Test points_layer_from_point_set with reverse_coords=False."""
307+
# Create a simple 3D point set
308+
PointSetType = itk.PointSet[itk.F, 3]
309+
point_set = PointSetType.New()
310+
311+
# Add some points (ITK order: x,y,z)
312+
points_data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]], dtype=np.float32)
313+
points = itk.vector_container_from_array(points_data.flatten())
314+
point_set.SetPoints(points)
315+
316+
# Convert to napari points layer with reverse_coords=False
317+
points_layer = itk_napari_conversion.points_layer_from_point_set(point_set, reverse_coords=False)
318+
319+
# Check that data is NOT reversed
320+
assert np.allclose(points_data, points_layer.data)
321+
322+
323+
def test_point_set_from_points_layer_no_reverse():
324+
"""Test point_set_from_points_layer with reverse_coords=False."""
325+
# Create napari points layer
326+
data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
327+
points_layer = napari.layers.Points(data)
328+
329+
# Convert to ITK point set with reverse_coords=False
330+
point_set = itk_napari_conversion.point_set_from_points_layer(points_layer, reverse_coords=False)
331+
332+
# Check points are NOT reversed
333+
points_array = itk.array_from_vector_container(point_set.GetPoints())
334+
assert np.allclose(data, points_array)
335+
336+
337+
def test_roundtrip_itk_to_napari_to_itk():
338+
"""Test roundtrip conversion from ITK to napari and back to ITK."""
339+
# Create a simple 3D point set with ITK coordinates (x,y,z)
340+
PointSetType = itk.PointSet[itk.F, 3]
341+
original_point_set = PointSetType.New()
342+
343+
# Add some points with distinct x,y,z values for testing
344+
original_data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]], dtype=np.float32)
345+
points = itk.vector_container_from_array(original_data.flatten())
346+
original_point_set.SetPoints(points)
347+
348+
# Convert ITK -> napari (reverses coords) -> ITK (reverses back)
349+
points_layer = itk_napari_conversion.points_layer_from_point_set(original_point_set)
350+
roundtrip_point_set = itk_napari_conversion.point_set_from_points_layer(points_layer)
351+
352+
# Check that the roundtrip produces the original data
353+
roundtrip_array = itk.array_from_vector_container(roundtrip_point_set.GetPoints())
354+
assert np.allclose(original_data, roundtrip_array)
355+
356+
357+
def test_roundtrip_napari_to_itk_to_napari():
358+
"""Test roundtrip conversion from napari to ITK and back to napari."""
359+
# Create napari points layer with napari coordinates (z,y,x)
360+
original_data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
361+
original_layer = napari.layers.Points(original_data)
362+
363+
# Convert napari -> ITK (reverses coords) -> napari (reverses back)
364+
point_set = itk_napari_conversion.point_set_from_points_layer(original_layer)
365+
roundtrip_layer = itk_napari_conversion.points_layer_from_point_set(point_set)
366+
367+
# Check that the roundtrip produces the original data
368+
assert np.allclose(original_data, roundtrip_layer.data)
369+
370+
371+
def test_points_layer_from_point_set_2d():
372+
"""Test points_layer_from_point_set with 2D points."""
373+
# Create a simple 2D point set
374+
PointSetType = itk.PointSet[itk.F, 2]
375+
point_set = PointSetType.New()
376+
377+
# Add some points (ITK order: x,y)
378+
points_data = np.array([[1.0, 2.0], [4.0, 5.0], [7.0, 8.0]], dtype=np.float32)
379+
points = itk.vector_container_from_array(points_data.flatten())
380+
point_set.SetPoints(points)
381+
382+
# Convert to napari points layer with default reverse_coords=True
383+
points_layer = itk_napari_conversion.points_layer_from_point_set(point_set)
384+
385+
# Check that data is reversed (napari order: y,x)
386+
expected = points_data[:, ::-1]
387+
assert np.allclose(expected, points_layer.data)
388+
389+
390+
def test_point_set_from_points_layer_2d():
391+
"""Test point_set_from_points_layer with 2D points."""
392+
# Create napari points layer (napari order: y,x)
393+
data = np.array([[1.0, 2.0], [4.0, 5.0], [7.0, 8.0]])
394+
points_layer = napari.layers.Points(data)
395+
396+
# Convert to ITK point set with default reverse_coords=True
397+
point_set = itk_napari_conversion.point_set_from_points_layer(points_layer)
398+
399+
# Check points are reversed (ITK order: x,y)
400+
points_array = itk.array_from_vector_container(point_set.GetPoints())
401+
expected = data[:, ::-1]
402+
assert np.allclose(expected, points_array)
403+

0 commit comments

Comments
 (0)