Skip to content

Commit

Permalink
Merge pull request #587 from czbiohub-sf/develop
Browse files Browse the repository at this point in the history
v0.5.5
  • Loading branch information
NicholasBratvold authored Jan 17, 2025
2 parents d18fbab + 8f89f03 commit eee9546
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 22 deletions.
9 changes: 5 additions & 4 deletions ulc_mm_package/QtGUI/oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import os
import sys
import traceback
import socket
import enum
import logging
Expand Down Expand Up @@ -840,9 +841,8 @@ def shutoff(self):
self.shutoff_done = True

def emergency_shutoff(self):
self.logger.warning("Starting emergency oracle shut off.")

if not self.shutoff_done:
self.logger.warning("Starting emergency oracle shut off.")
try:
os.remove(LOCKFILE)
self.logger.info(f"Removed lockfile ({LOCKFILE}).")
Expand Down Expand Up @@ -873,8 +873,9 @@ def main():

app.connect_signal(oracle.scopeop.shutoff)

def shutoff_excepthook(type, value, traceback):
sys.__excepthook__(type, value, traceback)
def shutoff_excepthook(type, value, tb):
tb_string = "".join(traceback.format_exception(type, value, tb))
oracle.logger.warning(f"Oracle shutoff due to exception - {tb_string}")
try:
app.shutoff.emit()
# Pause before shutting off hardware to ensure there are no calls to camera post-shutoff
Expand Down
39 changes: 32 additions & 7 deletions ulc_mm_package/QtGUI/scope_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ def _end_experiment(self, *args):

runtime = self._get_experiment_runtime()
if runtime != 0:
self.logger.info(f"Net FPS is {self.frame_count/runtime}")
self.logger.info(f"Net FPS is {self.frame_count/runtime:.1f}")

self.finishing_experiment.emit(10)

Expand Down Expand Up @@ -575,14 +575,19 @@ def run_autobrightness(self, img, _timestamp):
self.logger.info("Slot executed after experiment ended.")
return

self.img_signal.disconnect(self.run_autobrightness)
try:
self.img_signal.disconnect(self.run_autobrightness)
except TypeError:
self.logger.info(
"run_autobrightness: img_signal already disconnected, no signal/slot changes were made."
)

try:
self.autobrightness_routine.send(img)
except StopIteration as e:
self.autobrightness_result = e.value
self.logger.info(
f"Autobrightness ✅. Mean pixel val = {self.autobrightness_result}."
f"Autobrightness ✅. Mean pixel val = {self.autobrightness_result:.1f}."
)
self.last_img = img
if self.state in {
Expand Down Expand Up @@ -638,7 +643,12 @@ def run_cellfinder(self, img, _timestamp):
self.logger.info("Slot executed after experiment ended")
return

self.img_signal.disconnect(self.run_cellfinder)
try:
self.img_signal.disconnect(self.run_cellfinder)
except TypeError:
self.logger.info(
"run_cellfinder: img_signal already disconnected, no signal/slot changes were made."
)

try:
self.cellfinder_routine.send(img)
Expand Down Expand Up @@ -667,7 +677,12 @@ def run_autofocus(self, img, _timestamp):
self.logger.info("Slot executed after experiment ended.")
return

self.img_signal.disconnect(self.run_autofocus)
try:
self.img_signal.disconnect(self.run_autofocus)
except TypeError:
self.logger.info(
"run_autofocus: img_signal already disconnected, no signal/slot changes were made."
)

if not self.autofocus_done:
if len(self.autofocus_batch) < AF_BATCH_SIZE:
Expand Down Expand Up @@ -742,7 +757,12 @@ def run_fastflow(self, img, timestamp):
self.logger.info("Slot executed after experiment ended.")
return

self.img_signal.disconnect(self.run_fastflow)
try:
self.img_signal.disconnect(self.run_fastflow)
except TypeError:
self.logger.info(
"run_fastflow: img_signal already disconnected, no signal/slot changes were made."
)

try:
img_ds_10x = downsample_image(img, DOWNSAMPLE_FACTOR)
Expand Down Expand Up @@ -806,7 +826,12 @@ def run_experiment(self, img, timestamp) -> None:
self.logger.info("Slot executed after experiment ended.")
return

self.img_signal.disconnect(self.run_experiment)
try:
self.img_signal.disconnect(self.run_experiment)
except TypeError:
self.logger.info(
"run_experiment: img_signal already disconnected, no signal/slot changes were made."
)

current_time = perf_counter()
self.img_metadata["looptime"] = current_time - self.last_time
Expand Down
2 changes: 1 addition & 1 deletion ulc_mm_package/hardware/hardware_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
MPRLS_RST = 10
MPRLS_PWR = 22

MIN_PRESSURE_DIFF = 400 # In units of hPa
MIN_PRESSURE_DIFF = 330 # In units of hPa
# ================ Fan constants ================ #
FAN_GPIO = 5
CAM_FAN_1 = 23
Expand Down
22 changes: 17 additions & 5 deletions ulc_mm_package/hardware/scope_routines.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@
BrightnessCriticallyLow,
checkLedWorking,
)
from ulc_mm_package.image_processing.flow_control import FlowController

