Hi,
is there a way to get the mapping of numpy data types to the SimpleITK data types?
I have seen this,
but it is a method variable, and it requires an input image. I am wondering whether I cant get the SimpleITK type equivalent given the numpy data type, e.g. get sitkUInt8 given numpy.uint8. For obvious reasons, I am not willing to hard-code the mapping myself.
Sorry for this very basic question, but have not found any relevant code/posts.
Not exactly sure what is the intended goal here, so my answer may or may not be relefant.
The direction from numpy types to SimpleITK is one-to-many (np.uint64 can be mapped to sitVectorUInt64 or sitkUInt64).
If the intent is to create a SimpleITK image from a numpy array the GetImageFromArray has a isVector parameter which takes care of the ambiguity mentioned above and maps the types correctly (the relevant code with the mapping is just below the code you were looking at).
If this doesn’t address the issue, please provide more details.
I have a very simple script that takes an image and changes its pixel type using SimpletITK. Right now, as the use case is labelmap images, I have the target SimpleITK pixel type hard-coded, but I would like to offer some flexibility and have the pixel type as an input argument to the script.
I thought that since across my applications I use NumPy a lot more than SimpleITK I would accept a NumPy data type string as the argument.
I see now the one-to-many mapping. Although that may be an issue at some point, for now I could content myself using the _get_sitk_pixelid if that is exposed by the public API. So given the NumPy data type, I could create a simple NumPy array, and the give that to _get_sitk_pixelid and then somehow retrieve the data type from the returned integer value?
Thanks for the clarification. So what you really are looking for is a mapping from arbitrary strings, input to the script, to a SimpleITK pixel type. Below is possibly a solution:
import SimpleITK as sitk
import argparse
import sys
str_2_sitk_pixel_type = {
"uint8": sitk.sitkUInt8, # the key is arbitrary so could have been "np_uint8"
"uint16": sitk.sitkUInt16,
"uint32": sitk.sitkUInt32,
"uint64": sitk.sitkUInt64,
"int8": sitk.sitkInt8,
"int16": sitk.sitkInt16,
"int32": sitk.sitkInt32,
"int64": sitk.sitkInt64,
"float32": sitk.sitkFloat32,
"float64": sitk.sitkFloat64,
"complex64": sitk.sitkComplexFloat32,
"complex128": sitk.sitkComplexFloat64,
}
def positive_int(i):
res = int(i)
if res > 0:
return res
raise argparse.ArgumentTypeError(f"Invalid argument ({i}), expected value > 0 .")
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument(
"image_sizes", type=positive_int, nargs=2, help="image size x and y pixels"
)
parser.add_argument(
"pixel_type",
choices=list(str_2_sitk_pixel_type.keys()),
help="String representations of SimpleITK pixel types.",
)
args = parser.parse_args(argv)
# This will always work because argparse enforced valid values for the inputs
image = sitk.Image(args.image_sizes, str_2_sitk_pixel_type[args.pixel_type])
print(image)
sys.exit(0)
if __name__ == "__main__":
sys.exit(main())
There are a couple things going on here that I am having problems figuring what you are trying to do.
First you describe you usage as “label map images”. I am not sure if you want itk::Image of labels or an itk::LabelMap. There are rather different and can both be represented in SimpleITK.
Second, numpy uses “data type objects” to describe the data of arrays. There is more details related to these data type objects that most users know. But the commonly used enumerations numpy.uint8 are not actually datatype objects, and comparisons with them break down in some cases. They need to be converted to dtype objects to do useful comparisons.
I think you are on your own to convert strings to numpy dtypes. I would recommend using a manual map over eval from user input. Maybe some regex could be used.
Second, I’d use the following function in SimpleITK.extra:
I am not thinking of a labelmap in terms of SimpleITK: for “labelmap” I mean an image containing integer values issues by a segmentation method.
Second, numpy uses “data type objects” to describe the data of arrays. There is more details related to these data type objects that most users know. But the commonly used enumerations numpy.uint8 are not actually datatype objects, and comparisons with them break down in some cases. They need to be converted to dtype objects to do useful comparisons.
The labelmaps that I am trying to convert are issued by some tool that unfortunately writes these images as having a float pixel type, and then I am using another tool that requires labelmap images to have integer pixel types. I cannot assume more than that/do not have more information.
If you have some specific code it made help.
The essential part of the code can be summarized to:
eval is dangerous because it will execute the code found in the string which may be harmful for the user (delete files or any other operation you can do with Python).
If you have complete control of the string contents then probably ok to use. For example if the string passed to eval is obtained using argparse and the parameter is limited to a “choices” set that you control, the user will be limited to valid selections and cannot provide problematic input, so there is no problem.
Reading the input image into a casted type may result in truncation. You may want to read the input image input then automatically chooses type by computing the min/max then select the appropriate SimpleITK pixel type.
Here is what GH Copilot wrote to do this:
import SimpleITK as sitk
# Read the input image
in_fname = "input_image.nii"
out_fname = "output_image.nii"
image = sitk.ReadImage(in_fname)
# Compute image statistics
stats = sitk.StatisticsImageFilter()
stats.Execute(image)
min_pixel_value = stats.GetMinimum()
max_pixel_value = stats.GetMaximum()
# Determine the appropriate integer pixel type
if min_pixel_value >= 0:
if max_pixel_value <= 255:
pixel_type = sitk.sitkUInt8
elif max_pixel_value <= 65535:
pixel_type = sitk.sitkUInt16
elif max_pixel_value <= 4294967295:
pixel_type = sitk.sitkUInt32
else:
pixel_type = sitk.sitkUInt64
else:
if min_pixel_value >= -128 and max_pixel_value <= 127:
pixel_type = sitk.sitkInt8
elif min_pixel_value >= -32768 and max_pixel_value <= 32767:
pixel_type = sitk.sitkInt16
elif min_pixel_value >= -2147483648 and max_pixel_value <= 2147483647:
pixel_type = sitk.sitkInt32
else:
pixel_type = sitk.sitkInt64
# Cast the image to the selected type
cast_image = sitk.Cast(image, pixel_type)
# Write the image to the output file
sitk.WriteImage(cast_image, out_fname)
IMHO, this is still a risky recommendation. For this case to convert a string to a numpy dtype the following can be used: np.dtype("uint8"). There are many other strings that the dtype constructor also supports, that would not be supported as conversion into an itk/sitk image type.