Skip to content
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0935d19
Reset counter after adjusting autobrightness
i-jey Jan 21, 2025
1159339
Oops, need to fmt with an older version
i-jey Jan 21, 2025
c0cb7c2
Merge pull request #597 from czbiohub-sf/periodic_ab_bugfix
i-jey Jan 22, 2025
d44aed5
Add monitoring and dialog box exception if a pressure leak is detecte…
i-jey Jan 31, 2025
60da548
remove extraneous e
i-jey Jan 31, 2025
74eb338
remove old pyngrok dependency (no longer used)
i-jey Feb 20, 2025
8057ad0
Merge pull request #600 from czbiohub-sf/remove_pyngrok
i-jey Feb 20, 2025
748c2fd
Remove unused ngrok utils
i-jey Feb 20, 2025
4771f07
Merge pull request #601 from czbiohub-sf/remove_old_ngrok_utils
i-jey Feb 20, 2025
56ff6fb
Setting NCS performance hit will potentially allow for more frequent …
i-jey Feb 28, 2025
3f4fd5f
formatting
i-jey Mar 1, 2025
42bd123
formatting
i-jey Mar 4, 2025
5833fd8
Force the predictions handler to reset as opposed to relying on gc
i-jey Mar 4, 2025
7404211
Merge pull request #603 from czbiohub-sf/formatting
i-jey Mar 4, 2025
ea5d78d
merge branch 'develop' into reset_predictions_handler
i-jey Mar 4, 2025
df0da79
Merge pull request #604 from czbiohub-sf/reset_predictions_handler
i-jey Mar 5, 2025
2b2588a
Address potential memory leaks
i-jey Mar 5, 2025
4e78eca
remove unnecessary __init__ call in PredictionsHandler reset
i-jey Mar 5, 2025
3b7166f
close per img metadata file
i-jey Mar 7, 2025
819a5e7
This was the main problem - we should not instantiate a new FlowContr…
i-jey Mar 7, 2025
76bb1de
remove unused import
i-jey Mar 7, 2025
5c576bb
Merge branch 'fix_potential_memory_leaks' into ncs_high_throughput
i-jey Mar 7, 2025
604a197
Merge pull request #606 from czbiohub-sf/fix_potential_memory_leaks
i-jey Mar 7, 2025
0813fb7
Merge branch 'develop' into ncs_high_throughput
i-jey Mar 7, 2025
d03f4cb
Set Autofocus batch size for pre-flow/post-flow to 20 (down from 50 w…
i-jey Mar 7, 2025
42914d4
reset flow controller prior to starting cell finder
i-jey Mar 7, 2025
a939cfc
Double the fast flow pneumatic module step size just so we can get up…
i-jey Mar 7, 2025
b2fa3e3
fmt
i-jey Mar 7, 2025
796d854
fmt
i-jey Mar 7, 2025
2f3a6fe
Merge pull request #602 from czbiohub-sf/ncs_high_throughput
i-jey Mar 8, 2025
7aa24a3
merge branch 'develop' into faster_fast_flow
i-jey Mar 8, 2025
6ceacca
Add in a small check to CellFinder to ensure the motor is not in moti…
i-jey Mar 8, 2025
ac952d0
Merge pull request #607 from czbiohub-sf/faster_fast_flow
NicholasBratvold Mar 8, 2025
1802246
Merge branch 'develop' into pressure_leak_warning
i-jey Mar 8, 2025
97f47d8
Revert "Merge branch 'develop' into pressure_leak_warning"
i-jey Mar 10, 2025
09eff4d
rollback to PR607 merge
i-jey Mar 10, 2025
7f19eb8
remove old unused files
i-jey Mar 10, 2025
4ef8ba4
Context: We set the pneumatic module step size to be twice what it is…
i-jey Mar 12, 2025
f13e84b
Merge pull request #608 from czbiohub-sf/pneumatic_module_step_size_r…
i-jey Mar 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ulc_mm_package/QtGUI/acquisition.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" Image manager
"""Image manager

Receives images from the camera and sends them to Liveview and ScopeOp.

Expand Down
10 changes: 0 additions & 10 deletions ulc_mm_package/QtGUI/dev_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@

from ulc_mm_package.image_processing.processing_constants import FLOWRATE

from ulc_mm_package.utilities.ngrok_utils import make_tcp_tunnel, NgrokError
from ulc_mm_package.utilities.email_utils import send_ngrok_email

from ulc_mm_package.neural_nets.AutofocusInference import AutoFocus
import ulc_mm_package.neural_nets.neural_network_constants as nn_constants

Expand Down Expand Up @@ -636,13 +633,6 @@ def __init__(self, *args, **kwargs):
# Misc
self.fan.turn_on_all()
self.btnExit.clicked.connect(self.exit)
try:
ngrok_address = make_tcp_tunnel()
send_ngrok_email()
except NgrokError as e:
print(f"Ngrok error : {e}")
ngrok_address = "-ngrok error-"
self.lblngrok.setText(f"{ngrok_address}")

# Set slider min/max
self.min_exposure_us = 100
Expand Down
2 changes: 1 addition & 1 deletion ulc_mm_package/QtGUI/form_gui.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" Experiment form GUI window
"""Experiment form GUI window

Takes user input and exports experiment metadata.

Expand Down
2 changes: 1 addition & 1 deletion ulc_mm_package/QtGUI/liveview_gui.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" Liveview GUI window
"""Liveview GUI window

Displays camera preview and conveys info to user during runs."""

Expand Down
23 changes: 2 additions & 21 deletions ulc_mm_package/QtGUI/oracle.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" High-level state machine manager.
"""High-level state machine manager.

The Oracle sees all and knows all.
It owns all GUI windows, threads, and worker objects (ScopeOp and Acquisition).
Expand Down Expand Up @@ -70,8 +70,6 @@
AUTOFOCUS_MODEL_DIR,
YOGO_MODEL_DIR,
)
from ulc_mm_package.utilities.email_utils import send_ngrok_email, EmailError
from ulc_mm_package.utilities.ngrok_utils import make_tcp_tunnel, NgrokError

from ulc_mm_package.QtGUI.scope_op import ScopeOp
from ulc_mm_package.QtGUI.form_gui import FormGUI
Expand Down Expand Up @@ -159,24 +157,7 @@ def __init__(self):
self.next_state()

def _init_tcp(self):
try:
tcp_addr = make_tcp_tunnel()
self.logger.info(f"SSH address is {tcp_addr}.")
self.liveview_window.update_tcp(tcp_addr)
send_ngrok_email()
except NgrokError as e:
self.logger.warning(
f"SSH address could not be found - {e}. This can be safely ignored."
)
self.liveview_window.update_tcp("unavailable")
except EmailError as e:
self.logger.warning(
f"SSH address could not be emailed - {e}. This can be safely ignored."
)
except Exception as e:
self.logger.warning(
f"Unexpected error while setting up TCP: {e}. This can be safely ignored."
)
self.liveview_window.update_tcp("unavailable")

def _check_lock(self):
if path.isfile(LOCKFILE):
Expand Down
22 changes: 19 additions & 3 deletions ulc_mm_package/QtGUI/scope_op.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" Mid-level/hardware state machine manager
"""Mid-level/hardware state machine manager

Controls hardware (ie. the Scope) operations.
Manages hardware routines and interactions with Oracle and Acquisition.
Expand Down Expand Up @@ -875,6 +875,9 @@ def run_experiment(self, img, timestamp) -> None:
self.mscope.cell_diagnosis_model.work_queue_size(),
)