from ulc_mm_package.image_processing.flow_control import (
FlowController,
CantReachTargetFlowrate,
)
from ulc_mm_package.image_processing.cell_finder import (
CellFinder,
NoCellsFound,
LowDensity,
)
from ulc_mm_package.hardware.pneumatic_module import PressureLeak, PressureSensorBusy
from ulc_mm_package.hardware.pneumatic_module import (
PressureLeak,
PressureSensorBusy,
SyringeEndOfTravel,
)
from ulc_mm_package.hardware.motorcontroller import Direction, MotorControllerError
from ulc_mm_package.hardware.hardware_constants import (
MIN_PRESSURE_DIFF,
Expand Down Expand Up @@ -510,9 +516,15 @@ def find_cells_routine(
mscope.pneumatic_module.getAmbientPressure()
- mscope.pneumatic_module.getPressure()[0]
)
while curr_pressure_gauge < processing_constants.MAX_VACUUM_PRESSURE:
while curr_pressure_gauge < MIN_PRESSURE_DIFF:
# Pass in a flow_error=1.0 to pull the syringe down
flow_controller.adjustSyringe(flow_error=1.0)
try:
flow_controller.adjustSyringe(flow_error=1.0)
except (CantReachTargetFlowrate, SyringeEndOfTravel):
self.logger.info(
f"Syringe reached end of travel but current pressure: ({curr_pressure_gauge:.2f} mBar) is less than max allowable ({processing_constants.MAX_VACUUM_PRESSURE:.2f} mBar).\nContinuing with cell finder anyway..."
)
break
curr_pressure_gauge = abs(
mscope.pneumatic_module.getAmbientPressure()
- mscope.pneumatic_module.getPressure()[0]
Expand Down
14 changes: 12 additions & 2 deletions ulc_mm_package/image_processing/focus_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def radial_average(data):


def gradientAverage(img: np.ndarray):
"""Returns the normalized gradient average (in x and y)"""
gx, gy = np.gradient(img) / np.max(img)
"""Returns the normalized (by mean) gradient average (in x and y)"""
gx, gy = np.gradient(img) / np.mean(img)
return np.average(np.sqrt(gx**2 + gy**2))


Expand Down Expand Up @@ -86,12 +86,22 @@ def get_diff(img: np.ndarray):
return gy, gx


def numba_mean(img: np.ndarray):
return np.mean(img)


@njit(cache=True)
def custom_gradient_average(img: np.ndarray):
# I know these are flipped from what the return statement in `get_diff`
# but my 'gy' corresponds to np.gradient's 'gx'
# and rewriting `get_diff` is a bit of a pain and ultimately inconsequential
# for how we're using it here
img_mean = np.mean(img)

# A guard against any potential division by zero errors
img_mean = 1 if img_mean == 0 else img_mean
gx, gy = get_diff(img)
gx = gx / img_mean
gy = gy / img_mean

return np.mean(np.sqrt(gx**2 + gy**2))
2 changes: 1 addition & 1 deletion ulc_mm_package/logger.config
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ keys=fileHandler, printHandler
keys=stringFormatter, printFormatter

[logger_root]
level=DEBUG
level=INFO
handlers=fileHandler, printHandler

[handler_fileHandler]
Expand Down
6 changes: 4 additions & 2 deletions ulc_mm_package/utilities/lock_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import functools

from threading import Lock
from typing import Optional
from contextlib import contextmanager

Expand All @@ -25,7 +26,7 @@ def wrapper(*args, **kwargs):


@contextmanager
def lock_timeout(lock, timeout: Optional[float] = None):
def lock_timeout(lock: Lock, timeout: Optional[float] = None):
"""lock context manager w/ timeout
timeout value of 'None' or negative numbers disables timeout
Expand All @@ -37,4 +38,5 @@ def lock_timeout(lock, timeout: Optional[float] = None):
try:
yield
finally:
lock.release()
if lock.locked():
lock.release()

0 comments on commit eee9546

Please sign in to comment.