Skip to content

Commit 4ac84ca

Browse files
authored
Merge pull request #6043 from hjmjohnson/backport-6027-buffer-protocol-tests
ENH: Backport #6027 — comprehensive tests for itk.Image buffer protocol and lifetime
2 parents 7330169 + 65e3aac commit 4ac84ca

7 files changed

Lines changed: 1537 additions & 36 deletions

File tree

.github/workflows/itk_dict.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Cuadra
4545
Cz
4646
DDataFrameobject
4747
Dasarathy
48+
Dask
4849
Deref
4950
Dij
5051
Dinstein
@@ -332,6 +333,7 @@ cy
332333
d'Attente
333334
dDwWmMyY
334335
da
336+
dask
335337
dat
336338
dcl
337339
dcmimage
@@ -500,6 +502,7 @@ highrange
500502
hipaa
501503
hor
502504
hough
505+
Hounsfield
503506
hval
504507
hvector
505508
ia
@@ -540,6 +543,7 @@ inputreader
540543
intensityrescaler
541544
interactor
542545
interdependencies
546+
interop
543547
interslice
544548
intra
545549
invariance
@@ -901,6 +905,7 @@ textit
901905
texttt
902906
textwidth
903907
tgz
908+
thewtex
904909
thisDic
905910
threadIDs
906911
threadsafe

Modules/Bridge/NumPy/wrapping/PyBuffer.i.init

Lines changed: 88 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,31 +29,95 @@ else:
2929
loads = dask_deserialize.dispatch(np.ndarray)
3030
return NDArrayITKBase(loads(header, frames))
3131

32+
# Module-level constant — built once at import time.
33+
# Composite pixel types (RGB, Vector, etc.) are decomposed to their
34+
# scalar component type before reaching _get_buffer_formatstring(),
35+
# so only scalar codes appear here.
36+
# Platform aliases (ST/IT/OT) resolve to UL/SL at the wrapping level.
37+
import os as _os
38+
_BUFFER_FORMAT_MAP = {
39+
# --- integer types ---
40+
"B": "?", # bool
41+
"UC": "B", # unsigned char -> uint8
42+
"US": "H", # unsigned short -> uint16
43+
"UI": "I", # unsigned int -> uint32
44+
"UL": "L", # unsigned long -> platform (8 bytes Linux/macOS, 4 bytes Windows)
45+
"ULL": "Q", # unsigned long long -> uint64
46+
"SC": "b", # signed char -> int8
47+
"SS": "h", # signed short -> int16
48+
"SI": "i", # signed int -> int32
49+
"SL": "l", # signed long -> platform
50+
"SLL": "q", # signed long long -> int64
51+
# --- floating point types ---
52+
"F": "f", # float -> float32
53+
"D": "d", # double -> float64
54+
# "LD" (long double) intentionally omitted: sizeof(long double)
55+
# is 16 bytes on Linux/macOS x86-64 but struct "d" is 8 bytes.
56+
# Casting would silently corrupt the buffer.
57+
}
58+
59+
def _get_buffer_formatstring(itk_pixel_code: str) -> str:
60+
"""Return the struct format character for an ITK pixel type code.
61+
62+
Used by the PEP 688 ``__buffer__`` protocol implementation on
63+
``itk.Image`` to describe the element type of the exported
64+
memoryview. Format characters follow Python's ``struct`` module
65+
specification.
66+
67+
Parameters
68+
----------
69+
itk_pixel_code : str
70+
Short name of the ITK component type, e.g. ``"UC"``, ``"F"``.
71+
72+
Returns
73+
-------
74+
str
75+
Single-character ``struct`` format string.
76+
77+
Raises
78+
------
79+
KeyError
80+
If ``itk_pixel_code`` is not a supported scalar type.
81+
"""
82+
try:
83+
return _BUFFER_FORMAT_MAP[itk_pixel_code]
84+
except KeyError:
85+
raise KeyError(
86+
f"Unsupported ITK pixel type code {itk_pixel_code!r} for "
87+
f"buffer export. Supported codes: "
88+
f"{sorted(_BUFFER_FORMAT_MAP)}"
89+
) from None
90+
if _os.name == 'nt':
91+
# On Windows, C ``long`` is 32-bit
92+
_BUFFER_FORMAT_MAP['UL'] = 'I'
93+
_BUFFER_FORMAT_MAP['SL'] = 'i'
94+
95+
3296
def _get_numpy_pixelid(itk_Image_type) -> np.dtype:
3397
"""Returns a ITK PixelID given a numpy array."""
3498

35-
# This is a Mapping from numpy array types to itk pixel types.
36-
_np_itk = {"UC":np.dtype(np.uint8),
37-
"US":np.dtype(np.uint16),
38-
"UI":np.dtype(np.uint32),
39-
"UL":np.dtype(np.uint64),
40-
"ULL":np.dtype(np.uint64),
41-
"SC":np.dtype(np.int8),
42-
"SS":np.dtype(np.int16),
43-
"SI":np.dtype(np.int32),
44-
"SL":np.dtype(np.int64),
45-
"SLL":np.dtype(np.int64),
46-
"F":np.dtype(np.float32),
47-
"D":np.dtype(np.float64),
48-
"PF2":np.dtype(np.float32),
49-
"PF3":np.dtype(np.float32),
50-
}
51-
import os
52-
if os.name == 'nt':
53-
_np_itk['UL'] = np.dtype(np.uint32)
54-
_np_itk['SL'] = np.dtype(np.int32)
55-
try:
56-
return _np_itk[itk_Image_type]
57-
except KeyError as e:
58-
raise e
99+
return _NUMPY_PIXELID_MAP[itk_Image_type]
100+
101+
102+
# Module-level constant — built once at import time.
103+
_NUMPY_PIXELID_MAP = {
104+
"B": np.dtype(np.bool_),
105+
"UC": np.dtype(np.uint8),
106+
"US": np.dtype(np.uint16),
107+
"UI": np.dtype(np.uint32),
108+
"UL": np.dtype(np.uint64),
109+
"ULL": np.dtype(np.uint64),
110+
"SC": np.dtype(np.int8),
111+
"SS": np.dtype(np.int16),
112+
"SI": np.dtype(np.int32),
113+
"SL": np.dtype(np.int64),
114+
"SLL": np.dtype(np.int64),
115+
"F": np.dtype(np.float32),
116+
"D": np.dtype(np.float64),
117+
"PF2": np.dtype(np.float32),
118+
"PF3": np.dtype(np.float32),
119+
}
120+
if _os.name == 'nt':
121+
_NUMPY_PIXELID_MAP['UL'] = np.dtype(np.uint32)
122+
_NUMPY_PIXELID_MAP['SL'] = np.dtype(np.int32)
59123
%}

Modules/Core/Common/wrapping/test/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,14 @@ if(ITK_WRAP_PYTHON)
5252
itkSpatialOrientationAdapterTest
5353
COMMAND
5454
${CMAKE_CURRENT_SOURCE_DIR}/itkSpatialOrientationAdapterTest.py)
55+
itk_python_add_test(
56+
NAME
57+
itkImageInteropPythonTest
58+
COMMAND
59+
${CMAKE_CURRENT_SOURCE_DIR}/itkImageInteropTest.py)
60+
itk_python_add_test(
61+
NAME
62+
itkImageLifetimePythonTest
63+
COMMAND
64+
${CMAKE_CURRENT_SOURCE_DIR}/itkImageLifetimeTest.py)
5565
endif()

0 commit comments

Comments
 (0)