@@ -232,7 +232,7 @@ def compute(self, op_input: InputContext, op_output: OutputContext, context: Exe
232
232
# the resultant ndarray for each slice needs to be transposed back, and the depth
233
233
# dim moved back to first, otherwise the seg ndarray would be flipped compared to
234
234
# the DICOM pixel array, causing the DICOM Seg Writer generate flipped seg images.
235
- out_ndarray = np .swapaxes (out_ndarray , 2 , 0 ).astype (np .uint8 )
235
+ out_ndarray = out_ndarray . T . astype ( np . uint8 ) # np.swapaxes(out_ndarray, 2, 0).astype(np.uint8)
236
236
print (f"Output Seg image numpy array shaped: { out_ndarray .shape } " )
237
237
print (f"Output Seg image pixel max value: { np .amax (out_ndarray )} " )
238
238
out_image = Image (out_ndarray , input_img_metadata )
@@ -278,6 +278,10 @@ class InMemImageReader(ImageReader):
278
278
279
279
This is derived from MONAI ImageReader. Instead of reading image from file system, this
280
280
class simply converts a in-memory SDK Image object to the expected formats from ImageReader.
281
+
282
+ The loaded data array will be in C order, for example, a 3D image NumPy array index order
283
+ will be `CDWH`. The actual data array loaded is to be the same as that from the
284
+ MONAI ITKReader, which can also load DICOM series.
281
285
"""
282
286
283
287
def __init__ (self , input_image : Image , channel_dim : Optional [int ] = None , ** kwargs ):
@@ -290,23 +294,39 @@ def verify_suffix(self, filename: Union[Sequence[str], str]) -> bool:
290
294
return True
291
295
292
296
def read (self , data : Union [Sequence [str ], str ], ** kwargs ) -> Union [Sequence [Any ], Any ]:
293
- # Really does not have anything to do.
294
- return self .input_image . asnumpy ()
297
+ # Really does not have anything to do. Simply return the Image object
298
+ return self .input_image
295
299
296
300
def get_data (self , input_image ):
297
301
"""Extracts data array and meta data from loaded image and return them.
298
302
299
303
This function returns two objects, first is numpy array of image data, second is dict of meta data.
300
304
It constructs `affine`, `original_affine`, and `spatial_shape` and stores them in meta dict.
301
- A single image is loaded with a single set of metadata as of now."""
305
+ A single image is loaded with a single set of metadata as of now.
306
+
307
+ The App SDK Image asnumpy function is expected to return a NumPy array of index order `DHW`.
308
+ This is because in the DICOM serie to volume operator pydicom Dataset pixel_array is used to
309
+ to get per instance pixel NumPy array, with index order of `HW`. When all instances are stacked,
310
+ along the first axis, the Image NumPy array's index order is `DHW`. ITK array_view_from_image
311
+ and SimpleITK GetArrayViewFromImage also returns a Numpy array with index order of `DHW`.
312
+ This NumPy array is then transposed to be consistent with the NumPy array from NibabelReader,
313
+ which loads NIfTI image and gets NumPy array using the image's get_fdata() function.
314
+
315
+ Args:
316
+ input_image (Image): an App SDK Image object.
317
+ """
302
318
303
319
img_array : List [np .ndarray ] = []
304
320
compatible_meta : Dict = {}
305
321
306
- for i in ensure_tuple (self . input_image ):
322
+ for i in ensure_tuple (input_image ):
307
323
if not isinstance (i , Image ):
308
324
raise TypeError ("Only object of Image type is supported." )
309
- data = i .asnumpy ()
325
+
326
+ # The Image asnumpy() retruns NumPy array similar to ITK array_view_from_image
327
+ # The array then needs to be transposed, as does in MONAI ITKReader, to align
328
+ # with the output from Nibabel reader loading NIfTI files.
329
+ data = i .asnumpy ().T
310
330
img_array .append (data )
311
331
header = self ._get_meta_dict (i )
312
332
_copy_compatible_dict (header , compatible_meta )
@@ -328,18 +348,19 @@ def _get_meta_dict(self, img: Image) -> Dict:
328
348
# So, for now have to get to the Image generator, namely DICOMSeriesToVolumeOperator, and
329
349
# rely on its published metadata.
330
350
331
- # Recall that the column and row data for pydicom pixel_array had been switched, and the depth
332
- # is the last dim in DICOMSeriesToVolumeOperator
351
+ # Referring to the MONAI ITKReader, the spacing is simply a NumPy array from the ITK image
352
+ # GetSpacing, in WHD.
333
353
meta_dict ["spacing" ] = np .asarray (
334
354
[
335
- img_meta_dict ["col_pixel_spacing" ],
336
355
img_meta_dict ["row_pixel_spacing" ],
356
+ img_meta_dict ["col_pixel_spacing" ],
337
357
img_meta_dict ["depth_pixel_spacing" ],
338
358
]
339
359
)
340
360
meta_dict ["original_affine" ] = np .asarray (img_meta_dict ["nifti_affine_transform" ])
341
361
meta_dict ["affine" ] = meta_dict ["original_affine" ]
342
- meta_dict ["spatial_shape" ] = np .asarray (img .asnumpy ().shape )
362
+ # The spatial shape, again, referring to ITKReader, it is the WHD
363
+ meta_dict ["spatial_shape" ] = np .asarray (img .asnumpy ().T .shape )
343
364
# Well, no channel as the image data shape is forced to the the same as spatial shape
344
365
meta_dict ["original_channel_dim" ] = "no_channel"
345
366
0 commit comments