# ------------------------------------
# Get and process YOGO results
# ------------------------------------
t0 = perf_counter()
for result in prev_yogo_results:
self.mscope.predictions_handler.add_yogo_pred(result)
Expand Down Expand Up @@ -903,6 +906,9 @@ def run_experiment(self, img, timestamp) -> None:
t1 = perf_counter()
self._update_metadata_if_verbose("yogo_result_mgmt", t1 - t0)

# ------------------------------------
# Run periodic singleshot autofocus routine
# ------------------------------------
t0 = perf_counter()
resized_img = cv2.resize(img, IMG_RESIZED_DIMS, interpolation=cv2.INTER_CUBIC)
try:
Expand Down Expand Up @@ -937,8 +943,10 @@ def run_experiment(self, img, timestamp) -> None:
if filtered_focus_err is not None:
self.filtered_focus_err = filtered_focus_err

# ------------------------------------
# Get classic image sharpness metric
# ------------------------------------
t0 = perf_counter()

# Downsample image for use in flowrate + classic image focus metric
img_ds_10x = downsample_image(img, 10)
try:
Expand All @@ -951,6 +959,10 @@ def run_experiment(self, img, timestamp) -> None:
)
self.oof_to_motor_sweep()
return

# ------------------------------------
# Run flow control routine
# ------------------------------------
try:
self.flowrate, _ = self.flowcontrol_routine.send((img_ds_10x, timestamp))
except Exception as e:
Expand All @@ -963,11 +975,15 @@ def run_experiment(self, img, timestamp) -> None:
t1 = perf_counter()
self._update_metadata_if_verbose("flowrate_dt", t1 - t0)

# ------------------------------------
# Run periodic autobrightness routine
# ------------------------------------
curr_mean_pixel_val = self.periodic_autobrightness_routine.send(resized_img)

# ------------------------------------
# Update remaining metadata in per-image csv
# ------------------------------------
t0 = perf_counter()
# Update remaining metadata
self.img_metadata["motor_pos"] = self.mscope.motor.getCurrentPosition()
try:
pressure, status = self.mscope.pneumatic_module.getPressure()
Expand Down
2 changes: 1 addition & 1 deletion ulc_mm_package/hardware/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def reset_for_end_experiment(self) -> None:
self.cell_diagnosis_model.reset(wait_for_jobs=False)

# Reset predictions handler
self.predictions_handler: PredictionsHandler = PredictionsHandler()
self.predictions_handler.reset()

def shutoff(self):
self.logger.info("Shutting off scope hardware.")
Expand Down
13 changes: 11 additions & 2 deletions ulc_mm_package/hardware/scope_routines.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we turn off fast flow in the future the pneumatic module min step size will stay doubled

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am similarly concerned if this reset adequately resets step size. Have you done multiple runs in a row where at least one run fails on fastflow? From the scope routine it looks like a failure in fastflow means it'll never reset the step size

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
checkLedWorking,
)
from ulc_mm_package.image_processing.flow_control import (
FlowController,
CantReachTargetFlowrate,
)
from ulc_mm_package.image_processing.cell_finder import (
Expand Down Expand Up @@ -248,6 +247,7 @@ def flow_control_routine(
flow_controller.set_alpha(
processing_constants.FLOW_CONTROL_EWMA_ALPHA * 2
) # Double the alpha, ~halve the half life
flow_controller.pneumatic_module.min_step_size *= 2

while True:
img, timestamp = yield flow_val, syringe_can_move
Expand All @@ -270,6 +270,7 @@ def flow_control_routine(
if fast_flow:
if flow_error is not None:
if flow_error == 0:
flow_controller.pneumatic_module.min_step_size /= 2
return flow_val

@init_generator
Expand Down Expand Up @@ -386,6 +387,7 @@ def periodic_autobrightness_routine(
if counter >= processing_constants.PERIODIC_AB_PERIOD_NUM_FRAMES:
autobrightness.autobrightness_pid_control(img)
curr_img_brightness = autobrightness.prev_mean_img_brightness
counter = 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, but how was this loop ever working without this line?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol it embarrassingly wasn't, this was a long overlooked bug


def checkPressureDifference(
self, mscope: MalariaScope, ambient_pressure: float
Expand Down Expand Up @@ -485,7 +487,9 @@ def find_cells_routine(
# Maximum number of times to run check for cells routine before aborting
max_attempts = 3
cell_finder = CellFinder()
flow_controller = FlowController(mscope.pneumatic_module)
mscope.flow_controller.reset()
flow_controller = mscope.flow_controller
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we have a local flow_controller function instead of directly referencing mscope.flow_controller?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for a bit of cleanliness/ease of typing, (instead of having to type mscope.flow_controller each time).


img = yield

# Initial check for cells, return current motor position if cells found
Expand All @@ -495,6 +499,11 @@ def find_cells_routine(
except NoCellsFound:
cell_finder.reset()

# Defensive check, ensure the motor isn't moving (say for example,
# if CellFinder was triggered by an OOF exception and SSAF just triggered a motor move)
while mscope.motor.is_locked():
pass

while True:
"""
1. Pull syringe for pull_time seconds (unless deliberately skipped)
Expand Down
8 changes: 4 additions & 4 deletions ulc_mm_package/image_processing/data_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,16 +298,16 @@ def close(
summary_report_dir / f"{self.time_str}_per_image_metadata_plot.jpg"
)

per_img_metadata_file = open(self.per_img_metadata_filename, "r")
counts_plot_loc = str(summary_report_dir / "counts.jpg")
conf_plot_loc = str(summary_report_dir / "confs.jpg")
objectness_plot_loc = str(summary_report_dir / "objectness.jpg")

# Only generate additional plots if DEBUG_REPORT environment variable is set to True
if DEBUG_REPORT:
make_per_image_metadata_plots(
per_img_metadata_file, per_image_metadata_plot_save_loc
)
with open(self.per_img_metadata_filename, "r") as per_img_metadata_file:
make_per_image_metadata_plots(
per_img_metadata_file, per_image_metadata_plot_save_loc
)

try:
make_cell_count_plot(pred_tensors, counts_plot_loc)
Expand Down
3 changes: 3 additions & 0 deletions ulc_mm_package/neural_nets/NCSModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ def _compile_model(
compiled_model = self.core.compile_model(
model,
self.device_name,
config={
"PERFORMANCE_HINT": "THROUGHPUT",
},
)
self.connected = True
return compiled_model
Expand Down
6 changes: 3 additions & 3 deletions ulc_mm_package/neural_nets/neural_network_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@


# ================ Autofocus constants ================ #
AF_PERIOD_S = 0.5
AF_PERIOD_S = 0.1 # (10 imgs/sec)
AF_PERIOD_NUM = int(
AF_PERIOD_S * ACQUISITION_FPS
) # Used for periodic (ie. EWMA) autofocus
AF_BATCH_SIZE = 10 # Used for single shot autofocus
AF_BATCH_SIZE = 20 # Used for single shot autofocus

AF_THRESHOLD = 2
AF_QSIZE = 10 # For AF_PERIOD_S = 0.5, we have a max delay of 5 sec
AF_QSIZE = 25

AUTOFOCUS_MODEL_NAME = "fast-cosmos-557"
AUTOFOCUS_MODEL_DIR = str(
Expand Down
13 changes: 13 additions & 0 deletions ulc_mm_package/neural_nets/predictions_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ def __init__(self):
# Setup heatmap masking
self.heatmaps = np.zeros((len(YOGO_CLASS_LIST), sy * sx))

def reset(self):
self.pred_tensors.fill(0)
self.new_pred_pointer = 0
self.max_confs = {x: [] for x in self.class_ids}
self.curr_min_of_max_confs_by_class = {
x: HIGH_CONF_THRESH - 1e-6 for x in self.class_ids
}
self.min_confs = {x: [] for x in self.class_ids}
self.curr_max_of_min_confs_by_class = {
x: HIGH_CONF_THRESH for x in self.class_ids
}
Comment on lines +77 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologize in advance because I know very little about the predictions handler... but why do these variables need to reset? I assumed these would be consistent across each experiment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no sweat! These variables hold the predictions for each run - but they are really big arrays - 15 rows * 3.5M columns (which is just an upper bound for how many cells we would reasonably expect YOGO to see). Instead of re-creating this giant array for each run, we just clear it, so that the next run's predictions can fill it up.

self.heatmaps.fill(0)

def add_raw_pred_to_heatmap(self, yogo_res: AsyncInferenceResult) -> None:
"""Add the raw YOGO prediction to the heatmap.

Expand Down
4 changes: 0 additions & 4 deletions ulc_mm_package/scope_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,6 @@ def IMG_HEIGHT(self) -> int:
if VERBOSE:
PER_IMAGE_METADATA_KEYS.extend(PER_IMAGE_TIMING_KEYS)

# ================ Environment variables ================ #
NGROK_AUTH_TOKEN_ENV_VAR = "NGROK_AUTH_TOKEN"
EMAIL_PW_TOKEN = "GMAIL_TOKEN"

# ================ SSD directory constants ================ #
SSD_NAME = "SamsungSSD"
if SIMULATION:
Expand Down
1 change: 1 addition & 0 deletions ulc_mm_package/summary_report/parasitemia_visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ def make_parasitemia_plot(parasitemia, err, savefile):
fig.subplots_adjust(left=0.1, right=0.9, top=0.8, bottom=0.1)
fig.tight_layout()
plt.savefig(savefile)
plt.close()


if __name__ == "__main__":
Expand Down
Loading