Lazy transformations of large volumes #1224
Replies: 12 comments 17 replies
-
Beta Was this translation helpful? Give feedback.
-
Yes, applying after extracting patches has its problems as the transform is applied within the coordinate frame of the patch rather than the coordinate frame of the originating volume. Affine transform is a perfect and simple example of this: the effect of scale and translation does not depend upon the location of the volume's origin but, as your example shows, the effect of rotation absolutely does. In principle, this is not a difficult problem. Most transforms (even certain composite transforms) form a bidirectional mapping of coordinates between input and output coordinate frames. When you want to access a value in the output coordinate frame, you apply the inverse transform to the output coordinate to get a coordinate in the original frame, interpolate the value, and return. This is how ITK's By the by, this sort of composable transform framework would immediately handle situations like #1052 where you want to avoid the bottleneck of multiple interpolations. |
Beta Was this translation helpful? Give feedback.
-
Yes I agree, it can be implement properly. It is easy with affine transform, and I would like also to have it for the resampling. The tricky part is how to insert it properly. My guess is that we need to add the transform to the torchio sampler because for a given output location one want a different (driven by the affine) input location ... But the main problem is that there is no "lazy data reading in torchio : (or I miss something ?...) if you want a patch torchio will read the whole volume. |
Beta Was this translation helpful? Give feedback.
-
I like the idea, (and I also need it , I start having memory issue with high resolution above 512^3) @fepegar what about adding h5py support ? @csparker247 : does the HDF also handle the affine with the same convention as the nifti ?... (because you forget the affine in your last code line ex) |
Beta Was this translation helpful? Give feedback.
-
One tricky implementation detail here is not so much about the resampling as it is the stochastic nature of the transforms in this project. If we compose multiple randomized transforms, when should the parameters for those transforms be updated? It seems like one might want the option of doing it at different times. As an example: # build transforms
tfm = VirtualCompose(list_of_transforms)
# virtual dataset with virtual transform
tvol = tfm(vol)
for i in range(0, 100, 10):
# same transform applied to each chunk
s = tvol[i:i+10, i:i+10, i:i+10]
# option to update params on slice op
tvol = tfm(vol, update_on_slice=True)
for i in range(0, 100, 10):
# different transform applied to each chunks
s = tvol[i:i+10, i:i+10, i:i+10] It seems like the former would probably be more useful in a |
Beta Was this translation helpful? Give feedback.
-
Yes for the random transform point of view, one do not really care to have a coherent transform among patches. but there are so use case were we do. I would like to have a unique (random or not) transform and apply it (patch wise) to the whole volume. Instead of implementing, as you suggest, a Virtual transform, I was thinking to implement a patch sampler, that can also apply transform to the patches. |
Beta Was this translation helpful? Give feedback.
-
I am open to suggestion The difficulty I see with your Virtual Transform is how to implement the virtual loading and applying on the fly the adapted transform. On the other hand, going with torchio patche-based pipelines, I can see the steps of course it is easier for Affine, since the transform is the same (and only parametrised with 4*4 matrix) on all patch. where as for Elastic you need to define it on the global volume (num_control_points ). this mean you also compute the bspline on the whole image (their may be memory issue here) and then just slide it (corresponding to the selected patch). For this transform the option update_on_slice=True will be easier to implement. ... (parameter are define at the patch level only ...) Since the logic is different depending on the transform, so it goes in your direction, to implement a VirtualTransform ... |
Beta Was this translation helpful? Give feedback.
-
I am not sure sitk will accept a "lazy" array the way it is implemented in torchio is through the nib_to_sitk So I think one need to construct the sitk volume, with only the input subregion needed. (invert transform of output region and takes the smallest box that contain it (since it can be rotated or deformed ...) |
Beta Was this translation helpful? Give feedback.
-
The problem I can not solve yet is how to build the affine that we want to apply to the input patch, so that the output result would be the same as the affine apply to the whole volume. Let's consider to start with, a simple rotation 20 ° around z axis at (whole) image center. I first though that one should take the same rotation 20° around z axis and only change the rotation center so that it correspond (in the patch referential) to the rotation center define on the whole image Since it does not work, I look at rotation applyed on the whole image, and just changing the center, with sitk SetRotation method of Euler3DTransform but it does not do what I expect ... |
Beta Was this translation helpful? Give feedback.
-
Yes I try to set the input (and output) patch affine correctly so that the get the same spatial position as the full volume. Yes it would be much better not to change the transform (so that it can be any transform) but when trying with rotation, I get confuse ... I was concerned about this line So I just try without the default value for center (so that sitk center is not change) but I still get black parts in my recomposed image, I need to triple check ... |
Beta Was this translation helpful? Give feedback.
-
Ok, now about lazy loading : It is almost ok if you use torchio patch sampler. In order to make it work I had to made those changes
This change allow to create a torchio image, but hdf5 numpy like array Torchio was implemented so that all data are always store as torch tensor. (which we want to avoid here in order not to reall the all file)
(and add With the preceding changes in torchio you can now do lazy loading with patch sampler (@fepegar would a PR with those changes be likely accepted ?)
the for loop run without loading the whole data from disk if you use random transform, then you get a different random transform for each patch, but for data augmentation during training, this may be good enough Next step is to have "coherent" spatial transform so that applying it at the patch level would lead to the same result as for the whole volume. This is what we discuss previously. I make it work for the affine, but it should work for any sitk transform too. |
Beta Was this translation helpful? Give feedback.
-
Not sure if usefull, but I add here a more detail example with : |
Beta Was this translation helpful? Give feedback.
-
I just came across this project and am very interested in the data augmentations and transforms. However, I tend to work with volumes that don't easily fit in memory (sometimes multiple terabytes in size), so I use
h5py
orzarr
to have a Numpy-like array interface with lazy loading of only the volumetric regions that I need to access. Looking around at the documentation, it seems quite trivial to apply thetorchio
augmentations and transforms at the subregion level (just load the subregion into an ndarray and go from there), but I was wondering if you've considered support for "lazy transformations" where transforms are applied on-the-fly for only the specific pixel values requested by a slice or getitem operation.To make this more tangible, it would be great if I could virtually apply an elastic deformation to my entire multi-terabyte volume, but the pixel values would only be interpolated within the bounds of a 3D subregion that I request:
Or maybe this functionality already exists in this project! I would love to know about it if it does. I know that ITK was designed to do this type of thing in the C++ world, but it was not particularly fun to work with, so I'm not sure how much of that paradigm has made it into SimpleITK.
Beta Was this translation helpful? Give feedback.
All reactions