Updated all log strings to new format
This commit is contained in:
parent
36e837d374
commit
9f5252194a
35 changed files with 121 additions and 715 deletions
|
|
@ -14,6 +14,5 @@ repos:
|
||||||
- id: pylint
|
- id: pylint
|
||||||
name: pylint
|
name: pylint
|
||||||
entry: poetry run pylint
|
entry: poetry run pylint
|
||||||
files: "./openflexure_microscope/"
|
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
[MESSAGES CONTROL]
|
[MESSAGES CONTROL]
|
||||||
|
|
||||||
disable=C0330,C0326,W1202,W0511,C,R
|
disable=fixme,C,R
|
||||||
max-line-length = 88
|
max-line-length = 88
|
||||||
|
|
||||||
|
[LOGGING]
|
||||||
|
|
||||||
|
logging-format-style=new
|
||||||
|
|
@ -38,7 +38,7 @@ sys.modules.update((mod_name, Mock()) for mod_name in mock_imports)
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
project = "OpenFlexure Microscope Software"
|
project = "OpenFlexure Microscope Software"
|
||||||
copyright = "2018, Bath Open Instrumentation Group"
|
copyright = "2018, Bath Open Instrumentation Group" # pylint: disable=redefined-builtin
|
||||||
author = "Bath Open Instrumentation Group"
|
author = "Bath Open Instrumentation Group"
|
||||||
|
|
||||||
# The short X.Y version
|
# The short X.Y version
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from labthings import Schema, fields, find_component
|
from labthings import Schema, fields, find_component
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
from labthings.views import PropertyView, View
|
from labthings.views import PropertyView
|
||||||
|
|
||||||
## Extension methods
|
## Extension methods
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import io # Used in our capture action
|
||||||
from flask import send_file # Used to send images from our server
|
from flask import send_file # Used to send images from our server
|
||||||
from labthings import Schema, fields, find_component
|
from labthings import Schema, fields, find_component
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
from labthings.views import ActionView, PropertyView, View
|
from labthings.views import ActionView, PropertyView
|
||||||
|
|
||||||
## Extension methods
|
## Extension methods
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,8 @@
|
||||||
import io # Used in our capture action
|
|
||||||
import time # Used in our timelapse function
|
import time # Used in our timelapse function
|
||||||
|
|
||||||
from flask import send_file # Used to send images from our server
|
from labthings import current_action, fields, find_component, update_action_progress
|
||||||
from labthings import (
|
|
||||||
Schema,
|
|
||||||
current_action,
|
|
||||||
fields,
|
|
||||||
find_component,
|
|
||||||
update_action_progress,
|
|
||||||
)
|
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
from labthings.views import ActionView, PropertyView, View
|
from labthings.views import ActionView
|
||||||
|
|
||||||
# Used in our timelapse function
|
# Used in our timelapse function
|
||||||
from openflexure_microscope.captures.capture_manager import generate_basename
|
from openflexure_microscope.captures.capture_manager import generate_basename
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,8 @@
|
||||||
import io # Used in our capture action
|
|
||||||
import time # Used in our timelapse function
|
import time # Used in our timelapse function
|
||||||
|
|
||||||
from flask import send_file # Used to send images from our server
|
from labthings import current_action, fields, find_component, update_action_progress
|
||||||
from labthings import (
|
|
||||||
Schema,
|
|
||||||
current_action,
|
|
||||||
fields,
|
|
||||||
find_component,
|
|
||||||
update_action_progress,
|
|
||||||
)
|
|
||||||
from labthings.extensions import BaseExtension
|
from labthings.extensions import BaseExtension
|
||||||
from labthings.views import ActionView, PropertyView, View
|
from labthings.views import ActionView
|
||||||
|
|
||||||
# Used to convert our GUI dictionary into a complete eV extension GUI
|
# Used to convert our GUI dictionary into a complete eV extension GUI
|
||||||
from openflexure_microscope.api.utilities.gui import build_gui
|
from openflexure_microscope.api.utilities.gui import build_gui
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ root_log.addHandler(fh)
|
||||||
access_log.addHandler(afh)
|
access_log.addHandler(afh)
|
||||||
|
|
||||||
# Log server paths being used
|
# Log server paths being used
|
||||||
logging.info("Running with data path %s", OPENFLEXURE_VAR_PATH)
|
logging.info("Running with data path {}", OPENFLEXURE_VAR_PATH)
|
||||||
|
|
||||||
logging.info("Creating app")
|
logging.info("Creating app")
|
||||||
# Create flask app
|
# Create flask app
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ def handle_extension_error(extension_name):
|
||||||
yield
|
yield
|
||||||
except Exception: # pylint: disable=W0703
|
except Exception: # pylint: disable=W0703
|
||||||
logging.error(
|
logging.error(
|
||||||
"Exception loading builtin extension %s: \n%s",
|
"Exception loading builtin extension {}: \n{}",
|
||||||
extension_name,
|
extension_name,
|
||||||
traceback.format_exc(),
|
traceback.format_exc(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class JPEGSharpnessMonitor:
|
||||||
raise e
|
raise e
|
||||||
if stop < 1:
|
if stop < 1:
|
||||||
stop = len(jpeg_times)
|
stop = len(jpeg_times)
|
||||||
logging.debug("changing stop to {}".format(stop))
|
logging.debug("changing stop to {}", (stop))
|
||||||
jpeg_times = jpeg_times[start:stop]
|
jpeg_times = jpeg_times[start:stop]
|
||||||
jpeg_zs = np.interp(jpeg_times, stage_times, stage_zs)
|
jpeg_zs = np.interp(jpeg_times, stage_times, stage_zs)
|
||||||
return jpeg_times, jpeg_zs, jpeg_sizes[start:stop]
|
return jpeg_times, jpeg_zs, jpeg_sizes[start:stop]
|
||||||
|
|
@ -292,9 +292,8 @@ def fast_up_down_up_autofocus(
|
||||||
logging.debug("Correction move")
|
logging.debug("Correction move")
|
||||||
correction_move = best_z + target_z - jz[inow]
|
correction_move = best_z + target_z - jz[inow]
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Fast autofocus scan: correcting backlash by moving {} steps".format(
|
"Fast autofocus scan: correcting backlash by moving {} steps",
|
||||||
correction_move
|
(correction_move),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
m.focus_rel(correction_move)
|
m.focus_rel(correction_move)
|
||||||
return m.data_dict()
|
return m.data_dict()
|
||||||
|
|
|
||||||
|
|
@ -95,10 +95,10 @@ class AutostorageExtension(BaseExtension):
|
||||||
|
|
||||||
def on_microscope(self, microscope_obj):
|
def on_microscope(self, microscope_obj):
|
||||||
"""Function to automatically call when the parent LabThing has a microscope attached."""
|
"""Function to automatically call when the parent LabThing has a microscope attached."""
|
||||||
logging.debug("Autostorage extension found microscope %s", microscope_obj)
|
logging.debug("Autostorage extension found microscope {}", microscope_obj)
|
||||||
if hasattr(microscope_obj, "captures"):
|
if hasattr(microscope_obj, "captures"):
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Autostorage extension bound to CaptureManager %s", self.capture_manager
|
"Autostorage extension bound to CaptureManager {}", self.capture_manager
|
||||||
)
|
)
|
||||||
|
|
||||||
# Store a reference to the CaptureManager
|
# Store a reference to the CaptureManager
|
||||||
|
|
@ -117,7 +117,7 @@ class AutostorageExtension(BaseExtension):
|
||||||
# If preferred path does not exist, or cannot be written to
|
# If preferred path does not exist, or cannot be written to
|
||||||
if not (os.path.isdir(location) and check_rw(location)):
|
if not (os.path.isdir(location) and check_rw(location)):
|
||||||
logging.error(
|
logging.error(
|
||||||
"Preferred capture path %s is missing or cannot be written to. Restoring defaults.",
|
"Preferred capture path {} is missing or cannot be written to. Restoring defaults.",
|
||||||
location,
|
location,
|
||||||
)
|
)
|
||||||
# Reset the storage location to default
|
# Reset the storage location to default
|
||||||
|
|
|
||||||
|
|
@ -63,17 +63,15 @@ def auto_expose_and_freeze_settings(camera):
|
||||||
|
|
||||||
logging.info("Freezing the camera settings...")
|
logging.info("Freezing the camera settings...")
|
||||||
camera.shutter_speed = camera.exposure_speed
|
camera.shutter_speed = camera.exposure_speed
|
||||||
logging.info("Shutter speed = {}".format(camera.shutter_speed))
|
logging.info("Shutter speed = {}", (camera.shutter_speed))
|
||||||
camera.exposure_mode = "off"
|
camera.exposure_mode = "off"
|
||||||
logging.info("Auto exposure disabled")
|
logging.info("Auto exposure disabled")
|
||||||
g = camera.awb_gains
|
g = camera.awb_gains
|
||||||
camera.awb_mode = "off"
|
camera.awb_mode = "off"
|
||||||
camera.awb_gains = g
|
camera.awb_gains = g
|
||||||
logging.info("Auto white balance disabled, gains are {}".format(g))
|
logging.info("Auto white balance disabled, gains are {}", (g))
|
||||||
logging.info(
|
logging.info(
|
||||||
"Analogue gain: {}, Digital gain: {}".format(
|
"Analogue gain: {}, Digital gain: {}", camera.analog_gain, camera.digital_gain
|
||||||
camera.analog_gain, camera.digital_gain
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
adjust_exposure_to_setpoint(camera, 215)
|
adjust_exposure_to_setpoint(camera, 215)
|
||||||
|
|
||||||
|
|
@ -100,7 +98,7 @@ def lst_from_channels(channels):
|
||||||
# lst_resolution = list(np.ceil(full_resolution / 64.0).astype(int))
|
# lst_resolution = list(np.ceil(full_resolution / 64.0).astype(int))
|
||||||
lst_resolution = [(r // 64) + 1 for r in full_resolution]
|
lst_resolution = [(r // 64) + 1 for r in full_resolution]
|
||||||
# NB the size of the LST is 1/64th of the image, but rounded UP.
|
# NB the size of the LST is 1/64th of the image, but rounded UP.
|
||||||
logging.info("Generating a lens shading table at {}x{}".format(*lst_resolution))
|
logging.info("Generating a lens shading table at {}x{}", *lst_resolution)
|
||||||
lens_shading = np.zeros([channels.shape[0]] + lst_resolution, dtype=np.float)
|
lens_shading = np.zeros([channels.shape[0]] + lst_resolution, dtype=np.float)
|
||||||
for i in range(lens_shading.shape[0]):
|
for i in range(lens_shading.shape[0]):
|
||||||
image_channel = channels[i, :, :]
|
image_channel = channels[i, :, :]
|
||||||
|
|
@ -118,9 +116,12 @@ def lst_from_channels(channels):
|
||||||
image_channel, [(0, lw * 32 - iw), (0, lh * 32 - ih)], mode="edge"
|
image_channel, [(0, lw * 32 - iw), (0, lh * 32 - ih)], mode="edge"
|
||||||
) # Pad image to the right and bottom
|
) # Pad image to the right and bottom
|
||||||
logging.info(
|
logging.info(
|
||||||
"Channel shape: {}x{}, shading table shape: {}x{}, after padding {}".format(
|
"Channel shape: {}x{}, shading table shape: {}x{}, after padding {}",
|
||||||
iw, ih, lw * 32, lh * 32, padded_image_channel.shape
|
iw,
|
||||||
)
|
ih,
|
||||||
|
lw * 32,
|
||||||
|
lh * 32,
|
||||||
|
padded_image_channel.shape,
|
||||||
)
|
)
|
||||||
# Next, fill the shading table (except edge pixels). Please excuse the
|
# Next, fill the shading table (except edge pixels). Please excuse the
|
||||||
# for loop - I know it's not fast but this code needn't be!
|
# for loop - I know it's not fast but this code needn't be!
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ class ScanExtension(BaseExtension):
|
||||||
|
|
||||||
for x_y in line:
|
for x_y in line:
|
||||||
# Move to new grid position without changing z
|
# Move to new grid position without changing z
|
||||||
logging.debug("Moving to step {}".format([x_y[0], x_y[1], next_z]))
|
logging.debug("Moving to step {}", ([x_y[0], x_y[1], next_z]))
|
||||||
microscope.stage.move_abs([x_y[0], x_y[1], next_z])
|
microscope.stage.move_abs([x_y[0], x_y[1], next_z])
|
||||||
# Refocus
|
# Refocus
|
||||||
if autofocus_enabled:
|
if autofocus_enabled:
|
||||||
|
|
@ -270,11 +270,11 @@ class ScanExtension(BaseExtension):
|
||||||
# Make sure we use our current best estimate of focus (i.e. the current position) next point
|
# Make sure we use our current best estimate of focus (i.e. the current position) next point
|
||||||
next_z = microscope.stage.position[2]
|
next_z = microscope.stage.position[2]
|
||||||
|
|
||||||
logging.debug("Returning to {}".format(initial_position))
|
logging.debug("Returning to {}", (initial_position))
|
||||||
microscope.stage.move_abs(initial_position)
|
microscope.stage.move_abs(initial_position)
|
||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
logging.info("Scan took %s seconds", end - start)
|
logging.info("Scan took {} seconds", end - start)
|
||||||
|
|
||||||
def stack(
|
def stack(
|
||||||
self,
|
self,
|
||||||
|
|
@ -298,17 +298,17 @@ class ScanExtension(BaseExtension):
|
||||||
|
|
||||||
# Store initial position
|
# Store initial position
|
||||||
initial_position = microscope.stage.position
|
initial_position = microscope.stage.position
|
||||||
logging.debug("Starting z-stack from position %s", microscope.stage.position)
|
logging.debug("Starting z-stack from position {}", microscope.stage.position)
|
||||||
|
|
||||||
with microscope.lock:
|
with microscope.lock:
|
||||||
# Move to center scan
|
# Move to center scan
|
||||||
logging.debug("Moving to z-stack starting position")
|
logging.debug("Moving to z-stack starting position")
|
||||||
microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)])
|
microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)])
|
||||||
logging.debug("Starting scan from position %s", microscope.stage.position)
|
logging.debug("Starting scan from position {}", microscope.stage.position)
|
||||||
|
|
||||||
for i in range(steps):
|
for i in range(steps):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
logging.debug("Capturing from position %s", microscope.stage.position)
|
logging.debug("Capturing from position {}", microscope.stage.position)
|
||||||
self.capture(
|
self.capture(
|
||||||
microscope,
|
microscope,
|
||||||
basename,
|
basename,
|
||||||
|
|
@ -328,10 +328,10 @@ class ScanExtension(BaseExtension):
|
||||||
return
|
return
|
||||||
|
|
||||||
if i != steps - 1:
|
if i != steps - 1:
|
||||||
logging.debug("Moving z by {}".format(step_size))
|
logging.debug("Moving z by {}", (step_size))
|
||||||
microscope.stage.move_rel([0, 0, step_size])
|
microscope.stage.move_rel([0, 0, step_size])
|
||||||
if return_to_start:
|
if return_to_start:
|
||||||
logging.debug("Returning to {}".format(initial_position))
|
logging.debug("Returning to {}", (initial_position))
|
||||||
microscope.stage.move_abs(initial_position)
|
microscope.stage.move_abs(initial_position)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ class ZipGetterAPIView(View):
|
||||||
if not session_id in default_zip_manager.session_zips:
|
if not session_id in default_zip_manager.session_zips:
|
||||||
return abort(404) # 404 Not Found
|
return abort(404) # 404 Not Found
|
||||||
|
|
||||||
logging.info("Session ID: %s", session_id)
|
logging.info("Session ID: {}", session_id)
|
||||||
|
|
||||||
return send_file(
|
return send_file(
|
||||||
default_zip_manager.zip_fp_from_id(session_id).name,
|
default_zip_manager.zip_fp_from_id(session_id).name,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class SleepFor(ActionView):
|
||||||
|
|
||||||
def post(self, args):
|
def post(self, args):
|
||||||
sleep_time = args.get("time")
|
sleep_time = args.get("time")
|
||||||
logging.info("Going to sleep for %s...", sleep_time)
|
logging.info("Going to sleep for {}...", sleep_time)
|
||||||
start = time.time()
|
start = time.time()
|
||||||
time.sleep(sleep_time)
|
time.sleep(sleep_time)
|
||||||
end = time.time()
|
end = time.time()
|
||||||
|
|
|
||||||
|
|
@ -73,12 +73,10 @@ def init_default_extensions(extension_dir):
|
||||||
default_ext_path = os.path.join(extension_dir, "defaults.py")
|
default_ext_path = os.path.join(extension_dir, "defaults.py")
|
||||||
|
|
||||||
if not os.path.isfile(default_ext_path): # If user extensions file doesn't exist
|
if not os.path.isfile(default_ext_path): # If user extensions file doesn't exist
|
||||||
logging.warning(
|
logging.warning("No extension file found at {}. Creating...", (extension_dir))
|
||||||
"No extension file found at {}. Creating...".format(extension_dir)
|
|
||||||
)
|
|
||||||
create_file(default_ext_path)
|
create_file(default_ext_path)
|
||||||
|
|
||||||
logging.info("Populating {}...".format(default_ext_path))
|
logging.info("Populating {}...", (default_ext_path))
|
||||||
with open(default_ext_path, "w") as outfile:
|
with open(default_ext_path, "w") as outfile:
|
||||||
outfile.write(_DEFAULT_EXTENSION_INIT)
|
outfile.write(_DEFAULT_EXTENSION_INIT)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ def build_gui_from_dict(gui_description, extension_object):
|
||||||
if "route" in form and form["route"] in ext_rules.keys():
|
if "route" in form and form["route"] in ext_rules.keys():
|
||||||
form["route"] = ext_rules[form["route"]]["urls"][0]
|
form["route"] = ext_rules[form["route"]]["urls"][0]
|
||||||
else:
|
else:
|
||||||
logging.warning("No valid expandable route found for %s", form["route"])
|
logging.warning("No valid expandable route found for {}", form["route"])
|
||||||
|
|
||||||
# Inject extension information
|
# Inject extension information
|
||||||
api_gui["id"] = extension_object.name
|
api_gui["id"] = extension_object.name
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,11 @@ class MoveStageAPI(ActionView):
|
||||||
# Handle absolute positioning (calculate a relative move from current position and target)
|
# Handle absolute positioning (calculate a relative move from current position and target)
|
||||||
if (args.get("absolute")) and (microscope.stage): # Only if stage exists
|
if (args.get("absolute")) and (microscope.stage): # Only if stage exists
|
||||||
target_position = axes_to_array(args, ["x", "y", "z"])
|
target_position = axes_to_array(args, ["x", "y", "z"])
|
||||||
logging.debug("TARGET: {}".format(target_position))
|
logging.debug("TARGET: {}", (target_position))
|
||||||
position = [
|
position = [
|
||||||
target_position[i] - microscope.stage.position[i] for i in range(3)
|
target_position[i] - microscope.stage.position[i] for i in range(3)
|
||||||
]
|
]
|
||||||
logging.debug("DELTA: {}".format(position))
|
logging.debug("DELTA: {}", (position))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Get coordinates from payload
|
# Get coordinates from payload
|
||||||
|
|
|
||||||
|
|
@ -113,10 +113,10 @@ class BaseCamera(metaclass=ABCMeta):
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Close the BaseCamera and all attached StreamObjects."""
|
"""Close the BaseCamera and all attached StreamObjects."""
|
||||||
logging.info("Closing {}".format(self))
|
logging.info("Closing {}", (self))
|
||||||
# Stop worker thread
|
# Stop worker thread
|
||||||
self.stop_worker()
|
self.stop_worker()
|
||||||
logging.info("Closed {}".format(self))
|
logging.info("Closed {}", (self))
|
||||||
|
|
||||||
# START AND STOP WORKER THREAD
|
# START AND STOP WORKER THREAD
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,10 +156,10 @@ class PiCameraStreamer(BaseCamera):
|
||||||
for key in PiCameraStreamer.picamera_settings_keys:
|
for key in PiCameraStreamer.picamera_settings_keys:
|
||||||
try:
|
try:
|
||||||
value = getattr(self.camera, key)
|
value = getattr(self.camera, key)
|
||||||
logging.debug("Reading PiCamera().{}: {}".format(key, value))
|
logging.debug("Reading PiCamera().{}: {}", key, value)
|
||||||
conf_dict["picamera"][key] = value
|
conf_dict["picamera"][key] = value
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logging.debug("Unable to read PiCamera attribute {}".format(key))
|
logging.debug("Unable to read PiCamera attribute {}", (key))
|
||||||
|
|
||||||
# Include a serialised lens shading table
|
# Include a serialised lens shading table
|
||||||
if (
|
if (
|
||||||
|
|
@ -243,26 +243,22 @@ class PiCameraStreamer(BaseCamera):
|
||||||
# Set exposure mode
|
# Set exposure mode
|
||||||
if "exposure_mode" in settings_dict:
|
if "exposure_mode" in settings_dict:
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Applying exposure_mode: {}".format(settings_dict["exposure_mode"])
|
"Applying exposure_mode: {}", (settings_dict["exposure_mode"])
|
||||||
)
|
)
|
||||||
self.camera.exposure_mode = settings_dict["exposure_mode"]
|
self.camera.exposure_mode = settings_dict["exposure_mode"]
|
||||||
|
|
||||||
# Apply gains and let them settle
|
# Apply gains and let them settle
|
||||||
if "analog_gain" in settings_dict:
|
if "analog_gain" in settings_dict:
|
||||||
logging.debug(
|
logging.debug("Applying analog_gain: {}", (settings_dict["analog_gain"]))
|
||||||
"Applying analog_gain: {}".format(settings_dict["analog_gain"])
|
|
||||||
)
|
|
||||||
set_analog_gain(self.camera, float(settings_dict["analog_gain"]))
|
set_analog_gain(self.camera, float(settings_dict["analog_gain"]))
|
||||||
if "digital_gain" in settings_dict:
|
if "digital_gain" in settings_dict:
|
||||||
logging.debug(
|
logging.debug("Applying digital_gain: {}", (settings_dict["digital_gain"]))
|
||||||
"Applying digital_gain: {}".format(settings_dict["digital_gain"])
|
|
||||||
)
|
|
||||||
set_digital_gain(self.camera, float(settings_dict["digital_gain"]))
|
set_digital_gain(self.camera, float(settings_dict["digital_gain"]))
|
||||||
|
|
||||||
# Apply shutter speed
|
# Apply shutter speed
|
||||||
if "shutter_speed" in settings_dict:
|
if "shutter_speed" in settings_dict:
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Applying shutter_speed: {}".format(settings_dict["shutter_speed"])
|
"Applying shutter_speed: {}", (settings_dict["shutter_speed"])
|
||||||
)
|
)
|
||||||
self.camera.shutter_speed = int(settings_dict["shutter_speed"])
|
self.camera.shutter_speed = int(settings_dict["shutter_speed"])
|
||||||
|
|
||||||
|
|
@ -272,17 +268,17 @@ class PiCameraStreamer(BaseCamera):
|
||||||
if "awb_gains" in settings_dict:
|
if "awb_gains" in settings_dict:
|
||||||
logging.debug("Applying awb_mode: off")
|
logging.debug("Applying awb_mode: off")
|
||||||
self.camera.awb_mode = "off"
|
self.camera.awb_mode = "off"
|
||||||
logging.debug("Applying awb_gains: {}".format(settings_dict["awb_gains"]))
|
logging.debug("Applying awb_gains: {}", (settings_dict["awb_gains"]))
|
||||||
self.camera.awb_gains = settings_dict["awb_gains"]
|
self.camera.awb_gains = settings_dict["awb_gains"]
|
||||||
elif "awb_mode" in settings_dict:
|
elif "awb_mode" in settings_dict:
|
||||||
logging.debug("Applying awb_mode: {}".format(settings_dict["awb_mode"]))
|
logging.debug("Applying awb_mode: {}", (settings_dict["awb_mode"]))
|
||||||
self.camera.awb_mode = settings_dict["awb_mode"]
|
self.camera.awb_mode = settings_dict["awb_mode"]
|
||||||
|
|
||||||
# Handle some properties that can be quickly applied
|
# Handle some properties that can be quickly applied
|
||||||
batched_keys = ["framerate", "saturation"]
|
batched_keys = ["framerate", "saturation"]
|
||||||
for key in batched_keys:
|
for key in batched_keys:
|
||||||
if (key in settings_dict) and hasattr(self.camera, key):
|
if (key in settings_dict) and hasattr(self.camera, key):
|
||||||
logging.debug("Applying {}: {}".format(key, settings_dict[key]))
|
logging.debug("Applying {}: {}", key, settings_dict[key])
|
||||||
setattr(self.camera, key, settings_dict[key])
|
setattr(self.camera, key, settings_dict[key])
|
||||||
|
|
||||||
# Final optional pause to settle
|
# Final optional pause to settle
|
||||||
|
|
@ -307,7 +303,7 @@ class PiCameraStreamer(BaseCamera):
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
if np.abs(centre[i] - 0.5) + size / 2 > 0.5:
|
if np.abs(centre[i] - 0.5) + size / 2 > 0.5:
|
||||||
centre[i] = 0.5 + (1.0 - size) / 2 * np.sign(centre[i] - 0.5)
|
centre[i] = 0.5 + (1.0 - size) / 2 * np.sign(centre[i] - 0.5)
|
||||||
logging.info("setting zoom, centre {}, size {}".format(centre, size))
|
logging.info("setting zoom, centre {}, size {}", centre, size)
|
||||||
new_fov = (centre[0] - size / 2, centre[1] - size / 2, size, size)
|
new_fov = (centre[0] - size / 2, centre[1] - size / 2, size, size)
|
||||||
self.camera.zoom = new_fov
|
self.camera.zoom = new_fov
|
||||||
|
|
||||||
|
|
@ -329,13 +325,12 @@ class PiCameraStreamer(BaseCamera):
|
||||||
self.preview_active = True
|
self.preview_active = True
|
||||||
except picamerax.exc.PiCameraMMALError as e:
|
except picamerax.exc.PiCameraMMALError as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
"Suppressed a MMALError in start_preview. Exception: {}".format(e)
|
"Suppressed a MMALError in start_preview. Exception: {}", (e)
|
||||||
)
|
)
|
||||||
except picamerax.exc.PiCameraValueError as e:
|
except picamerax.exc.PiCameraValueError as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
"Suppressed a ValueError exception in start_preview. Exception: {}".format(
|
"Suppressed a ValueError exception in start_preview. Exception: {}",
|
||||||
e
|
(e),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def stop_preview(self):
|
def stop_preview(self):
|
||||||
|
|
@ -364,7 +359,7 @@ class PiCameraStreamer(BaseCamera):
|
||||||
if not self.record_active:
|
if not self.record_active:
|
||||||
|
|
||||||
# Start the camera video recording on port 2
|
# Start the camera video recording on port 2
|
||||||
logging.info("Recording to {}".format(output))
|
logging.info("Recording to {}", (output))
|
||||||
|
|
||||||
self.camera.start_recording(
|
self.camera.start_recording(
|
||||||
output,
|
output,
|
||||||
|
|
@ -406,19 +401,19 @@ class PiCameraStreamer(BaseCamera):
|
||||||
"""
|
"""
|
||||||
for k in kwargs.keys():
|
for k in kwargs.keys():
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"Warning, kwarg %s is invalid for stop_stream_recording.", k
|
"Warning, kwarg {} is invalid for stop_stream_recording.", k
|
||||||
)
|
)
|
||||||
with self.lock:
|
with self.lock:
|
||||||
# Stop the camera video recording on port 1
|
# Stop the camera video recording on port 1
|
||||||
try:
|
try:
|
||||||
self.camera.stop_recording(splitter_port=splitter_port)
|
self.camera.stop_recording(splitter_port=splitter_port)
|
||||||
except picamerax.exc.PiCameraNotRecording:
|
except picamerax.exc.PiCameraNotRecording:
|
||||||
logging.info("Not recording on splitter_port {}".format(splitter_port))
|
logging.info("Not recording on splitter_port {}", (splitter_port))
|
||||||
else:
|
else:
|
||||||
logging.info(
|
logging.info(
|
||||||
"Stopped MJPEG stream on port {1}. Switching to {0}.".format(
|
"Stopped MJPEG stream on port {}. Switching to {}.",
|
||||||
self.image_resolution, splitter_port
|
splitter_port,
|
||||||
)
|
self.image_resolution,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Increase the resolution for taking an image
|
# Increase the resolution for taking an image
|
||||||
|
|
@ -436,7 +431,7 @@ class PiCameraStreamer(BaseCamera):
|
||||||
"""
|
"""
|
||||||
for k in kwargs.keys():
|
for k in kwargs.keys():
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"Warning, kwarg %s is invalid for stop_stream_recording.", k
|
"Warning, kwarg {} is invalid for stop_stream_recording.", k
|
||||||
)
|
)
|
||||||
with self.lock(timeout=None):
|
with self.lock(timeout=None):
|
||||||
# Reduce the resolution for video streaming
|
# Reduce the resolution for video streaming
|
||||||
|
|
@ -470,9 +465,9 @@ class PiCameraStreamer(BaseCamera):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"Started MJPEG stream at {} on port {}".format(
|
"Started MJPEG stream at {} on port {}",
|
||||||
self.stream_resolution, splitter_port
|
self.stream_resolution,
|
||||||
)
|
splitter_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
def capture(
|
def capture(
|
||||||
|
|
@ -501,7 +496,7 @@ class PiCameraStreamer(BaseCamera):
|
||||||
output_object (str/BytesIO): Target object.
|
output_object (str/BytesIO): Target object.
|
||||||
"""
|
"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
logging.info("Capturing to {}".format(output))
|
logging.info("Capturing to {}", (output))
|
||||||
|
|
||||||
# Set resolution and stop stream recording if necessary
|
# Set resolution and stop stream recording if necessary
|
||||||
if not use_video_port:
|
if not use_video_port:
|
||||||
|
|
@ -537,7 +532,7 @@ class PiCameraStreamer(BaseCamera):
|
||||||
logging.debug("Creating PiRGBArray")
|
logging.debug("Creating PiRGBArray")
|
||||||
with picamerax.array.PiRGBArray(self.camera) as output:
|
with picamerax.array.PiRGBArray(self.camera) as output:
|
||||||
|
|
||||||
logging.info("Capturing to {}".format(output))
|
logging.info("Capturing to {}", (output))
|
||||||
|
|
||||||
self.camera.capture(output, format="rgb", use_video_port=True)
|
self.camera.capture(output, format="rgb", use_video_port=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,26 +55,17 @@ if __name__ == "__main__":
|
||||||
# fix the shutter speed
|
# fix the shutter speed
|
||||||
cam.shutter_speed = cam.exposure_speed
|
cam.shutter_speed = cam.exposure_speed
|
||||||
|
|
||||||
logging.info(
|
logging.info("Current a/d gains: {}, {}", cam.analog_gain, cam.digital_gain)
|
||||||
"Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)
|
|
||||||
)
|
|
||||||
|
|
||||||
logging.info("Attempting to set analogue gain to 1")
|
logging.info("Attempting to set analogue gain to 1")
|
||||||
set_analog_gain(cam, 1)
|
set_analog_gain(cam, 1)
|
||||||
logging.info("Attempting to set digital gain to 1")
|
logging.info("Attempting to set digital gain to 1")
|
||||||
set_digital_gain(cam, 1)
|
set_digital_gain(cam, 1)
|
||||||
# The old code is left in here in case it is a useful example...
|
|
||||||
# ret = mmal.mmal_port_parameter_set_rational(cam._camera.control._port,
|
|
||||||
# MMAL_PARAMETER_DIGITAL_GAIN,
|
|
||||||
# to_rational(1))
|
|
||||||
# print("Return code: {}".format(ret))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
logging.info(
|
logging.info(
|
||||||
"Current a/d gains: {}, {}".format(
|
"Current a/d gains: {}, {}", cam.analog_gain, cam.digital_gain
|
||||||
cam.analog_gain, cam.digital_gain
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
||||||
|
|
@ -25,23 +25,23 @@ def make_file_list(directory, formats):
|
||||||
glob.glob("{}/**/*.{}".format(directory, fmt.lower()), recursive=True)
|
glob.glob("{}/**/*.{}".format(directory, fmt.lower()), recursive=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.info("{} capture files found on disk".format(len(files)))
|
logging.info("{} capture files found on disk", (len(files)))
|
||||||
|
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
|
||||||
def build_captures_from_exif(capture_path):
|
def build_captures_from_exif(capture_path):
|
||||||
logging.debug("Reloading captures from {}...".format(capture_path))
|
logging.debug("Reloading captures from {}...", (capture_path))
|
||||||
files = make_file_list(capture_path, EXIF_FORMATS)
|
files = make_file_list(capture_path, EXIF_FORMATS)
|
||||||
captures = OrderedDict()
|
captures = OrderedDict()
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
logging.debug("Reloading capture {}...".format(f))
|
logging.debug("Reloading capture {}...", (f))
|
||||||
capture = capture_from_path(f)
|
capture = capture_from_path(f)
|
||||||
if capture:
|
if capture:
|
||||||
captures[capture.id] = capture
|
captures[capture.id] = capture
|
||||||
|
|
||||||
logging.info("{} capture files successfully reloaded".format(len(captures)))
|
logging.info("{} capture files successfully reloaded", (len(captures)))
|
||||||
|
|
||||||
return captures
|
return captures
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ def capture_from_path(path):
|
||||||
capture.sync_basic_metadata()
|
capture.sync_basic_metadata()
|
||||||
return capture
|
return capture
|
||||||
except (InvalidImageDataError, json.decoder.JSONDecodeError):
|
except (InvalidImageDataError, json.decoder.JSONDecodeError):
|
||||||
logging.error("Invalid metadata at {}.".format(path))
|
logging.error("Invalid metadata at {}.", (path))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ class CaptureObject(object):
|
||||||
|
|
||||||
# Store a nice ID
|
# Store a nice ID
|
||||||
self.id = uuid.uuid4() #: str: Unique capture ID
|
self.id = uuid.uuid4() #: str: Unique capture ID
|
||||||
logging.debug("Created CaptureObject {}".format(self.id))
|
logging.debug("Created CaptureObject {}", (self.id))
|
||||||
|
|
||||||
self.time = datetime.datetime.now()
|
self.time = datetime.datetime.now()
|
||||||
|
|
||||||
|
|
@ -97,17 +97,17 @@ class CaptureObject(object):
|
||||||
self.tags = []
|
self.tags = []
|
||||||
|
|
||||||
def write(self, s):
|
def write(self, s):
|
||||||
logging.debug("Writing to %s", self)
|
logging.debug("Writing to {}", self)
|
||||||
self.stream.write(s)
|
self.stream.write(s)
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
logging.info("Writing image data to disk %s", self.file)
|
logging.info("Writing image data to disk {}", self.file)
|
||||||
with open(self.file, "wb") as outfile:
|
with open(self.file, "wb") as outfile:
|
||||||
outfile.write(self.stream.getbuffer())
|
outfile.write(self.stream.getbuffer())
|
||||||
self.stream.close()
|
self.stream.close()
|
||||||
logging.info("Writing metadata to disk %s", self.file)
|
logging.info("Writing metadata to disk {}", self.file)
|
||||||
self._init_metadata()
|
self._init_metadata()
|
||||||
logging.info("Finished writing to disk %s", self.file)
|
logging.info("Finished writing to disk {}", self.file)
|
||||||
|
|
||||||
def open(self, mode):
|
def open(self, mode):
|
||||||
return open(self.file, mode)
|
return open(self.file, mode)
|
||||||
|
|
@ -175,7 +175,7 @@ class CaptureObject(object):
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_full_metadata(self):
|
def read_full_metadata(self):
|
||||||
logging.info("Reading full capture metadata from %s...", self.file)
|
logging.info("Reading full capture metadata from {}...", self.file)
|
||||||
exif_dict = self._read_exif()
|
exif_dict = self._read_exif()
|
||||||
return self._decode_usercomment(exif_dict)
|
return self._decode_usercomment(exif_dict)
|
||||||
|
|
||||||
|
|
@ -271,7 +271,7 @@ class CaptureObject(object):
|
||||||
|
|
||||||
# Write new data to file EXIF, if supported
|
# Write new data to file EXIF, if supported
|
||||||
if self.format.upper() in EXIF_FORMATS and self.exists:
|
if self.format.upper() in EXIF_FORMATS and self.exists:
|
||||||
logging.info("Writing Exif data to %s", self.file)
|
logging.info("Writing Exif data to {}", self.file)
|
||||||
|
|
||||||
# Extract current Exif data
|
# Extract current Exif data
|
||||||
exif_dict = self._read_exif()
|
exif_dict = self._read_exif()
|
||||||
|
|
@ -286,7 +286,7 @@ class CaptureObject(object):
|
||||||
|
|
||||||
# Serialize metadata
|
# Serialize metadata
|
||||||
metadata_string = json.dumps(metadata_dict, cls=JSONEncoder)
|
metadata_string = json.dumps(metadata_dict, cls=JSONEncoder)
|
||||||
logging.debug("Saving metadata string to file: %s", metadata_string)
|
logging.debug("Saving metadata string to file: {}", metadata_string)
|
||||||
|
|
||||||
# Insert metadata into exif_dict
|
# Insert metadata into exif_dict
|
||||||
exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_string.encode()
|
exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_string.encode()
|
||||||
|
|
@ -296,7 +296,7 @@ class CaptureObject(object):
|
||||||
|
|
||||||
# Insert exif into file
|
# Insert exif into file
|
||||||
piexif.insert(exif_bytes, self.file)
|
piexif.insert(exif_bytes, self.file)
|
||||||
logging.info("Finished writing Exif data to %s", self.file)
|
logging.info("Finished writing Exif data to {}", self.file)
|
||||||
|
|
||||||
# PROPERTIES
|
# PROPERTIES
|
||||||
|
|
||||||
|
|
@ -316,7 +316,7 @@ class CaptureObject(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.exists: # If data file exists
|
if self.exists: # If data file exists
|
||||||
logging.info("Opening from file {}".format(self.file))
|
logging.info("Opening from file {}", (self.file))
|
||||||
with open(self.file, "rb") as f:
|
with open(self.file, "rb") as f:
|
||||||
d = io.BytesIO(f.read()) # Load bytes from file
|
d = io.BytesIO(f.read()) # Load bytes from file
|
||||||
d.seek(0) # Rewind loaded bytestream
|
d.seek(0) # Rewind loaded bytestream
|
||||||
|
|
@ -364,7 +364,7 @@ class CaptureObject(object):
|
||||||
"""If the StreamObject has been saved, delete the file."""
|
"""If the StreamObject has been saved, delete the file."""
|
||||||
|
|
||||||
if os.path.isfile(self.file):
|
if os.path.isfile(self.file):
|
||||||
logging.info("Deleting file {}".format(self.file))
|
logging.info("Deleting file {}", (self.file))
|
||||||
os.remove(self.file)
|
os.remove(self.file)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ class CaptureManager:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
logging.info("Closing {}".format(self))
|
logging.info("Closing {}", (self))
|
||||||
# Close all StreamObjects
|
# Close all StreamObjects
|
||||||
for capture_list in [self.images.values(), self.videos.values()]:
|
for capture_list in [self.images.values(), self.videos.values()]:
|
||||||
for stream_object in capture_list:
|
for stream_object in capture_list:
|
||||||
|
|
@ -75,9 +75,9 @@ class CaptureManager:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if os.path.isdir(self.paths["temp"]):
|
if os.path.isdir(self.paths["temp"]):
|
||||||
logging.info("Clearing {}...".format(self.paths["temp"]))
|
logging.info("Clearing {}...", (self.paths["temp"]))
|
||||||
shutil.rmtree(self.paths["temp"])
|
shutil.rmtree(self.paths["temp"])
|
||||||
logging.debug("Cleared {}.".format(self.paths["temp"]))
|
logging.debug("Cleared {}.", (self.paths["temp"]))
|
||||||
|
|
||||||
def rebuild_captures(self):
|
def rebuild_captures(self):
|
||||||
self.images = build_captures_from_exif(self.paths["default"])
|
self.images = build_captures_from_exif(self.paths["default"])
|
||||||
|
|
@ -158,7 +158,7 @@ class CaptureManager:
|
||||||
|
|
||||||
# Update capture list
|
# Update capture list
|
||||||
capture_key = str(output.id)
|
capture_key = str(output.id)
|
||||||
logging.debug("Adding image %s with key %s", output, capture_key)
|
logging.debug("Adding image {} with key {}", output, capture_key)
|
||||||
|
|
||||||
self.images[capture_key] = output
|
self.images[capture_key] = output
|
||||||
|
|
||||||
|
|
@ -205,7 +205,7 @@ class CaptureManager:
|
||||||
|
|
||||||
# Update capture list
|
# Update capture list
|
||||||
capture_key = str(output.id)
|
capture_key = str(output.id)
|
||||||
logging.debug("Adding video %s with key %s", output, capture_key)
|
logging.debug("Adding video {} with key {}", output, capture_key)
|
||||||
self.videos[capture_key] = output
|
self.videos[capture_key] = output
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ def load_json_file(config_path) -> dict:
|
||||||
"""
|
"""
|
||||||
config_path = os.path.expanduser(config_path)
|
config_path = os.path.expanduser(config_path)
|
||||||
|
|
||||||
logging.info("Loading {}...".format(config_path))
|
logging.info("Loading {}...", config_path)
|
||||||
|
|
||||||
with open(config_path) as config_file:
|
with open(config_path) as config_file:
|
||||||
try:
|
try:
|
||||||
|
|
@ -109,7 +109,7 @@ def save_json_file(config_path: str, config_dict: dict):
|
||||||
"""
|
"""
|
||||||
config_path = os.path.expanduser(config_path)
|
config_path = os.path.expanduser(config_path)
|
||||||
|
|
||||||
logging.info("Saving {}...".format(config_path))
|
logging.info("Saving {}...", config_path)
|
||||||
logging.debug(config_dict)
|
logging.debug(config_dict)
|
||||||
|
|
||||||
with open(config_path, "w") as outfile:
|
with open(config_path, "w") as outfile:
|
||||||
|
|
@ -142,14 +142,14 @@ def initialise_file(config_path, populate: str = "{}\n"):
|
||||||
"""
|
"""
|
||||||
config_path = os.path.expanduser(config_path)
|
config_path = os.path.expanduser(config_path)
|
||||||
|
|
||||||
logging.debug("Initialising {}".format(config_path))
|
logging.debug("Initialising {}", (config_path))
|
||||||
logging.debug("Exists: {}".format(os.path.exists(config_path)))
|
logging.debug("Exists: {}", (os.path.exists(config_path)))
|
||||||
|
|
||||||
if not os.path.exists(config_path): # If user config file doesn't exist
|
if not os.path.exists(config_path): # If user config file doesn't exist
|
||||||
logging.warning("No config file found at {}. Creating...".format(config_path))
|
logging.warning("No config file found at {}. Creating...", (config_path))
|
||||||
create_file(config_path)
|
create_file(config_path)
|
||||||
|
|
||||||
logging.info("Populating {}...".format(config_path))
|
logging.info("Populating {}...", (config_path))
|
||||||
with open(config_path, "w") as outfile:
|
with open(config_path, "w") as outfile:
|
||||||
outfile.write(populate)
|
outfile.write(populate)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ class Microscope:
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Shut down the microscope hardware."""
|
"""Shut down the microscope hardware."""
|
||||||
logging.info("Closing {}".format(self))
|
logging.info("Closing {}", (self))
|
||||||
if self.camera:
|
if self.camera:
|
||||||
try:
|
try:
|
||||||
self.camera.close()
|
self.camera.close()
|
||||||
|
|
@ -83,7 +83,7 @@ class Microscope:
|
||||||
except TimeoutError as e:
|
except TimeoutError as e:
|
||||||
logging.error(e)
|
logging.error(e)
|
||||||
self.captures.close()
|
self.captures.close()
|
||||||
logging.info("Closed {}".format(self))
|
logging.info("Closed {}", (self))
|
||||||
|
|
||||||
def setup(self, configuration):
|
def setup(self, configuration):
|
||||||
"""
|
"""
|
||||||
|
|
@ -196,7 +196,7 @@ class Microscope:
|
||||||
Applies a settings dictionary to the microscope. Missing parameters will be left untouched.
|
Applies a settings dictionary to the microscope. Missing parameters will be left untouched.
|
||||||
"""
|
"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
logging.debug("Microscope: Applying settings: {}".format(settings))
|
logging.debug("Microscope: Applying settings: {}", (settings))
|
||||||
|
|
||||||
# If attached to a camera
|
# If attached to a camera
|
||||||
if ("camera" in settings) and self.camera:
|
if ("camera" in settings) and self.camera:
|
||||||
|
|
@ -329,14 +329,14 @@ class Microscope:
|
||||||
|
|
||||||
# Load cached bits of metadata
|
# Load cached bits of metadata
|
||||||
if cache_key:
|
if cache_key:
|
||||||
logging.debug("Reading cached microscope metadata: %s", cache_key)
|
logging.debug("Reading cached microscope metadata: {}", cache_key)
|
||||||
metadata = self.metadata_cache.get(cache_key, None)
|
metadata = self.metadata_cache.get(cache_key, None)
|
||||||
if not metadata:
|
if not metadata:
|
||||||
logging.debug("Building and caching microscope metadata: %s", cache_key)
|
logging.debug("Building and caching microscope metadata: {}", cache_key)
|
||||||
metadata = self.force_get_metadata()
|
metadata = self.force_get_metadata()
|
||||||
self.metadata_cache[cache_key] = metadata
|
self.metadata_cache[cache_key] = metadata
|
||||||
else:
|
else:
|
||||||
logging.debug("Building microscope metadata: %s", cache_key)
|
logging.debug("Building microscope metadata: {}", cache_key)
|
||||||
metadata = self.force_get_metadata()
|
metadata = self.force_get_metadata()
|
||||||
|
|
||||||
# Keys that should never be cached
|
# Keys that should never be cached
|
||||||
|
|
@ -362,7 +362,7 @@ class Microscope:
|
||||||
metadata: dict = None,
|
metadata: dict = None,
|
||||||
cache_key: str = None,
|
cache_key: str = None,
|
||||||
):
|
):
|
||||||
logging.debug("Microscope capturing to %s", filename)
|
logging.debug("Microscope capturing to {}", filename)
|
||||||
if not annotations:
|
if not annotations:
|
||||||
annotations = {}
|
annotations = {}
|
||||||
if not metadata:
|
if not metadata:
|
||||||
|
|
@ -384,7 +384,7 @@ class Microscope:
|
||||||
extras = {}
|
extras = {}
|
||||||
if fmt == "jpeg":
|
if fmt == "jpeg":
|
||||||
extras["thumbnail"] = (*THUMBNAIL_SIZE, 85)
|
extras["thumbnail"] = (*THUMBNAIL_SIZE, 85)
|
||||||
logging.info("Starting microscope capture %s", output.file)
|
logging.info("Starting microscope capture {}", output.file)
|
||||||
self.camera.capture(
|
self.camera.capture(
|
||||||
output,
|
output,
|
||||||
use_video_port=use_video_port,
|
use_video_port=use_video_port,
|
||||||
|
|
@ -395,6 +395,6 @@ class Microscope:
|
||||||
)
|
)
|
||||||
|
|
||||||
output.put_and_save(tags, annotations, full_metadata)
|
output.put_and_save(tags, annotations, full_metadata)
|
||||||
logging.debug("Finished capture to %s", output.file)
|
logging.debug("Finished capture to {}", output.file)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ def main():
|
||||||
logging.info("Loading user settings...")
|
logging.info("Loading user settings...")
|
||||||
settings = user_settings.load()
|
settings = user_settings.load()
|
||||||
cap_path = str(settings.get("captures", {}).get("paths", {}).get("default"))
|
cap_path = str(settings.get("captures", {}).get("paths", {}).get("default"))
|
||||||
logging.info("Capture path found: %s", cap_path)
|
logging.info("Capture path found: {}", cap_path)
|
||||||
|
|
||||||
if not cap_path:
|
if not cap_path:
|
||||||
logging.error(
|
logging.error(
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ def launch_timeout_test_process(target, args=(), kwargs=None, timeout=10):
|
||||||
# If thread is still active
|
# If thread is still active
|
||||||
if p.is_alive():
|
if p.is_alive():
|
||||||
logging.error(
|
logging.error(
|
||||||
"Function %s reached timeout after %s seconds. Terminating.",
|
"Function {} reached timeout after {} seconds. Terminating.",
|
||||||
target,
|
target,
|
||||||
timeout,
|
timeout,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,13 @@ class MissingStage(BaseStage):
|
||||||
|
|
||||||
self._position = list(np.array(self._position) + np.array(initial_move))
|
self._position = list(np.array(self._position) + np.array(initial_move))
|
||||||
logging.debug(np.array(self._position) + np.array(initial_move))
|
logging.debug(np.array(self._position) + np.array(initial_move))
|
||||||
logging.debug("New position: %s", self._position)
|
logging.debug("New position: {}", self._position)
|
||||||
|
|
||||||
def move_abs(self, final, **kwargs):
|
def move_abs(self, final, **kwargs):
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
self._position = list(final)
|
self._position = list(final)
|
||||||
logging.debug("New position: %s", self._position)
|
logging.debug("New position: {}", self._position)
|
||||||
|
|
||||||
def zero_position(self):
|
def zero_position(self):
|
||||||
"""Set the current position to zero"""
|
"""Set the current position to zero"""
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ class SangaStage(BaseStage):
|
||||||
|
|
||||||
@backlash.setter
|
@backlash.setter
|
||||||
def backlash(self, blsh):
|
def backlash(self, blsh):
|
||||||
logging.debug("Setting backlash to {}".format(blsh))
|
logging.debug("Setting backlash to {}", (blsh))
|
||||||
if blsh is None:
|
if blsh is None:
|
||||||
self._backlash = None
|
self._backlash = None
|
||||||
elif isinstance(blsh, Iterable):
|
elif isinstance(blsh, Iterable):
|
||||||
|
|
@ -118,7 +118,7 @@ class SangaStage(BaseStage):
|
||||||
backlash: (default: True) whether to correct for backlash.
|
backlash: (default: True) whether to correct for backlash.
|
||||||
"""
|
"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
logging.debug("Moving sangaboard by %s", displacement)
|
logging.debug("Moving sangaboard by {}", displacement)
|
||||||
if not backlash or self.backlash is None:
|
if not backlash or self.backlash is None:
|
||||||
return self.board.move_rel(displacement, axis=axis)
|
return self.board.move_rel(displacement, axis=axis)
|
||||||
if axis is not None:
|
if axis is not None:
|
||||||
|
|
@ -158,7 +158,7 @@ class SangaStage(BaseStage):
|
||||||
"""Make an absolute move to a position
|
"""Make an absolute move to a position
|
||||||
"""
|
"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
logging.debug("Moving sangaboard to %s", final)
|
logging.debug("Moving sangaboard to {}", final)
|
||||||
self.board.move_abs(final, **kwargs)
|
self.board.move_abs(final, **kwargs)
|
||||||
# Settle outside of the stage lock so that another move request
|
# Settle outside of the stage lock so that another move request
|
||||||
# can just take over before settling
|
# can just take over before settling
|
||||||
|
|
@ -262,7 +262,7 @@ class SangaDeltaStage(SangaStage):
|
||||||
# Transform into delta coordinates
|
# Transform into delta coordinates
|
||||||
displacement = np.dot(self.Tdv, displacement)
|
displacement = np.dot(self.Tdv, displacement)
|
||||||
|
|
||||||
logging.debug("Delta displacement: {}".format(displacement))
|
logging.debug("Delta displacement: {}", (displacement))
|
||||||
|
|
||||||
# Do the move
|
# Do the move
|
||||||
SangaStage.move_rel(self, displacement, axis=None, backlash=backlash)
|
SangaStage.move_rel(self, displacement, axis=None, backlash=backlash)
|
||||||
|
|
@ -274,7 +274,7 @@ class SangaDeltaStage(SangaStage):
|
||||||
# Transform into delta coordinates
|
# Transform into delta coordinates
|
||||||
final = np.dot(self.Tdv, final)
|
final = np.dot(self.Tdv, final)
|
||||||
|
|
||||||
logging.debug("Delta final: {}".format(final))
|
logging.debug("Delta final: {}", (final))
|
||||||
|
|
||||||
# Do the move
|
# Do the move
|
||||||
SangaStage.move_abs(self, final, **kwargs)
|
SangaStage.move_abs(self, final, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class Timer(object):
|
||||||
|
|
||||||
def __exit__(self, type_, value, traceback):
|
def __exit__(self, type_, value, traceback):
|
||||||
self.end = time.time()
|
self.end = time.time()
|
||||||
logging.debug("%s time: %s", self.name, self.end - self.start)
|
logging.debug("{} time: {}", self.name, self.end - self.start)
|
||||||
|
|
||||||
|
|
||||||
def deserialise_array_b64(b64_string, dtype, shape):
|
def deserialise_array_b64(b64_string, dtype, shape):
|
||||||
|
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import requests
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
|
|
||||||
class APIconnection:
|
|
||||||
def __init__(self, host="localhost", port=5000, api_ver="v1"):
|
|
||||||
self.base = self.build_base(host=host, port=port, api_ver=api_ver)
|
|
||||||
|
|
||||||
def build_base(self, host, port, api_ver):
|
|
||||||
return "http://{}:{}/api/{}".format(host, port, api_ver)
|
|
||||||
|
|
||||||
def uri(self, suffix):
|
|
||||||
return self.base + suffix
|
|
||||||
|
|
||||||
def get(self, route, json=True, timeout=5):
|
|
||||||
r = requests.get(self.uri(route), timeout=timeout)
|
|
||||||
if json:
|
|
||||||
return r.json()
|
|
||||||
else:
|
|
||||||
return r
|
|
||||||
|
|
||||||
def post(self, route, json=None, timeout=5):
|
|
||||||
r = requests.post(self.uri(route), json=json, timeout=timeout)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def delete(self, route, timeout=5):
|
|
||||||
r = requests.delete(self.uri(route), timeout=timeout)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def set_overlay(self, message="", size=50):
|
|
||||||
json = {"text": message, "size": size}
|
|
||||||
return self.post("/camera/overlay", json=json)
|
|
||||||
|
|
||||||
def get_overlay(self):
|
|
||||||
return self.get("/camera/overlay")
|
|
||||||
|
|
||||||
def get_config(self):
|
|
||||||
return self.get("/config")
|
|
||||||
|
|
||||||
def set_config(self, config_dict):
|
|
||||||
return self.post("/config", json=config_dict)
|
|
||||||
|
|
||||||
def get_state(self):
|
|
||||||
return self.get("/state")
|
|
||||||
|
|
||||||
def start_preview(self):
|
|
||||||
return self.post("/camera/preview/start")
|
|
||||||
|
|
||||||
def stop_preview(self):
|
|
||||||
return self.post("/camera/preview/stop")
|
|
||||||
|
|
||||||
def move_by(self, x=0, y=0, z=0):
|
|
||||||
json = {"x": x, "y": y, "z": z}
|
|
||||||
return self.post("/stage/position", json=json)
|
|
||||||
|
|
||||||
def new_capture(self, use_video_port=True, keep_on_disk=False, resize=None):
|
|
||||||
json = {"keep_on_disk": keep_on_disk, "use_video_port": use_video_port}
|
|
||||||
|
|
||||||
if resize:
|
|
||||||
json["size"] = {"width": resize[0], "height": resize[1]}
|
|
||||||
|
|
||||||
return self.post("/camera/capture", json=json)
|
|
||||||
|
|
||||||
def get_capture(self, capture_id):
|
|
||||||
uri_route = "/camera/capture/{}/download".format(capture_id)
|
|
||||||
r = self.get(uri_route, json=False)
|
|
||||||
img = Image.open(BytesIO(r.content))
|
|
||||||
array = np.asarray(img, dtype=np.int32)
|
|
||||||
return array
|
|
||||||
|
|
||||||
def del_capture(self, capture_id):
|
|
||||||
uri_route = "/camera/capture/{}".format(capture_id)
|
|
||||||
return self.delete(uri_route)
|
|
||||||
|
|
||||||
def capture(
|
|
||||||
self,
|
|
||||||
use_video_port=True,
|
|
||||||
keep_on_disk=False,
|
|
||||||
delete_after_use=True,
|
|
||||||
resize=None,
|
|
||||||
):
|
|
||||||
p = self.new_capture(
|
|
||||||
use_video_port=use_video_port, keep_on_disk=keep_on_disk, resize=resize
|
|
||||||
)
|
|
||||||
capture_id = p["metadata"]["id"]
|
|
||||||
img_array = self.get_capture(capture_id)
|
|
||||||
|
|
||||||
if delete_after_use:
|
|
||||||
self.del_capture(capture_id)
|
|
||||||
|
|
||||||
return img_array
|
|
||||||
|
|
||||||
def set_zoom(self, zoom_value=1.0):
|
|
||||||
json = {"zoom_value": zoom_value}
|
|
||||||
return self.post("/camera/zoom", json=json)
|
|
||||||
|
|
||||||
def get_zoom(self):
|
|
||||||
return self.get("/camera/zoom")
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from api_client import APIconnection
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCapture(unittest.TestCase):
|
|
||||||
def test_capture_config(self):
|
|
||||||
connection = APIconnection(host="localhost", port=5000, api_ver="v1")
|
|
||||||
config = connection.get_config()
|
|
||||||
|
|
||||||
expected_keys = ["image_resolution", "stream_resolution", "numpy_resolution"]
|
|
||||||
|
|
||||||
for key in expected_keys:
|
|
||||||
self.assertTrue(key in config)
|
|
||||||
|
|
||||||
def test_capture_videoport(self):
|
|
||||||
connection = APIconnection(host="localhost", port=5000, api_ver="v1")
|
|
||||||
resolution = connection.get_config()["stream_resolution"]
|
|
||||||
|
|
||||||
for resize in [None, (640, 480)]:
|
|
||||||
|
|
||||||
if resize:
|
|
||||||
resolution = resize
|
|
||||||
|
|
||||||
capture_array = connection.capture(
|
|
||||||
use_video_port=True,
|
|
||||||
keep_on_disk=False,
|
|
||||||
delete_after_use=True,
|
|
||||||
resize=resize,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(capture_array.shape == (resolution[1], resolution[0], 3))
|
|
||||||
|
|
||||||
def test_capture_full(self):
|
|
||||||
connection = APIconnection(host="localhost", port=5000, api_ver="v1")
|
|
||||||
resolution = connection.get_config()["image_resolution"]
|
|
||||||
|
|
||||||
for resize in [None, (640, 480)]:
|
|
||||||
|
|
||||||
if resize:
|
|
||||||
resolution = resize
|
|
||||||
|
|
||||||
capture_array = connection.capture(
|
|
||||||
use_video_port=False,
|
|
||||||
keep_on_disk=False,
|
|
||||||
delete_after_use=True,
|
|
||||||
resize=resize,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(capture_array.shape == (resolution[1], resolution[0], 3))
|
|
||||||
|
|
||||||
|
|
||||||
class TestStage(unittest.TestCase):
|
|
||||||
def test_stage_config(self):
|
|
||||||
connection = APIconnection(host="localhost", port=5000, api_ver="v1")
|
|
||||||
config = connection.get_config()
|
|
||||||
|
|
||||||
expected_keys = ["backlash"]
|
|
||||||
|
|
||||||
for key in expected_keys:
|
|
||||||
self.assertTrue(key in config)
|
|
||||||
|
|
||||||
def test_stage_state(self):
|
|
||||||
connection = APIconnection(host="localhost", port=5000, api_ver="v1")
|
|
||||||
state = connection.get_state()
|
|
||||||
|
|
||||||
self.assertTrue("stage" in state)
|
|
||||||
|
|
||||||
expected_keys = ["position"]
|
|
||||||
|
|
||||||
for key in expected_keys:
|
|
||||||
self.assertTrue(key in state["stage"])
|
|
||||||
|
|
||||||
def test_stage_movement(self):
|
|
||||||
connection = APIconnection(host="localhost", port=5000, api_ver="v1")
|
|
||||||
|
|
||||||
move_distance = 500
|
|
||||||
for axis in range(3):
|
|
||||||
for direction in [1, -1]:
|
|
||||||
pos_i_dict = connection.get_state()["stage"]["position"]
|
|
||||||
pos_i = [pos_i_dict["x"], pos_i_dict["y"], pos_i_dict["z"]]
|
|
||||||
|
|
||||||
move = [0, 0, 0]
|
|
||||||
move[axis] = move_distance * direction
|
|
||||||
|
|
||||||
connection.move_by(*move)
|
|
||||||
|
|
||||||
pos_f_dict = connection.get_state()["stage"]["position"]
|
|
||||||
pos_f = [pos_f_dict["x"], pos_f_dict["y"], pos_f_dict["z"]]
|
|
||||||
|
|
||||||
diff = np.subtract(pos_f, pos_i)
|
|
||||||
logging.debug("{} > {}".format(pos_i, pos_f))
|
|
||||||
|
|
||||||
self.assertTrue(np.array_equal(diff, move))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
suites = [
|
|
||||||
unittest.TestLoader().loadTestsFromTestCase(TestCapture),
|
|
||||||
unittest.TestLoader().loadTestsFromTestCase(TestStage),
|
|
||||||
]
|
|
||||||
|
|
||||||
alltests = unittest.TestSuite(suites)
|
|
||||||
|
|
||||||
result = unittest.TextTestRunner(verbosity=2).run(alltests)
|
|
||||||
|
|
@ -1,266 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import io
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from openflexure_microscope.camera.pi import CaptureObject, PiCameraStreamer
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCaptureMethods(unittest.TestCase):
|
|
||||||
def test_still_capture(self):
|
|
||||||
"""Tests capturing still images to a BytesIO stream."""
|
|
||||||
global camera
|
|
||||||
|
|
||||||
for use_video_port in [True, False]:
|
|
||||||
for resize in [None, (640, 480)]:
|
|
||||||
|
|
||||||
# Wait for camera
|
|
||||||
camera.wait_for_camera()
|
|
||||||
|
|
||||||
# Capture to a context (auto-deletes files when done)
|
|
||||||
with camera.new_image() as output:
|
|
||||||
|
|
||||||
camera.capture(output, use_video_port=use_video_port, resize=resize)
|
|
||||||
|
|
||||||
# Ensure file deletion fails and returns False
|
|
||||||
self.assertFalse(output.delete_file())
|
|
||||||
# Ensure capture not stored to file
|
|
||||||
self.assertFalse(os.path.isfile(output.file))
|
|
||||||
|
|
||||||
# BEFORE DELETE: Ensure StreamObject 'stream' has
|
|
||||||
# a valid BytesIO object and byte string
|
|
||||||
self.assertTrue(isinstance(output.data, io.IOBase))
|
|
||||||
self.assertTrue(isinstance(output.binary, (bytes, bytearray)))
|
|
||||||
|
|
||||||
# Save capture to file
|
|
||||||
output.save_file()
|
|
||||||
# Check file got saved
|
|
||||||
self.assertTrue(os.path.isfile(output.file))
|
|
||||||
|
|
||||||
# Delete file
|
|
||||||
output.delete_file()
|
|
||||||
# Check file got deleted
|
|
||||||
self.assertFalse(os.path.isfile(output.file))
|
|
||||||
|
|
||||||
# AFTER DELETE: Ensure StreamObject 'stream' has
|
|
||||||
# a valid BytesIO object and byte string
|
|
||||||
self.assertTrue(isinstance(output.data, io.IOBase))
|
|
||||||
self.assertTrue(isinstance(output.binary, (bytes, bytearray)))
|
|
||||||
|
|
||||||
# Create a PIL image from stream
|
|
||||||
image = Image.open(output.data)
|
|
||||||
|
|
||||||
# Ensure a valid PIL image was created
|
|
||||||
self.assertTrue(isinstance(image, Image.Image))
|
|
||||||
|
|
||||||
# Calculate expected dimensions
|
|
||||||
if resize:
|
|
||||||
dims = resize
|
|
||||||
else:
|
|
||||||
if use_video_port:
|
|
||||||
dims = camera.stream_resolution
|
|
||||||
else:
|
|
||||||
dims = camera.image_resolution
|
|
||||||
|
|
||||||
# Ensure PIL image size matches expected size
|
|
||||||
print(image.size, dims)
|
|
||||||
self.assertTrue(image.size == dims)
|
|
||||||
|
|
||||||
def test_still_store(self):
|
|
||||||
"""Tests capturing still images to a file on disk."""
|
|
||||||
global camera
|
|
||||||
|
|
||||||
for use_video_port in [True, False]:
|
|
||||||
for resize in [None, (640, 480)]:
|
|
||||||
# Wait for camera
|
|
||||||
camera.wait_for_camera()
|
|
||||||
|
|
||||||
# Capture
|
|
||||||
output = camera.capture(
|
|
||||||
camera.new_image().file,
|
|
||||||
use_video_port=use_video_port,
|
|
||||||
resize=resize,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check file got saved
|
|
||||||
self.assertTrue(os.path.isfile(output.file))
|
|
||||||
statinfo = os.stat(output.file)
|
|
||||||
self.assertTrue(statinfo.st_size > 0)
|
|
||||||
|
|
||||||
# Ensure StreamObject 'stream' has
|
|
||||||
# a valid BytesIO object and byte string
|
|
||||||
self.assertTrue(isinstance(output.data, io.IOBase))
|
|
||||||
self.assertTrue(isinstance(output.binary, (bytes, bytearray)))
|
|
||||||
|
|
||||||
# Ensure file deletion completes and returns True
|
|
||||||
self.assertTrue(output.delete_file())
|
|
||||||
|
|
||||||
# Check file got deleted
|
|
||||||
self.assertFalse(os.path.isfile(output.file))
|
|
||||||
|
|
||||||
|
|
||||||
class TestUnencodedMethods(unittest.TestCase):
|
|
||||||
def test_yuv_array(self):
|
|
||||||
"""Tests capturing unencoded YUV data to a Numpy array."""
|
|
||||||
global camera
|
|
||||||
|
|
||||||
for use_video_port in [True, False]:
|
|
||||||
for resize in [None, (640, 480)]:
|
|
||||||
|
|
||||||
print("{}, {}, {}".format(id, resize, use_video_port))
|
|
||||||
|
|
||||||
# Wait for camera
|
|
||||||
camera.wait_for_camera()
|
|
||||||
|
|
||||||
# Capture RGB array
|
|
||||||
yuv = camera.yuv(use_video_port=use_video_port, resize=resize)
|
|
||||||
|
|
||||||
# Ensure capture output is a valid numpy array
|
|
||||||
self.assertTrue(isinstance(yuv, np.ndarray))
|
|
||||||
|
|
||||||
# Calculate expected dimensions
|
|
||||||
if resize:
|
|
||||||
dims = resize
|
|
||||||
else:
|
|
||||||
if use_video_port:
|
|
||||||
dims = camera.stream_resolution
|
|
||||||
else:
|
|
||||||
dims = camera.numpy_resolution
|
|
||||||
|
|
||||||
# Ensure array shape matches expected dimensions
|
|
||||||
self.assertTrue(yuv.shape == (dims[1], dims[0], 3))
|
|
||||||
|
|
||||||
def test_rgb_array(self):
|
|
||||||
"""Tests capturing unencoded YUV/RGB data to a Numpy array."""
|
|
||||||
global camera
|
|
||||||
|
|
||||||
for use_video_port in [True, False]:
|
|
||||||
for resize in [None, (640, 480)]:
|
|
||||||
|
|
||||||
print("{}, {}, {}".format(id, resize, use_video_port))
|
|
||||||
|
|
||||||
# Wait for camera
|
|
||||||
camera.wait_for_camera()
|
|
||||||
|
|
||||||
# Capture RGB array
|
|
||||||
rgb = camera.array(use_video_port=use_video_port, resize=resize)
|
|
||||||
|
|
||||||
# Ensure capture output is a valid numpy array
|
|
||||||
self.assertTrue(isinstance(rgb, np.ndarray))
|
|
||||||
|
|
||||||
# Calculate expected dimensions
|
|
||||||
if resize:
|
|
||||||
dims = resize
|
|
||||||
else:
|
|
||||||
if use_video_port:
|
|
||||||
dims = camera.stream_resolution
|
|
||||||
else:
|
|
||||||
dims = camera.numpy_resolution
|
|
||||||
|
|
||||||
# Ensure array shape matches expected dimensions
|
|
||||||
self.assertTrue(rgb.shape == (dims[1], dims[0], 3))
|
|
||||||
|
|
||||||
|
|
||||||
class TestRecordMethods(unittest.TestCase):
|
|
||||||
def test_video_record(self):
|
|
||||||
"""Tests recording videos to BytesIO stream, and to file on disk."""
|
|
||||||
global camera
|
|
||||||
|
|
||||||
# Wait for camera
|
|
||||||
camera.wait_for_camera()
|
|
||||||
|
|
||||||
with camera.new_video() as output:
|
|
||||||
|
|
||||||
# Start recording
|
|
||||||
camera.start_recording(output)
|
|
||||||
|
|
||||||
# Record for 2 seconds
|
|
||||||
time.sleep(2)
|
|
||||||
# Stop recording
|
|
||||||
camera.stop_recording()
|
|
||||||
|
|
||||||
# Check stream
|
|
||||||
self.assertTrue(isinstance(output.data, io.IOBase))
|
|
||||||
self.assertTrue(isinstance(output.binary, (bytes, bytearray)))
|
|
||||||
|
|
||||||
# Check file
|
|
||||||
statinfo = os.stat(output.file)
|
|
||||||
self.assertTrue(statinfo.st_size > 0)
|
|
||||||
|
|
||||||
# Log path
|
|
||||||
temp_path = output.file
|
|
||||||
|
|
||||||
# Check file got deleted on __exit__
|
|
||||||
self.assertFalse(os.path.isfile(temp_path))
|
|
||||||
|
|
||||||
time.sleep(0.25)
|
|
||||||
|
|
||||||
def test_video_store(self):
|
|
||||||
"""Tests recording videos to file on disk, without context manager."""
|
|
||||||
global camera
|
|
||||||
|
|
||||||
# Wait for camera
|
|
||||||
camera.wait_for_camera()
|
|
||||||
|
|
||||||
# Start recording
|
|
||||||
output = camera.start_recording(camera.new_video())
|
|
||||||
|
|
||||||
# Record for 2 seconds
|
|
||||||
time.sleep(2)
|
|
||||||
# Stop recording
|
|
||||||
camera.stop_recording()
|
|
||||||
|
|
||||||
# Check file
|
|
||||||
statinfo = os.stat(output.file)
|
|
||||||
self.assertTrue(statinfo.st_size > 0)
|
|
||||||
|
|
||||||
# Ensure file deletion completes and returns True
|
|
||||||
self.assertTrue(output.delete_file())
|
|
||||||
|
|
||||||
# Check file got deleted
|
|
||||||
self.assertFalse(os.path.isfile(output.file))
|
|
||||||
|
|
||||||
|
|
||||||
class TestThreadStarting(unittest.TestCase):
|
|
||||||
def test_restarting_stream(self):
|
|
||||||
"""Tests that a capture call restarts the camera worker thread."""
|
|
||||||
global camera
|
|
||||||
|
|
||||||
# Wait for camera
|
|
||||||
camera.wait_for_camera()
|
|
||||||
|
|
||||||
# Force-stop the camera worker thread (should return True)
|
|
||||||
self.assertTrue(camera.stop_worker())
|
|
||||||
|
|
||||||
# Check Pi camera has been disconnected
|
|
||||||
self.assertTrue(camera.camera)
|
|
||||||
|
|
||||||
# Restart worker thread
|
|
||||||
self.assertTrue(camera.start_worker())
|
|
||||||
|
|
||||||
self.assertTrue(camera.camera)
|
|
||||||
self.assertTrue(camera.thread)
|
|
||||||
self.assertIsInstance(camera.stream, io.IOBase)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
with PiCameraStreamer() as camera:
|
|
||||||
|
|
||||||
suites = [
|
|
||||||
unittest.TestLoader().loadTestsFromTestCase(TestCaptureMethods),
|
|
||||||
unittest.TestLoader().loadTestsFromTestCase(TestUnencodedMethods),
|
|
||||||
unittest.TestLoader().loadTestsFromTestCase(TestThreadStarting),
|
|
||||||
unittest.TestLoader().loadTestsFromTestCase(TestRecordMethods),
|
|
||||||
]
|
|
||||||
|
|
||||||
alltests = unittest.TestSuite(suites)
|
|
||||||
|
|
||||||
result = unittest.TextTestRunner(verbosity=2).run(alltests)
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import atexit
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from openflexure_stage import OpenFlexureStage
|
|
||||||
|
|
||||||
from openflexure_microscope import Microscope, config
|
|
||||||
from openflexure_microscope.camera.pi import PiCameraStreamer
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPluginMethods(unittest.TestCase):
|
|
||||||
def test_plugin_load(self):
|
|
||||||
plugin_arr = microscope.plugins.plugins
|
|
||||||
|
|
||||||
plugin_names = [plugin[0] for plugin in plugin_arr]
|
|
||||||
|
|
||||||
self.assertTrue("testing" in plugin_names)
|
|
||||||
|
|
||||||
def test_camera_access(self):
|
|
||||||
identify = microscope.plugins.testing.identify()
|
|
||||||
self.assertTrue(identify[0] is microscope.camera)
|
|
||||||
|
|
||||||
def test_stage_access(self):
|
|
||||||
identify = microscope.plugins.testing.identify()
|
|
||||||
self.assertTrue(identify[1] is microscope.stage)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
with Microscope() as microscope:
|
|
||||||
|
|
||||||
microscope.attach(PiCameraStreamer(), OpenFlexureStage())
|
|
||||||
|
|
||||||
microscope.plugins.attach("openflexure_microscope.plugins.testing:Plugin")
|
|
||||||
|
|
||||||
suites = [unittest.TestLoader().loadTestsFromTestCase(TestPluginMethods)]
|
|
||||||
|
|
||||||
alltests = unittest.TestSuite(suites)
|
|
||||||
|
|
||||||
result = unittest.TextTestRunner(verbosity=2).run(alltests)
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from openflexure_stage import OpenFlexureStage
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
class TestMicroscope(unittest.TestCase):
|
|
||||||
def test_movement(self):
|
|
||||||
move_distance = 500
|
|
||||||
for axis in range(3):
|
|
||||||
for direction in [1, -1]:
|
|
||||||
pos_i = stage.position
|
|
||||||
logging.debug(pos_i)
|
|
||||||
|
|
||||||
logging.info(
|
|
||||||
"Moving axis {} by {}".format(axis, move_distance * direction)
|
|
||||||
)
|
|
||||||
move = [0, 0, 0]
|
|
||||||
move[axis] = move_distance * direction
|
|
||||||
|
|
||||||
stage.move_rel(move)
|
|
||||||
|
|
||||||
pos_f = stage.position
|
|
||||||
diff = np.subtract(pos_f, pos_i)
|
|
||||||
logging.debug("{} > {}".format(pos_i, pos_f))
|
|
||||||
|
|
||||||
self.assertTrue(np.array_equal(diff, move))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
with OpenFlexureStage("/dev/ttyUSB0") as stage:
|
|
||||||
|
|
||||||
suites = [unittest.TestLoader().loadTestsFromTestCase(TestMicroscope)]
|
|
||||||
|
|
||||||
alltests = unittest.TestSuite(suites)
|
|
||||||
|
|
||||||
result = unittest.TextTestRunner(verbosity=2).run(alltests)
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue