funkelab / gunpowder

A library to facilitate machine learning on multi-dimensional images.
https://funkelab.github.io/gunpowder/
MIT License
78 stars 56 forks source link

Question: 2D data from 3D provider #166

Closed erjel closed 2 years ago

erjel commented 2 years ago

Hi,

I have a question: Is there an option to drop a spatial dimension in a pipeline? Currently I have 3D volume (c, x,y,z) and would like to extract random (x,y) slices. I figured, that something like:

pipeline = source + gp.RandomLocation() + gp.Squeeze([raw], axis=3)
with build(pipeline):
    batch = pipeline.request_batch(
        BatchRequest({
            raw: ArraySpec(roi=Roi((0, 0), (100, 100))),
       })
    )

Would probably work, but I get the error message:

gunpowder/nodes/random_location.py:236: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = [0:100, 0:100, 0:100] (100, 100, 100), by = (0, 0)

    def shift(self, by):
        '''Shift this ROI.'''

>       return Roi(self.__offset + by, self.__shape)

gunpowder/roi.py:258: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = (0, 0, 0), other = (0, 0)

    def __add__(self, other):

        assert isinstance(other, tuple), f"can only add Coordinate or tuples to Coordinate. {type(other)} is invalid"
>       assert self.dims() == len(other), "can only add Coordinate of equal dimensions"
E       AssertionError: can only add Coordinate of equal dimensions
E       assert 3 == 2
E         +3
E         -2

gunpowder/coordinate.py:46: AssertionError

So I am stuck here and would appreciate any help....

Eric

funkey commented 2 years ago

Hi Eric,

This one is a bit tricky, since we would be mixing 2D and 3D ROIs. Here are some (imperfect) options:

  1. Make a 3D request of shape (1, h, w). Everything is still treated as a 3D volume, but you get only a single slice. With RandomLocation, this will select a random z section, which I assume is what you want. Use Squeeze and Unsqueeze to get the arrays in the right shape (e.g., if you train with torch.Train, you probably need a batch and channel dimension anyway).
  2. Write a node that takes a 2D request and turns it into a 3D request by adding an extra dimension with size 1 in prepare() for each array request in it. Undo the same in process(), i.e., take each array, assert that the z dimension is of size 1, squeeze the array .data and update its .spec to be 2D.
  3. Write a custom source node that returns a 2D array for a given z index. Create as many of those source nodes as you have z sections (sources are lightweight, but if you have many z sections this might still cause some overhead). Add a RandomProvider after the sources to select a random z section on each request.

I would recommend option 1, i.e., treat everything as 3D, one section thick.

erjel commented 2 years ago

Hi Jan,

thank you for the quick response!

I tried the first option, but I have the problem that the z dimension is considered a spatial coordinate (c, x, y, z) and therefore I can not use Squeeze. I went for the 2. option and created a general ReduceDim node. I will test how much of an overhead this will create and how far I can go from here.

Eric