-
Notifications
You must be signed in to change notification settings - Fork 41
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
Clarity support with pipeline for data processing #124
Conversation
@@ -250,6 +261,7 @@ def initialize(self): | |||
""" | |||
_logger.info('Initializing.') | |||
time.sleep(0.5) | |||
self._initialized = True |
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.
Why not add this attribute during object construction? This would mean that one only neds to check for it's value and not have always also check if the attribute already exists. Also, should it not be set back to False
after shutdown?
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.
Perhaps not a good reason, but this would still work if a device fails to
set that member during init. In the TestCamera case, it's a simple
attribute. If we used a similar idea on real hardware, we might want some
other test (with library calls) to test that the devices is correctly
initialized, so _initialized could then be a property.
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 change is only being made to TestCamera
so there is no what other device could do.
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, because I've introduced it here for now, but we might want to move it over to the base Device class if it's useful.
microscope/devices.py
Outdated
(0, 1): numpy.flipud(numpy.rot90(data, rot)), | ||
(1, 0): numpy.fliplr(numpy.rot90(data, rot)), | ||
(1, 1): numpy.fliplr(numpy.flipud(numpy.rot90(data, rot))) | ||
}[flips] |
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 introduced in this change but isn't this indexing of the dict always perfoming all flips and rotations and then we are discarding them all except one? If so, shouldn't we ensure that we only do the transformation that is actually required?
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 point. I assumed lazy evaluation, but maybe that's not the case. Perhaps lambdas would be more appropriate.
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 not evaluated lazily. I'll wrap it with lambda.
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.
Fix included in this PR: cfff4a5.
Subclasses should call super()._process_data(data) after doing their | ||
own processing.""" | ||
import functools | ||
return functools.reduce(lambda x, f: f(x), self.pipeline, 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.
If we have an attribute which is a list of processing steps, why should subclasses do their own processing first instead of just appending to self.pipeline
?
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 - I think we should do one or the other. This requires some thought, though.
Originally _process_data
was a method on DataDevice
s to implement the processing step, and I think that was meant to be pure virtual.
Now we have Devices
that are not DataDevices
that need to add processing steps, and I addressed that with the pipeline, but that needs super()._process_data()
to ensure that the pipeline is executed after the DataDevice
does anything it needs to do (e.g. running it through a LUT, type conversions, reshaping arrays).
The DataDevice
could just add it's processing to the start of the pipeline, instead, and _process_data
would be implemented on DataDevice
and just process the pipeline.
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.
See comments in review immediately below this one.
@@ -409,6 +409,8 @@ def __init__(self, buffer_length=0, **kwargs): | |||
self._acquiring = False | |||
# A condition to signal arrival of a new data and unblock grab_next_data | |||
self._new_data_condition = threading.Condition() | |||
# A data processing pipeline: a list of f(data) -> data. | |||
self.pipeline = [] |
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.
Should we not make this a private attribute? Also, would be nice to also have type annotations for 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.
Maybe it should be private, but then we'd need methods to allow other devices to add their processing steps.
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 talked about this at length.
- Most (all?) classes derived from DataDevice should probably add to the pipeline rather than override _process_data.
- Devices that override _process_data must call super()._process_data().
- The docstring on DataDevice._process_data should indicate this clearly.
- We could make pipeline private, but that would require methods to add/remove steps from 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.
cam_kwargs = {} | ||
for key in cam_kw_keys: | ||
cam_kwargs[key.replace("camera.", "")] = kwargs[key] | ||
del kwargs[key] |
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.
Would a camera_kwargs
argument with the arguments to construct the camera make this cleaner?
def __init__(self, camera=None, camera_kwargs={}, **kwargs):
super().__init__(**kwargs)
if camera is not None:
self._camera = camera(**camera_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.
It might make the code a bit cleaner, but then you'd have nested { {} }
in the config, and maybe that's not cleaner / more error-prone.
I have spoken with Mick earilier today in person about this PR and the This Clarity mixing a Not mentioned anywhere yet on this PR is that the reasoning behind However, we are just delaying the problem and we will eventually need For now, we need this Aurox interface and it does do what we need it |
Leaving this unhandled could prevent other devices in the same process (e.g. on a controller) being shut down.
get_all_settings reports the values of problem settings as None. Previously, we considered adding a new entry, __errors__, to the returned dict, but this is adding an item that is not a setting to a dict where everything else is a setting, and would require clients to handle this key as a special case.
Fixed ClarityProcessor import and conversion of UMat to ndarray.
Dict evaulation is not lazy: previously, this code evaluated all allowable transforms before picking the appropriate one. This is now fixed by breaking it down into the rotation transfrom, then passing the result to one function from a dict of flip transform functions.
I've addressed some of the review items, and rebased this onto master.
|
It just occurred to me that we can't use a dot on parameter name, the following will fail because dot can't be used in a keyword:
even though this works:
|
So it currently only works because the ```DeviceServer``` uses the second
method to call the constructor?
…On Mon, 6 Jan 2020 at 11:25, Carnë Draug ***@***.***> wrote:
whether to use camera.some_parameter=x or camera_kwargs =
{'some_parameter': x}
It just occurred to me that we can't use a dot on parameter name, the
following will fail because dot can't be used in a keyword:
Clarity(camera.some_parameter=x)
even though this works:
Clarity(**{'camera.some_parameter':x})
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<https://github.com/MicronOxford/microscope/pull/124?email_source=notifications&email_token=ABHGTL37QU5AIMBZNDHPU3DQ4MIMBA5CNFSM4JV5D3M2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIFF4LI#issuecomment-571104813>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABHGTL5YB2FE7NBTBNPD3F3Q4MIMBANCNFSM4JV5D3MQ>
.
--
________________________________
Mick Phillips
________________________________
|
Yes. This point is just about the use of a period on an argument name, it's not about whether passing a dict of arguments to the camera vs passing multiple args marked for the camera. |
I think we need another approach for this. Data processing can modify the shape of returned arrays, yet when we query this, the request goes to the underlying data source which returns the original shape. With the pipeline approach on one method, it's not easy to address this. A better approach might be to take the underlying camera class, dynamically create an augmented class that modifies both the data collection and data shape methods, and serve an instance of that augmented class. |
We have #133 which is a different implementation also from Mick, that addresses some of the issues from this solution. We are not merging this. Closing. |
Addresses issue #121. Needs testing with hardware.