-
Notifications
You must be signed in to change notification settings - Fork 5
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
Ticket/425/motion/correction/tiffs #433
Conversation
this will allow us to more easily swap in something other than a median_filter on demand
(not kernel_size)
TIFFs are limited to 4GB in size because of 32 bit offsets https://stackoverflow.com/questions/62245475/how-to-save-a-very-large-numpy-array-as-an-image-loading-as-little-as-possible
Codecov Report
@@ Coverage Diff @@
## main #433 +/- ##
==========================================
+ Coverage 90.23% 90.57% +0.34%
==========================================
Files 88 89 +1
Lines 5426 5456 +30
==========================================
+ Hits 4896 4942 +46
+ Misses 530 514 -16
Flags with carried forward coverage won't be shown. Click here to find out more.
|
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.
Found one thing that I have questions about. Also, there seems to be a lot of duplicated code in this module/ticket. Is there a way of possibly eliminating some of the duplicated code?
@@ -16,8 +17,23 @@ class VideoBaseSchema(argschema.ArgSchema): | |||
required=False, | |||
allow_none=True, | |||
default=3, | |||
description=("Radius of median filter kernel; " | |||
"if None, no median filter applied")) | |||
description=("Size of spatial filter kernel; " |
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.
Can you instead add a new argument use_spatial_filter
to control whether to apply spatial filtering? Passing None or 0 here is a little awkward, imo.
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 am of the opposite opinion. I don't like the idea of adding more parameters to the schema. It clutters up --help
and leaves more places for users to screw up (setting use_spatial_filter=False, kernel_size=2
when they intended to use a kernel of size 2). I like the idea that you can just turn filtering off by setting the kernel size to nothing. We can talk about this, if you want.
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.
In that case you could just issue a warning that use_spatial_filter
is False but kernel_size
is not 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.
To me, that seems just as complicated as the present
use_kernel = False
if self.args['kernel_size'] is not None:
if self.args['kernel_size'] > 0:
use_kernel = True
I'm going to leave this.
quantiles = (0.0, 1.0) | ||
|
||
use_kernel = False | ||
if self.args['kernel_size'] is not 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.
Adding a new argument use_spatial_filter
will simplify this logic
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 know, I just think the simplicity in the CLI is worth it.
@@ -30,13 +28,14 @@ def create_downsampled_video( | |||
input_hz: float, | |||
video_path: pathlib.Path, | |||
output_hz: float, | |||
kernel_size: Optional[int], | |||
spatial_filter: Optional[Callable[[np.ndarray], np.ndarray]], |
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.
Can create_downsampled_video_h5
instead convert the spatial_filter
argument as a string into a function rather than the CLI constructing it and passing a function around?
If we wanted to use this without going through the CLI, we would have to replicate that logic.
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 actually like that these methods accept callables. It means that, outside of the CLI, we can quickly experiment with new spatial filters by just passing in a function without having to implement the if/else
block necessary to parse a string into a function.
Again: open to discussion, but I do see this as a feature, rather than a bug.
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.
Sure, then you allow this function to accept string or callable. If string, it converts it into a callable.
This would not force the user to go through the CLI to be able to properly pull the correct function for "mean" or "median" filters and would allow your use case of passing a custom callable for experimenting.
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 going to leave this. Your proposal would require adding a kernel_size
arg to create_downsampled_video_h5
. I'm not a huge fan of increasing the number of arguments more than we have to. When/if we move to class-based video manipulation, I suspect the apply_spatial_filter()
method will be general enough to accept an object that is a spatial filter (I know I'm talking like someone who lives in C++
-ville; in python, it would just be a callable). I still like the generalizability of that. We can revisit this conversation when we move to class-based manipulation of videos.
""" | ||
|
||
downsampled_video = skimage_measure.block_reduce( | ||
video, |
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.
It's a bummer we can't combine our median filter with mean filter somehow. They are both doing the same thing except they use different functions and do/don't do downsampling
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.
Probably the responsible thing to do would be to consolidate these in a video_utils
module. Do you mind if we leave that work fro #436 (if you agree, I'll add this as an explicit bullet to that ticket)?
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.
Looks good, left some nitpicks.
@@ -16,8 +17,23 @@ class VideoBaseSchema(argschema.ArgSchema): | |||
required=False, | |||
allow_none=True, | |||
default=3, | |||
description=("Radius of median filter kernel; " | |||
"if None, no median filter applied")) | |||
description=("Size of spatial filter kernel; " |
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.
Sorry can you please make it clear that these kernels apply specifically downsampling . It is possible to apply a sliding kernel with no downsampling
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.
Our default median filter actually doesn't apply downsampling.
The size of the frame coming in is the size of the frame coming out
>>> import numpy as np
>>> import scipy.ndimage as scipy_ndimage
>>> f = np.random.random((25, 25))
>>>
>>> f2 = scipy_ndimage.median_filter(f, size=3, mode='reflect')
>>> f.shape
(25, 25)
>>> f2.shape
(25, 25)
(because this isn't complicated enough...)
allow_none=False, | ||
default='median', | ||
validation=OneOf(('median', 'mean')), | ||
description=("Type of spatial smoothing kernel to be " |
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.
Same thoughts above about mentioning "downsampling". Also I'm assuming the "downsampling" mentioned already here is the temporal downsampling
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 modified the docstring to explicitly call out that the "mean" filter downsamples the video frames while the "median" filter does not.
quantiles = (self.args['lower_quantile'], | ||
self.args['upper_quantile']) | ||
else: | ||
quantiles = (0.0, 1.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.
Could you instead change the default to 0 and 1 instead of None
? Then this logic wouldn't be needed.
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.
Done
@@ -16,8 +17,23 @@ class VideoBaseSchema(argschema.ArgSchema): | |||
required=False, | |||
allow_none=True, | |||
default=3, | |||
description=("Radius of median filter kernel; " | |||
"if None, no median filter applied")) | |||
description=("Size of spatial filter kernel; " |
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.
In that case you could just issue a warning that use_spatial_filter
is False but kernel_size
is not None
@@ -0,0 +1,44 @@ | |||
from ophys_etl.modules.video.utils import ( |
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.
Could you please make it clear that this is specific to the CLI? Ie by calling this module cli_mixin
or by placing it in a cli
package
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.
Done
@@ -30,13 +28,14 @@ def create_downsampled_video( | |||
input_hz: float, | |||
video_path: pathlib.Path, | |||
output_hz: float, | |||
kernel_size: Optional[int], | |||
spatial_filter: Optional[Callable[[np.ndarray], np.ndarray]], |
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.
Sure, then you allow this function to accept string or callable. If string, it converts it into a callable.
This would not force the user to go through the CLI to be able to properly pull the correct function for "mean" or "median" filters and would allow your use case of passing a custom callable for experimenting.
kernel_size: Optional[int] | ||
Size of the median filter kernel to be applied to the downsampled | ||
movie (if None, no median filter will be applied) | ||
spatial_filter: Optional[Callable[[np.ndarray], np.ndarray]] |
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.
My thinking is this -- if what you are modeling can be thought of as a thing, then it should use a class-based design. Video is a thing, therefore it should use a class-based design. I don't think we necessarily need to carry around the whole video in memory with this approach.
src/ophys_etl/modules/video/utils.py
Outdated
dtype=float) | ||
|
||
mgr = multiprocessing.Manager() | ||
output_lock = mgr.Lock() | ||
validity_dict = mgr.dict() | ||
process_list = [] | ||
|
||
print(f'spatial filter {spatial_filter}') |
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 you meant to use the logger?
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 actually meant to remove that print statement. I added that when I was implementing spatial_filter
to make sure it got passed through. Thanks for catching that.
run time was getting excessive
bd5a33e
to
534a109
Compare
During the 2/2/2022 meeting with the SSF team, the scientists requested that we produce spatiotemporally downsampled TIFF files to support their QC of motion correction. This PR modifies our existing
ophys_etl/modules/video
modules to produce those files. Examples of the output can be found inJun Zhuang has looked at examples of these files and is happy with the output.