-
-
Notifications
You must be signed in to change notification settings - Fork 674
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH: pickle serialization for itk images (issue 1091) #2829
ENH: pickle serialization for itk images (issue 1091) #2829
Conversation
This is not going to pass the CI checks right now. There's a few more steps:
Click to expand details to see what python code we need to add:import os
import six
from functools import reduce
import numpy as np
try:
import zstandard as zstd
except ImportError:
import zstd
import itk
def _image_to_type(itkimage): # noqa: C901
component = itk.template(itkimage)[1][0]
if component == itk.UL:
if os.name == 'nt':
return 'uint32_t', 1
else:
return 'uint64_t', 1
mangle = None
pixelType = 1
if component == itk.SL:
if os.name == 'nt':
return 'int32_t', 1,
else:
return 'int64_t', 1,
if component in (itk.SC, itk.UC, itk.SS, itk.US, itk.SI, itk.UI, itk.F,
itk.D, itk.B):
mangle = component
elif component in [i[1] for i in itk.Vector.items()]:
mangle = itk.template(component)[1][0]
pixelType = 5
elif component == itk.complex[itk.F]:
# complex float
return 'float', 10
elif component == itk.complex[itk.D]:
# complex float
return 'double', 10
elif component in [i[1] for i in itk.CovariantVector.items()]:
# CovariantVector
mangle = itk.template(component)[1][0]
pixelType = 7
elif component in [i[1] for i in itk.Offset.items()]:
# Offset
return 'int64_t', 4
elif component in [i[1] for i in itk.FixedArray.items()]:
# FixedArray
mangle = itk.template(component)[1][0]
pixelType = 11
elif component in [i[1] for i in itk.RGBAPixel.items()]:
# RGBA
mangle = itk.template(component)[1][0]
pixelType = 3
elif component in [i[1] for i in itk.RGBPixel.items()]:
# RGB
mangle = itk.template(component)[1][0]
pixelType = 2
elif component in [i[1] for i in itk.SymmetricSecondRankTensor.items()]:
# SymmetricSecondRankTensor
mangle = itk.template(component)[1][0]
pixelType = 8
else:
raise RuntimeError('Unrecognized component type: {0}'.format(str(component)))
_python_to_js = {
itk.SC: 'int8_t',
itk.UC: 'uint8_t',
itk.SS: 'int16_t',
itk.US: 'uint16_t',
itk.SI: 'int32_t',
itk.UI: 'uint32_t',
itk.F: 'float',
itk.D: 'double',
itk.B: 'uint8_t'
}
return _python_to_js[mangle], pixelType
def itkimage_to_json(itkimage, manager=None):
"""Serialize a Python itk.Image object.
Attributes of this dictionary are to be passed to the JavaScript itkimage
constructor.
"""
if itkimage is None:
return None
else:
direction = itkimage.GetDirection()
directionMatrix = direction.GetVnlMatrix()
directionList = []
dimension = itkimage.GetImageDimension()
pixel_arr = itk.array_view_from_image(itkimage)
componentType, pixelType = _image_to_type(itkimage)
if 'int64' in componentType:
# JavaScript does not yet support 64-bit integers well
if componentType == 'uint64_t':
pixel_arr = pixel_arr.astype(np.uint32)
componentType = 'uint32_t'
else:
pixel_arr = pixel_arr.astype(np.int32)
componentType = 'int32_t'
compressor = zstd.ZstdCompressor(level=3)
compressed = compressor.compress(pixel_arr.data)
pixel_arr_compressed = memoryview(compressed)
for col in range(dimension):
for row in range(dimension):
directionList.append(directionMatrix.get(row, col))
imageType = dict(
dimension=dimension,
componentType=componentType,
pixelType=pixelType,
components=itkimage.GetNumberOfComponentsPerPixel()
)
return dict(
imageType=imageType,
origin=tuple(itkimage.GetOrigin()),
spacing=tuple(itkimage.GetSpacing()),
size=tuple(itkimage.GetBufferedRegion().GetSize()),
direction={'data': directionList,
'rows': dimension,
'columns': dimension},
compressedData=compressed
)
def _type_to_image(jstype):
_pixelType_to_prefix = {
1: '',
2: 'RGB',
3: 'RGBA',
4: 'O',
5: 'V',
7: 'CV',
8: 'SSRT',
11: 'FA'
}
pixelType = jstype['pixelType']
dimension = jstype['dimension']
if pixelType == 10:
if jstype['componentType'] == 'float':
return itk.Image[itk.complex, itk.F], np.float32
else:
return itk.Image[itk.complex, itk.D], np.float64
def _long_type():
if os.name == 'nt':
return 'LL'
else:
return 'L'
prefix = _pixelType_to_prefix[pixelType]
_js_to_python = {
'int8_t': 'SC',
'uint8_t': 'UC',
'int16_t': 'SS',
'uint16_t': 'US',
'int32_t': 'SI',
'uint32_t': 'UI',
'int64_t': 'S' + _long_type(),
'uint64_t': 'U' + _long_type(),
'float': 'F',
'double': 'D'
}
_js_to_numpy_dtype = {
'int8_t': np.int8,
'uint8_t': np.uint8,
'int16_t': np.int16,
'uint16_t': np.uint16,
'int32_t': np.int32,
'uint32_t': np.uint32,
'int64_t': np.int64,
'uint64_t': np.uint64,
'float': np.float32,
'double': np.float64
}
dtype = _js_to_numpy_dtype[jstype['componentType']]
if pixelType != 4:
prefix += _js_to_python[jstype['componentType']]
if pixelType not in (1, 2, 3, 10):
prefix += str(dimension)
prefix += str(dimension)
return getattr(itk.Image, prefix), dtype
def itkimage_from_json(js, manager=None):
"""Deserialize a Javascript itk.js Image object."""
if js is None:
return None
else:
ImageType, dtype = _type_to_image(js['imageType'])
decompressor = zstd.ZstdDecompressor()
if six.PY2:
asBytes = js['compressedData'].tobytes()
pixelBufferArrayCompressed = np.frombuffer(asBytes, dtype=np.uint8)
else:
pixelBufferArrayCompressed = np.frombuffer(js['compressedData'],
dtype=np.uint8)
pixelCount = reduce(lambda x, y: x * y, js['size'], 1)
numberOfBytes = pixelCount * \
js['imageType']['components'] * np.dtype(dtype).itemsize
pixelBufferArray = \
np.frombuffer(decompressor.decompress(pixelBufferArrayCompressed,
numberOfBytes),
dtype=dtype)
pixelBufferArray.shape = js['size'][::-1]
# Workaround for GetImageFromArray required until 5.0.1
# and https://github.com/numpy/numpy/pull/11739
pixelBufferArrayCopyToBeRemoved = pixelBufferArray.copy()
# image = itk.PyBuffer[ImageType].GetImageFromArray(pixelBufferArray)
image = itk.PyBuffer[ImageType].GetImageFromArray(
pixelBufferArrayCopyToBeRemoved)
Dimension = image.GetImageDimension()
image.SetOrigin(js['origin'])
image.SetSpacing(js['spacing'])
direction = image.GetDirection()
directionMatrix = direction.GetVnlMatrix()
directionJs = js['direction']['data']
for col in range(Dimension):
for row in range(Dimension):
directionMatrix.put(
row, col, directionJs[col + row * Dimension])
return image
It looks like ITK uses |
These could go in extras.py with the other conversion functions: To follow the naming convention there,
These could go in helpers.py:
They are mostly simple @GenevieveBuckley thank you! |
I'm a little bit worried I don't see the tests FAIL at commit eb18f29. They should be failing here, because the code is still incomplete and I've added this test:
Thoughts:
I'm going to go ahead and push the rest of my commits, but I'm hoping these links to the CI builds will let you see the build for this moment in time, when we expect to see test failures and don't: |
Things to check in the review:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GenevieveBuckley looking awesome! 🏅
Nice work on the test!
The CI is currently failing on:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/home/vsts/work/1/s-build/Wrapping/Generators/Python/itk/__init__.py", line 28, in <module>
from itk.support.extras import *
File "/home/vsts/work/1/s-build/Wrapping/Generators/Python/itk/support/extras.py", line 22, in <module>
import six
ModuleNotFoundError: No module named 'six'
itkTestDriver: Process exited with return value: 1
A few comments inline on how to address this, the import's etc.
Minor comments addressed, two things remaining:
|
Current
|
import itk | ||
|
||
ImageType, dtype = type_to_image(image_dict['imageType']) | ||
image = itk.PyBuffer[ImageType].GetImageFromArray(image_dict['data']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the right way to do it.
Not sure whether we'll run into this problem (stackoverflow post) or not, but if we do there is a suggestion over there on how to fix it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we want / need to roughtly do it this way, but GetImageFromArray
-> GetImageViewFromArray
image.SetOrigin(image_dict['origin']) | ||
image.SetSpacing(image_dict['spacing']) | ||
direction = image_dict['direction']['data'] | ||
image.SetDirection(direction) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've done this because of your suggestion to use the numpy direction array in the other function, so I imagine this function also needed to be updated. I've pulled the direction numpy array out of the dictionary and then used the .SetDirection() method.
It feels like logic is duplicated between image_from_dict and setstate. Is that right, or do I misunderstand what needs to happen where?
def __setstate__(self, state):
"""Set object state, necessary for serialization with pickle."""
import itk
deserialized = itk.image_from_dict(state)
self.__dict__['this'] = deserialized
self.SetOrigin(state['origin'])
self.SetSpacing(state['spacing'])
direction = np.asarray(self.GetDirection())
self.SetDirection(direction)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.dict['this'] = deserialized
That's cool! If it works, then, yes, maybe that is all we need.
I think I've done these two things now. We'll see what the CI tests say. I have not moved zstandard compression into |
Ok, I think now we just need to fix the test. 2021-10-28T06:49:19.4210320Z 150: RuntimeError: Size mismatch of image and Buffer.
2021-10-28T06:49:19.4221430Z 150:
2021-10-28T06:49:19.4311360Z 150: The above exception was the direct cause of the following exception:
2021-10-28T06:49:19.4312300Z 150:
2021-10-28T06:49:19.4323390Z 150: Traceback (most recent call last):
2021-10-28T06:49:19.4415490Z 150: File "/Users/runner/work/1/s/Wrapping/Generators/Python/Tests/extras.py", line 167, in <module>
2021-10-28T06:49:19.4426640Z 150: assert (pickle.loads(pickle.dumps(image)) == image).all()
2021-10-28T06:49:19.4519520Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/itkImagePython.py", line 4450, in __setstate__
2021-10-28T06:49:19.4577890Z 150: deserialized = itk.image_from_dict(state)
2021-10-28T06:49:19.4606180Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/support/extras.py", line 848, in image_from_dict
2021-10-28T06:49:19.4614160Z 150: image = itk.PyBuffer[ImageType].GetImageFromArray(image_dict['data'])
2021-10-28T06:49:19.4636070Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/itkPyBufferPython.py", line 3051, in GetImageFromArray
2021-10-28T06:49:19.4656660Z 150: imageView = itkPyBufferIRGBUC2.GetImageViewFromArray(ndarr, is_vector)
2021-10-28T06:49:19.4688430Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/itkPyBufferPython.py", line 3021, in GetImageViewFromArray
2021-10-28T06:49:19.4745730Z 150: imgview = itkPyBufferIRGBUC2._GetImageViewFromArray(ndarr, ndarr.shape[::-1], 1)
2021-10-28T06:49:19.4819430Z 150: SystemError: <built-in function itkPyBufferIRGBUC2__GetImageViewFromArray> returned a result with an error set
2021-10-28T06:49:19.4829650Z 169: Loading ITKPyBase... done
2021-10-28T06:49:19.4909310Z 167: Loading ITKImageGrid... done
2021-10-28T06:49:19.5010890Z 150: itkTestDriver: Process exited with return value: 1
2021-10-28T06:49:19.5024220Z 167/248 Test #150: PythonExtrasTest ...................................................***Failed 18.35 sec I think this might be happening at line 848 of
I'll try possibility 1 first. The suggestion on stackoverflow is to add |
Thinking out loud here. We have other numpy arrays for the direction, but that's not the line that's in the traceback. I don't think there's much point changing those arrays to be 32 bit, but it would be easy enough to do. Ooh, just found this thread with a suggestion from Matt to use |
All the other tests, including We're using the itk |
I worked on this some more this afternoon, and I still don't understand why that test is failing. Because I was just reusing whatever the last |
import itk | ||
|
||
ImageType, dtype = type_to_image(image_dict['imageType']) | ||
image = itk.PyBuffer[ImageType].GetImageFromArray(image_dict['data']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we want / need to roughtly do it this way, but GetImageFromArray
-> GetImageViewFromArray
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I've done these two things now. We'll see what the CI tests say.
Awesome, thanks, Genevieve!
I have not moved zstandard compression into getstate/setstate, right now it's been completely removed. It's my understanding that you want to try this out without any compression, is that correct @thewtex?
Yes, we could possibly add the compression in a subsequent step. For distributed computing with Dask distributed, the compute / data transfer may make it worthwhile (?)
Regarding the size / typing errors, we do need to use:
ImageType = helpers.image_type_from_wasm_type(image_dict['imageType'])
image = itk.PyBuffer[ImageType].GetImageViewFromArray(image_dict['data'])
Where type_to_image
is renamed to image_type_from_wasm_type
and it only returns the ImageType
.
The reason for this is that a np.ndarray
does not contain all the typing information as an imageType
. The dtype corresponds to the itk componentType. However, the imageType also defines how many spatial dimensions there are, if it is multi-component image, how many components there are and what those components mean, i.e. are they RGB values or values of a co-variant vector or values of a contravariant vector, etc.
Additionally, ideally we want this serialization format to correspond to what is used for interfacing with WebAssembly, described here:
Have tried to address review comments, waiting on CI checks now. |
ITK doesn't seem to recognize the array type, I'm getting messages to say it's not wrapped. We're using a The recommended solution is to set
2021-11-01T04:22:29.5169190Z 150: Traceback (most recent call last):
2021-11-01T04:22:29.5245040Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/support/template_class.py", line 525, in __getitem__
2021-11-01T04:22:29.5276550Z 150: this_item = self.__template__[key]
2021-11-01T04:22:29.5287930Z 150: KeyError: (<class 'itk.itkImagePython.itkImageUC2'>, <class 'numpy.uint8'>)
2021-11-01T04:22:29.5293920Z 150:
2021-11-01T04:22:29.5349140Z 150: During handling of the above exception, another exception occurred:
2021-11-01T04:22:29.5378830Z 150:
2021-11-01T04:22:29.5451240Z 150: Traceback (most recent call last):
2021-11-01T04:22:29.5480370Z 150: File "/Users/runner/work/1/s/Wrapping/Generators/Python/Tests/extras.py", line 176, in <module>
2021-11-01T04:22:29.5552620Z 150: assert (pickle.loads(pickle.dumps(image)) == image).all()
2021-11-01T04:22:29.5582710Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/itkImagePython.py", line 4451, in __setstate__
2021-11-01T04:22:29.5654270Z 150: deserialized = itk.image_from_dict(state)
2021-11-01T04:22:29.5684970Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/support/extras.py", line 844, in image_from_dict
2021-11-01T04:22:29.5754860Z 150: image = itk.PyBuffer[ImageType].GetImageViewFromArray(image_dict['data'])
2021-11-01T04:22:29.5787390Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/support/template_class.py", line 529, in __getitem__
2021-11-01T04:22:29.5858280Z 150: raise itk.TemplateTypeError(self, key)
2021-11-01T04:22:29.5890140Z 150: itk.support.extras.TemplateTypeError: itk.PyBuffer is not wrapped for input type `itk.Image[itk.UC,2], uint8`. Full details - click to expand!:2021-11-01T04:22:29.5169190Z 150: Traceback (most recent call last):
2021-11-01T04:22:29.5245040Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/support/template_class.py", line 525, in __getitem__
2021-11-01T04:22:29.5276550Z 150: this_item = self.__template__[key]
2021-11-01T04:22:29.5287930Z 150: KeyError: (<class 'itk.itkImagePython.itkImageUC2'>, <class 'numpy.uint8'>)
2021-11-01T04:22:29.5293920Z 150:
2021-11-01T04:22:29.5349140Z 150: During handling of the above exception, another exception occurred:
2021-11-01T04:22:29.5378830Z 150:
2021-11-01T04:22:29.5451240Z 150: Traceback (most recent call last):
2021-11-01T04:22:29.5480370Z 150: File "/Users/runner/work/1/s/Wrapping/Generators/Python/Tests/extras.py", line 176, in <module>
2021-11-01T04:22:29.5552620Z 150: assert (pickle.loads(pickle.dumps(image)) == image).all()
2021-11-01T04:22:29.5582710Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/itkImagePython.py", line 4451, in __setstate__
2021-11-01T04:22:29.5654270Z 150: deserialized = itk.image_from_dict(state)
2021-11-01T04:22:29.5684970Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/support/extras.py", line 844, in image_from_dict
2021-11-01T04:22:29.5754860Z 150: image = itk.PyBuffer[ImageType].GetImageViewFromArray(image_dict['data'])
2021-11-01T04:22:29.5787390Z 150: File "/Users/runner/work/1/s-build/Wrapping/Generators/Python/itk/support/template_class.py", line 529, in __getitem__
2021-11-01T04:22:29.5858280Z 150: raise itk.TemplateTypeError(self, key)
2021-11-01T04:22:29.5890140Z 150: itk.support.extras.TemplateTypeError: itk.PyBuffer is not wrapped for input type `itk.Image[itk.UC,2], uint8`.
2021-11-01T04:22:29.5962970Z 150:
2021-11-01T04:22:29.5995970Z 150: To limit the size of the package, only a limited number of
2021-11-01T04:22:29.6064550Z 150: types are available in ITK Python. To print the supported
2021-11-01T04:22:29.6097690Z 150: types, run the following command in your python environment:
2021-11-01T04:22:29.6113420Z 150:
2021-11-01T04:22:29.6171850Z 150: itk.PyBuffer.GetTypes()
2021-11-01T04:22:29.6214970Z 150:
2021-11-01T04:22:29.6273310Z 150: Possible solutions:
2021-11-01T04:22:29.6299720Z 150: * If you are an application user:
2021-11-01T04:22:29.6317900Z 150: ** Convert your input image into a supported format (see below).
2021-11-01T04:22:29.6376120Z 150: ** Contact developer to report the issue.
2021-11-01T04:22:29.6402390Z 150: * If you are an application developer, force input images to be
2021-11-01T04:22:29.6419710Z 150: loaded in a supported pixel type.
2021-11-01T04:22:29.6478930Z 150:
2021-11-01T04:22:29.6504980Z 150: e.g.: instance = itk.PyBuffer[itk.Image[itk.SS,2]].New(my_input)
2021-11-01T04:22:29.6521580Z 150:
2021-11-01T04:22:29.6581470Z 150: * (Advanced) If you are an application developer, build ITK Python yourself and
2021-11-01T04:22:29.6606990Z 150: turned to `ON` the corresponding CMake option to wrap the pixel type or image
2021-11-01T04:22:29.6623300Z 150: dimension you need. When configuring ITK with CMake, you can set
2021-11-01T04:22:29.6683270Z 150: `ITK_WRAP_${type}` (replace ${type} with appropriate pixel type such as
2021-11-01T04:22:29.6708610Z 150: `double`). If you need to support images with 4 or 5 dimensions, you can add
2021-11-01T04:22:29.6724810Z 150: these dimensions to the list of dimensions in the CMake variable
2021-11-01T04:22:29.6784960Z 150: `ITK_WRAP_IMAGE_DIMS`.
2021-11-01T04:22:29.6810720Z 150:
2021-11-01T04:22:29.6824150Z 150: Supported input types:
2021-11-01T04:22:29.6866850Z 150:
2021-11-01T04:22:29.6886940Z 150: itk.Image[itk.SS,2]
2021-11-01T04:22:29.6928350Z 150: itk.Image[itk.SS,3]
2021-11-01T04:22:29.6968720Z 150: itk.Image[itk.UC,2]
2021-11-01T04:22:29.6988820Z 150: itk.Image[itk.RGBPixel[itk.UC],2]
2021-11-01T04:22:29.7031540Z 150: itk.Image[itk.RGBAPixel[itk.UC],2]
2021-11-01T04:22:29.7070310Z 150: itk.Image[itk.UC,3]
2021-11-01T04:22:29.7090230Z 150: itk.Image[itk.RGBPixel[itk.UC],3]
2021-11-01T04:22:29.7128190Z 150: itk.Image[itk.RGBAPixel[itk.UC],3]
2021-11-01T04:22:29.7132950Z 150: itk.Image[itk.F,2]
2021-11-01T04:22:29.7193510Z 150: itk.Image[itk.SymmetricSecondRankTensor[itk.F,2],2]
2021-11-01T04:22:29.7229970Z 150: itk.Image[itk.Vector[itk.F,2],2]
2021-11-01T04:22:29.7236270Z 150: itk.Image[itk.CovariantVector[itk.F,2],2]
2021-11-01T04:22:29.7295040Z 150: itk.Image[itk.Vector[itk.F,3],2]
2021-11-01T04:22:29.7334710Z 150: itk.Image[itk.CovariantVector[itk.F,3],2]
2021-11-01T04:22:29.7338640Z 150: itk.Image[itk.Vector[itk.F,4],2]
2021-11-01T04:22:29.7396520Z 150: itk.Image[itk.CovariantVector[itk.F,4],2]
2021-11-01T04:22:29.7436410Z 150: itk.Image[itk.F,3]
2021-11-01T04:22:29.7439990Z 150: itk.Image[itk.SymmetricSecondRankTensor[itk.F,3],3]
2021-11-01T04:22:29.7498000Z 150: itk.Image[itk.Vector[itk.F,2],3]
2021-11-01T04:22:29.7537830Z 150: itk.Image[itk.CovariantVector[itk.F,2],3]
2021-11-01T04:22:29.7541550Z 150: itk.Image[itk.Vector[itk.F,3],3]
2021-11-01T04:22:29.7599130Z 150: itk.Image[itk.CovariantVector[itk.F,3],3]
2021-11-01T04:22:29.7639200Z 150: itk.Image[itk.Vector[itk.F,4],3]
2021-11-01T04:22:29.7642800Z 150: itk.Image[itk.CovariantVector[itk.F,4],3]
2021-11-01T04:22:29.7701950Z 150: itk.Image[itk.D,2]
2021-11-01T04:22:29.7740640Z 150: itk.Image[itk.SymmetricSecondRankTensor[itk.D,2],2]
2021-11-01T04:22:29.7802860Z 150: itk.Image[itk.Vector[itk.D,2],2]
2021-11-01T04:22:29.7842220Z 150: itk.Image[itk.CovariantVector[itk.D,2],2]
2021-11-01T04:22:29.7844230Z 150: itk.Image[itk.Vector[itk.D,3],2]
2021-11-01T04:22:29.7904210Z 150: itk.Image[itk.CovariantVector[itk.D,3],2]
2021-11-01T04:22:29.7943630Z 150: itk.Image[itk.Vector[itk.D,4],2]
2021-11-01T04:22:29.7945530Z 150: itk.Image[itk.CovariantVector[itk.D,4],2]
2021-11-01T04:22:29.8005560Z 150: itk.Image[itk.D,3]
2021-11-01T04:22:29.8044950Z 150: itk.Image[itk.SymmetricSecondRankTensor[itk.D,3],3]
2021-11-01T04:22:29.8046770Z 150: itk.Image[itk.Vector[itk.D,2],3]
2021-11-01T04:22:29.8106810Z 150: itk.Image[itk.CovariantVector[itk.D,2],3]
2021-11-01T04:22:29.8146350Z 150: itk.Image[itk.Vector[itk.D,3],3]
2021-11-01T04:22:29.8147830Z 150: itk.Image[itk.CovariantVector[itk.D,3],3]
2021-11-01T04:22:29.8208360Z 150: itk.Image[itk.Vector[itk.D,4],3]
2021-11-01T04:22:29.8249980Z 150: itk.Image[itk.CovariantVector[itk.D,4],3]
2021-11-01T04:22:29.8315330Z 150: itk.Image[itk.UI,2]
2021-11-01T04:22:29.8358090Z 150: itk.Image[itk.UI,3]
2021-11-01T04:22:29.8397900Z 150: itk.Image[itk.UL,2]
2021-11-01T04:22:29.8452810Z 150: itk.Image[itk.UL,3]
2021-11-01T04:22:29.8507560Z 150: itk.Image[itk.ULL,2]
2021-11-01T04:22:29.8537860Z 150: itk.Image[itk.ULL,3]
2021-11-01T04:22:29.8591970Z 150: itk.Image[itk.SI,2]
2021-11-01T04:22:29.8611040Z 150: itk.Image[itk.SI,3]
2021-11-01T04:22:29.8639990Z 150: itk.VectorImage[itk.SS,2]
2021-11-01T04:22:29.8693050Z 150: itk.VectorImage[itk.UC,2]
2021-11-01T04:22:29.8712380Z 150: itk.VectorImage[itk.F,2]
2021-11-01T04:22:29.8741220Z 150: itk.VectorImage[itk.SS,3]
2021-11-01T04:22:29.8794420Z 150: itk.VectorImage[itk.UC,3]
2021-11-01T04:22:29.8813630Z 150: itk.VectorImage[itk.F,3]
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
A few comments inline that should address this. |
Thanks for helping push this along Matt, I really appreciate it |
Maybe the Mac test failure is due to different compiler defaults. Does the test fail with clang on Linux? |
@GenevieveBuckley thank you so much for your persistence and efforts! 🧗 The error on mac does look odd. I will test locally and follow-up. |
Thanks Matt! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few minor issues noted inline after testing locally, a squash, and we will be good to rock 🚀 👨🎤
ITK.macOS.Python error:
Can be addressed by changing
to:
|
/azp run ITK.macOS.Python |
💚 |
6813bfd
to
af57f78
Compare
Finally the tests are all passing! 🎉 |
itk.SS: 'int16_t', | ||
itk.US: 'uint16_t', | ||
itk.SI: 'int32_t', | ||
itk.UI: 'uint32_t', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add the long
, long long
support here as in _long_type()
in image_type_from_wasm_type
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment suggests that maybe this was not quite what you had in mind?
The _long_type()
function behaves differently on posix vs windows machines, do we need to treat them differently here too? I'm sorry, I don't have a Windows machine handy, or I'd be able to test this out for myself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, on Visual Studio and 64-bit Windows, long is still 32-bits. GCC and clang have long as 64-bits on 64-bit machines, so we do need to treat long differently. I saw an if os.nt
as a distinguisher already used in the code to treat long differently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This size ambiguity of long
is a reason why we use the explicitly sized type names in the WebAssembly serialization interface. Recently NumPy has moved to prefer more explicit use of primative type sizes, e.g. np.float64
.
af57f78
to
773b457
Compare
773b457
to
27242f4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woohoo!! @GenevieveBuckley you rock! 💯 🍾 🎇
Hooray! 😄 It would be good to do another Dask & ITK blogpost, to let people know, so they can try it out. Would you be interested in that? I'm not familiar with ITK's release schedule, but this looks most likely to be included from the next release candidate (version 5.3rc2 and above). |
Yes, absolutely! Through this process, an interesting question came up about zstandard compression with Dask distributed. Since we are leveraging the NumPy array serialization, I am wondering if we could monkey-patch the NumPy serialization, which could help performance beyond ITK (or not)? 💭 🤔
Version 5.3rc2 is coming out now, but this will be available in 5.3rc3, coming out in the next week or two. We can test before the 5.3.0 release. |
FWIW Distributed does its own compression, which could use any of several compressors (including Zstandard). So this may already do what you want without needing to add this to pickle. |
Ok, good. John suggests we should re-run the code from the blogpost, which should tell us if this is still a problem or not. I'm mid-way through attempting to build & install itk locally, but not completely confident I'll be able to do that well. So I can either wait for the v5.3rc03 pre-release, or someone with a local itk development version could do that sooner. |
And because @thewtex doesn't seem to be on the Dask slack, I'll share a copy of this relevant comment by @jakirkham:
Blogpost link: https://blog.dask.org/2019/08/09/image-itk |
To clarify this:
I think Matt R. & I were seeing lock-ups when trying to run things in parallel with ITK and Dask. So the question would be when running this code today with a recent ITK version do they still occur? Matt M. suggests it may be fixed ( #1134 (comment) ). Either confirming the issue is fixed or still present would be useful. Including a repro if still present would also be helpful. |
This PR adds pickle support to ITK images. We want this so that ITK images can be serialized for use with Dask arrays.
Closes #1091
cc @thewtex
PR Checklist
Refer to the ITK Software Guide for
further development details if necessary.