Lock access to picamera

The picamera object isn't thread safe; I have now wrapped all access
in context managers that acquire the BaseCamera.lock

I also added an endpoint to reset the LST without auto-gain.
This commit is contained in:
Richard Bowman 2021-02-15 20:15:55 +00:00
parent a9709b14ea
commit 069d27bf15

View file

@ -6,6 +6,8 @@ from flask import abort
from labthings import find_component from labthings import find_component
from labthings.extensions import BaseExtension from labthings.extensions import BaseExtension
from labthings.views import ActionView from labthings.views import ActionView
from picamerax import PiCamera
from typing import Tuple
from openflexure_microscope.camera.base import BaseCamera from openflexure_microscope.camera.base import BaseCamera
from openflexure_microscope.microscope import Microscope from openflexure_microscope.microscope import Microscope
@ -78,9 +80,14 @@ class LSTExtension(BaseExtension):
"/auto_white_balance_from_raw", "/auto_white_balance_from_raw",
endpoint="auto_white_balance_from_raw", endpoint="auto_white_balance_from_raw",
) )
self.add_view(
AutoLensShadingTableView,
"/auto_lens_shading_table",
endpoint="auto_lens_shading_table",
)
@contextmanager
def find_microscope() -> Microscope: def find_picamera() -> Tuple[PiCamera, BaseCamera, Microscope]:
"""Locate the microscope and raise a sensible error if it's missing.""" """Locate the microscope and raise a sensible error if it's missing."""
microscope = find_component("org.openflexure.microscope") microscope = find_component("org.openflexure.microscope")
@ -89,23 +96,17 @@ def find_microscope() -> Microscope:
503, "No microscope connected. Unable to use camera calibration functions." 503, "No microscope connected. Unable to use camera calibration functions."
) )
if not hasattr(microscope.camera, "picamera"): scamera = microscope.camera
if not hasattr(scamera, "picamera"):
abort(503, "The PiCamera calibration plugin requires a Raspberry Pi camera.") abort(503, "The PiCamera calibration plugin requires a Raspberry Pi camera.")
return microscope with scamera.lock:
yield scamera.picamera, scamera, microscope
class RecalibrateView(ActionView): class RecalibrateView(ActionView):
args = { args = {}
"skip_auto_exposure": fields.Bool(
missing=False,
description=(
"By default we perform an auto-exposure and AWB "
"step before updating the lens shading table. Set "
"this argument to True to disable those steps."
),
)
}
def post(self, args): def post(self, args):
"""Reset the camera's settings. """Reset the camera's settings.
@ -113,35 +114,60 @@ class RecalibrateView(ActionView):
This generates new gains, exposure time, and lens shading This generates new gains, exposure time, and lens shading
table such that the background is as uniform as possible table such that the background is as uniform as possible
with a gray level of 230. It takes a little while to run. with a gray level of 230. It takes a little while to run.
This consists of three steps:
* Reset gain and exposure time, then increase them until
we have a suitably bright image
* Set the auto white balance based on a raw image
* Set the lens shading table based on a raw image
Each of these steps has its own endpoint if you want to
perform them separately.
""" """
microscope = find_microscope() with find_picamera() as (picamera, scamera, microscope):
logging.info("Starting microscope recalibration...") logging.info("Starting microscope recalibration...")
picamera = microscope.camera.picamera adjust_shutter_and_gain_from_raw(picamera)
with microscope.camera.lock: adjust_white_balance_from_raw(picamera)
if not args.get("skip_auto_exposure"):
adjust_shutter_and_gain_from_raw(picamera)
adjust_white_balance_from_raw(picamera)
lst = lst_from_camera(picamera) lst = lst_from_camera(picamera)
with pause_stream(microscope.camera) as scamera: with pause_stream(scamera):
scamera.picamera.lens_shading_table = lst picamera.lens_shading_table = lst
microscope.save_settings() microscope.save_settings()
class AutoLensShadingTableView(ActionView):
args = {}
def post(self, args):
"""Perform flat-field correction
This routine acquires a new image (which should be an
empty field of view, i.e. a perfect microscope would
record a uniform white image), and then uses it to set
the "lens shading table" such that future images will
be corrected for vignetting.
"""
with find_picamera() as (picamera, scamera, microscope):
logging.info("Generating lens shading table")
lst = lst_from_camera(picamera)
with pause_stream(scamera):
picamera.lens_shading_table = lst
microscope.save_settings()
class FlattenLSTView(ActionView): class FlattenLSTView(ActionView):
def post(self): def post(self):
microscope = find_microscope() with find_picamera() as (picamera, scamera, microscope):
with pause_stream(microscope.camera) as scamera: with pause_stream(scamera):
flat_lst = flat_lens_shading_table(scamera.camera) flat_lst = flat_lens_shading_table(picamera)
scamera.camera.lens_shading_table = flat_lst picamera.lens_shading_table = flat_lst
microscope.save_settings() microscope.save_settings()
class DeleteLSTView(ActionView): class DeleteLSTView(ActionView):
def post(self): def post(self):
microscope = find_microscope() with find_picamera() as (picamera, scamera, microscope):
with pause_stream(microscope.camera) as scamera: with pause_stream(scamera):
scamera.camera.lens_shading_table = None picamera.lens_shading_table = None
microscope.save_settings() microscope.save_settings()
class AutoExposureFromRawView(ActionView): class AutoExposureFromRawView(ActionView):
@ -180,12 +206,8 @@ class AutoExposureFromRawView(ActionView):
} }
def post(self, args): def post(self, args):
camera = find_component("org.openflexure.microscope").camera with find_picamera() as (picamera, scamera, microscope):
adjust_shutter_and_gain_from_raw(picamera, **args)
with camera.lock:
adjust_shutter_and_gain_from_raw(
camera.picamera, **args # I'm relying on the schema to fill in missing values
)
class AutoWhiteBalanceFromRawView(ActionView): class AutoWhiteBalanceFromRawView(ActionView):
@ -203,10 +225,8 @@ class AutoWhiteBalanceFromRawView(ActionView):
} }
def post(self, args): def post(self, args):
camera = find_component("org.openflexure.microscope").camera.picamera with find_picamera() as (picamera, scamera, microscope):
adjust_white_balance_from_raw( adjust_white_balance_from_raw(picamera, **args)
camera, **args # I'm relying on the schema to fill in missing values
)
class GetRawChannelPercentilesView(ActionView): class GetRawChannelPercentilesView(ActionView):
@ -219,5 +239,5 @@ class GetRawChannelPercentilesView(ActionView):
schema = fields.List(fields.Integer) schema = fields.List(fields.Integer)
def post(self, args): def post(self, args):
camera = find_component("org.openflexure.microscope").camera.picamera with find_picamera() as (picamera, scamera, microscope):
return get_channel_percentiles(camera, args["percentile"]) return get_channel_percentiles(picamera, args["percentile"])