-
Notifications
You must be signed in to change notification settings - Fork 11
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
from_images: python-native czi support and timepoint and channel multiplexing #822
Conversation
@@ -987,7 +989,10 @@ def add_layer_from_images( | |||
use_bioformats: bool = False, | |||
channel: Optional[int] = None, | |||
timepoint: Optional[int] = None, | |||
czi_channel: Optional[int] = None, |
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 have to admit that this whole method is growing quite large. It might be a good idea to extract it into its own module, but not quite sure.
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.
From a higher level this PR looks good, but it could use lots of code comments, because it's hard to grasp the domain concepts when one isn't really deep in this topic.
Dataset.from_images now adds a layer per timepoint and per channel (if the data doesn't have 1 or 3 channels).
Is there a layer limit? Will this create 100 layers when there are 100 timepoints?
possible_layers = pims_images.get_possible_layers() | ||
if ( | ||
possible_layers is not None | ||
and truncate_rgba_to_rgb | ||
and len(possible_layers.get("channel", [])) == 4 | ||
): | ||
if len(possible_layers) == 1: | ||
possible_layers = None | ||
else: | ||
del possible_layers["channel"] | ||
if possible_layers is not None: | ||
if allow_multiple_layers: | ||
suffix_with_kwargs = { | ||
"__" + "_".join(f"{k}={v}" for k, v in sorted(pairs)): dict(pairs) | ||
for pairs in product( | ||
*( | ||
[(key, value) for value in values] | ||
for key, values in possible_layers.items() | ||
) | ||
) | ||
} | ||
print(suffix_with_kwargs) | ||
else: | ||
suffix_with_kwargs = {"": {}} | ||
warnings.warn( | ||
f"There are dimensions beyond channels and xyz which cannot be read: {possible_layers}. " | ||
"Defaulting to the first one. " | ||
"Please set allow_multiple_layers=True if all of them should be written to different layers, " | ||
"or set specific values as arguments.", | ||
RuntimeWarning, | ||
) | ||
else: | ||
suffix_with_kwargs = {"": {}} |
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.
please add some comments here. I don't know whats going on with suffix_with_kwargs
(suffix
doesn't seem to be used by the way). Also the semantics of possible_layers == None
and the deleted possible_layers["channel"]
are not clear to me.
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.
(suffix doesn't seem to be used by the way).
suffix
is used as a layer name suffix below in self.add_layer(…)
, I renamed it to layer_name_suffix
. Also added many more comments what's happening there and renamed some vars to make it more clear.
elif len(images.shape) == 5: | ||
if images.shape[2] == 1 or ( | ||
images.shape[2] == 3 and images.dtype == np.dtype("uint8") | ||
): | ||
self._img_dims = "cyx" | ||
else: | ||
self._img_dims = "yxc" | ||
self._iter_dim = "z" | ||
self._timepoint = 0 | ||
if images.shape[0] > 1: | ||
self._possible_layers["timepoint"] = list( | ||
range(0, images.shape[0]) | ||
) |
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.
some comments would be nice. jumping from another domain into this code is quite hard 🙈
@philippotto Thanks a lot for the review!
I added a couple comments, refactored some parts slightly and used better variable names. What do you think about it now? Where is still more explanation necessary?
There's not limit currently. |
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.
Thank you for improving the code readability :) There's one code location left where I had some trouble with understanding. Other than that, it's already looking pretty good :)
if timepoint is not None: | ||
raise RuntimeError( | ||
f"Got {len(images.shape)} axes for the images, " | ||
+ "cannot map to 3D+channels+timepoints." |
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.
Maybe mention that timepoint must be None for this shape to work?
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.
Ah, this error message is misleading. This only is thrown if timepoint is already set, and the image is 5d after removing the time dimension. I improved the error message.
# Below, we iterate over suffix_with_pims_open_kwargs_per_layer in the for-loop | ||
# to add one layer per possible_layer if allow_multiple_layers is True. | ||
# If just a single layer is added, we still add a default value in the dict. |
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.
does this comment really refer to the immediate code below? because the code below merely creates suffix_with_pims_open_kwargs_per_layer
(with a for comprehension) and doesn't loop over 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.
Not directly, changed it to Further below, …
suffix_with_pims_open_kwargs_per_layer = { | ||
"__" + "_".join(f"{k}={v}" for k, v in sorted(pairs)): dict(pairs) | ||
for pairs in product( | ||
*( | ||
[(key, value) for value in values] | ||
for key, values in possible_layers.items() | ||
) | ||
) | ||
} |
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'm a bit lost what is happening in detail here. pairs
seems to be a list of key-value entries which can be passed as kwargs to pims? However, I don't understand how this is derived from possible_layers.items()
and product(...)
. Maybe you can add a comment which lists an example of what possible_layers.items()
could be and what the expected suffix_with_pims_open_kwargs_per_layer
value would be then. When input and output is known, it might be easier to follow the code :)
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.
Good idea, I added an example 👍
RuntimeWarning, | ||
) | ||
else: | ||
suffix_with_pims_open_kwargs_per_layer = {"": {}} |
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 basically means "call PIMS" without any kwargs and name the new layer like the user requested it (i.e., without suffix), right?
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.
Exactly 👍 Also added a comment about it.
@philippotto Thanks for re-checking, I added some more comments ✍️ What do you think about the number of layers, should we limit it to some maximum?
|
I think more than 20 layers are not really feasible to render in webKnossos (at the moment). This is why I'd suggest to limit it so that only the first X timepoints are used. There could be an "ignore_layer_limit" parameter if the user wants to circumvent this. But I don't think it's necessary. |
I added a |
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.
Awesome :)
Description:
Dataset.from_images
now adds a layer per timepoint and per channel (if the data doesn't have 1 or 3 channels).Dataset.from_images
ordataset.add_layer_from_images
, without using bioformats.dataset.add_layer_from_images
can add a layer per timepoint and per channel when passingallow_multiple_layers=True
.Issues:
Todos: