The Stage is now implemented as a runtime-checkable Protocol.
Part of this led to the realisation that we can't create a DirectThingClient
based on a Protocol, only on a concrete Thing. While it might be
a good idea to address that in LabThings in due course, for now
we work around it by creating both a Protocol and a stub class. The stub class implements the protocol, and
can be used to declare a dependency.
I've now split the base camera code into a runtime-checkable Protocol
and a concrete base class (which is optional). This removes the requirement
for cameras to subclass `openflexure_microscope.things.camera.Camera`
and instead uses duck typing that should
work both statically and dynamically.
As part of this, I've moved the commonly-used dependencies
on the camera inside `openflexure_microscope.things.camera` for consistency, and to make it easy to update them if it changes in the future.
I've now defined parent classes for camera and stage, and depend only on those. This allows
proper typing while enabling the actual hardware
classes to be swapped.
The interfaces definitely aren't final - this is a first
draft, largely to enable me to test out the concept
of a configuration server.
If any pixels in the manually processed raw image end up
saturated (>255), this causes an error. I've now manually
clamped the values between 0 and 255 to fix this.
It would probably be a good idea to warn when this happens,
as it does mean we're losing data.
I've replicated more of the camera pipeline in the images
saved during scans - in particular, I've added the colour
correction matrix (increases saturation) and the contrast
agorithm (implements gamma correction).
This slows down saving to ~0.5-2 seconds, but that's still less time than it takes to move.
I've swapped `capture_jpeg` for `capture_array`. This will acquire a raw image and process it manually. Currently I've included LST correction, colour balance, but **not** the colour unmixing matrix. In the future we could add this to implement proper correction for saturation.
Currently EXIF data does not seem to be working properly.
Removed maximum scan size of 750 images
Save json of scan inputs and use it for future stitching (ensures consistent parameters)
Moved looping autofocus to autofocus thing from recentering thing
Rewrote looping autofocus to skip unneeded moves, as if peak is out of range, no need to nicely focus
I've swapped out various `logging.exception` calls for
`logging.error` with `exc_info` set to the exception.
My reading of the docs is that the two shoulld be equivalent,
but the former fails with an error related to
message formatting, as if there was an extra (or missing) '%'.
I've changed the format of the downloaded zip file, so that it
now puts the stitched images at the top level (if they exist).
I've also modified the time of the last stitched image so it
returns None if it doesn't exist - this avoids HTTP errors in
the client.
Removed an unnecessary `Optional` and removed the `scan_` prefix from scans that have a non-empty name.
I also added a closing ` to a logging statement.
I now define the variables needed in the finally: block at
the very start, so I can test if they are None.
I've reordered the final move so it can happen as we are awaiting the background processes. I've added exception handling so this won't cause problems with the lock.
We've removed the initial double-autofocus (we just do a
single looping autofocus at the start), and now only do 3 retries rather than 5 if the focus is rejected.
I'm now running stitching in subprocesses during the scan.
This seems to be working well - I'm using multiple CPU
cores and getting images out. However, the only way to
avoid circular dependencies was to combine the Things.
I'm still getting not-infrequent memory allocation errors from the camera, which may or may not be related.