From e40b457fbaeb2236aca2cba73addaec493ddacdf Mon Sep 17 00:00:00 2001 From: Michael Rariden Date: Thu, 9 Apr 2026 12:36:39 -0400 Subject: [PATCH 1/6] add check to make sure image draw's red scatter is correct --- cellpose/gui/guiparts.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cellpose/gui/guiparts.py b/cellpose/gui/guiparts.py index 000039a5..b166ed06 100644 --- a/cellpose/gui/guiparts.py +++ b/cellpose/gui/guiparts.py @@ -716,7 +716,9 @@ def is_at_start(self, pos): return False def end_stroke(self): - self.parent.p0.removeItem(self.scatter) + if self.scatter is not None: + if self.scatter.scene() == self.parent.layer.scene(): + self.parent.p0.removeItem(self.scatter) if not self.parent.stroke_appended: self.parent.strokes.append(self.parent.current_stroke) self.parent.stroke_appended = True From 55b1be40b423579053b39db95751313644aeca5d Mon Sep 17 00:00:00 2001 From: Michael Rariden Date: Thu, 9 Apr 2026 13:00:30 -0400 Subject: [PATCH 2/6] add try catch to save when error occurs --- cellpose/gui/guiparts.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cellpose/gui/guiparts.py b/cellpose/gui/guiparts.py index b166ed06..3f204c1b 100644 --- a/cellpose/gui/guiparts.py +++ b/cellpose/gui/guiparts.py @@ -8,6 +8,8 @@ import numpy as np import pathlib, os +from cellpose.gui.io import _save_sets + def stylesheet(): return """ @@ -631,8 +633,11 @@ def __init__(self, image=None, viewbox=None, parent=None, **kargs): self.parent.in_stroke = False def mouseClickEvent(self, ev): - if (self.parent.masksOn or - self.parent.outlinesOn) and not self.parent.removing_region: + if not (self.parent.masksOn or + self.parent.outlinesOn) and self.parent.removing_region: + return + + try: is_right_click = ev.button() == QtCore.Qt.RightButton if self.parent.loaded \ and (is_right_click or ev.modifiers() & QtCore.Qt.ShiftModifier and not ev.double())\ @@ -668,9 +673,12 @@ def mouseClickEvent(self, ev): else: self.parent.select_cell_multi(idx) self.parent.removing_cells_list.append(idx) - elif self.parent.masksOn and not self.parent.deleting_multiple: self.parent.unselect_cell() + except Exception as err: + print('Error encountered while drawing. Saving masks and exiting...') + _save_sets(self.parent) + raise(err) def mouseDragEvent(self, ev): ev.ignore() From 6fac2fb94cee3e6c08e99df2bb6a1e9d03318a1c Mon Sep 17 00:00:00 2001 From: Michael Rariden Date: Thu, 9 Apr 2026 17:19:50 -0400 Subject: [PATCH 3/6] migrate print statements to logging, add debug config for log file --- cellpose/__init__.py | 4 ++++ cellpose/gui/gui.py | 7 ++++--- cellpose/gui/io.py | 28 ++++++++++++++-------------- cellpose/io.py | 33 +++++++++++++++++++++++---------- 4 files changed, 45 insertions(+), 27 deletions(-) diff --git a/cellpose/__init__.py b/cellpose/__init__.py index 4b975024..1b03042c 100644 --- a/cellpose/__init__.py +++ b/cellpose/__init__.py @@ -1 +1,5 @@ from cellpose.version import version, version_str +import logging + +# set base `cellpose` logger +logging.getLogger(__name__).addHandler(logging.NullHandler()) \ No newline at end of file diff --git a/cellpose/gui/gui.py b/cellpose/gui/gui.py index 471f8bda..2d97474b 100644 --- a/cellpose/gui/gui.py +++ b/cellpose/gui/gui.py @@ -2,6 +2,7 @@ Copyright © 2025 Howard Hughes Medical Institute, Authored by Carsen Stringer, Michael Rariden and Marius Pachitariu. """ +import logging import sys, os, pathlib, warnings, datetime, time, copy from qtpy import QtGui, QtCore @@ -138,7 +139,7 @@ def make_cmap(cm=0): def run(image=None): from ..io import logger_setup - logger, log_file = logger_setup() + logger_setup() # Always start by initializing Qt (only once per application) warnings.filterwarnings("ignore") app = QApplication(sys.argv) @@ -166,7 +167,7 @@ def run(image=None): app.setWindowIcon(app_icon) app.setStyle("Fusion") app.setPalette(guiparts.DarkPalette()) - MainW(image=image, logger=logger) + MainW(image=image, logger=logging.getLogger(__name__)) ret = app.exec_() sys.exit(ret) @@ -1141,7 +1142,7 @@ def remove_single_cell(self, idx): self.ismanual = np.delete(self.ismanual, idx - 1) self.cellcolors = np.delete(self.cellcolors, [idx], axis=0) del self.zdraw[idx - 1] - print("GUI_INFO: removed cell %d" % (idx - 1)) + self.logger.info(f'removed cell {idx-1}') def remove_region_cells(self): if self.removing_cells_list: diff --git a/cellpose/gui/io.py b/cellpose/gui/io.py index 73eb0e6c..016c9596 100644 --- a/cellpose/gui/io.py +++ b/cellpose/gui/io.py @@ -184,7 +184,7 @@ def _initialize_images(parent, image, load_3D=False): parent.stack *= 255 if load_3D: - print("GUI_INFO: converted to float and normalized values to 0.0->255.0") + parent.logger.info(": converted to float and normalized values to 0.0->255.0") del image gc.collect() @@ -203,7 +203,7 @@ def _initialize_images(parent, image, load_3D=False): parent.clear_all() if not hasattr(parent, "stack_filtered") and parent.restore: - print("GUI_INFO: no 'img_restore' found, applying current settings") + parent.logger.info(": no 'img_restore' found, applying current settings") parent.compute_restore() if parent.autobtn.isChecked(): @@ -319,7 +319,7 @@ def _load_seg(parent, filename=None, image=None, image_file=None, load_3D=False) if "manual_changes" in dat: parent.track_changes = dat["manual_changes"] - print("GUI_INFO: loaded in previous changes") + parent.logger.info("loaded in previous changes") if "zdraw" in dat: parent.zdraw = dat["zdraw"] else: @@ -407,7 +407,7 @@ def _masks_to_gui(parent, masks, outlines=None, colors=None): # get unique values shape = masks.shape if len(fastremap.unique(masks)) != masks.max() + 1: - print("GUI_INFO: renumbering masks") + parent.logger.info("renumbering masks") fastremap.renumber(masks, in_place=True) outlines = None masks = masks.reshape(shape) @@ -432,7 +432,7 @@ def _masks_to_gui(parent, masks, outlines=None, colors=None): if parent.cellpix_orig.ndim == 2: parent.cellpix_orig = parent.cellpix_orig[np.newaxis, :, :] - print(f"GUI_INFO: {masks.max()} masks found") + parent.logger.info(f"{masks.max()} masks found") # get outlines if outlines is None: # parent.outlinesOn @@ -446,7 +446,7 @@ def _masks_to_gui(parent, masks, outlines=None, colors=None): outlines = masks_to_outlines(parent.cellpix_orig[z]) parent.outpix_orig[z] = outlines * parent.cellpix_orig[z] if z % 50 == 0 and parent.NZ > 1: - print("GUI_INFO: plane %d outlines processed" % z) + parent.logger.info("plane %d outlines processed" % z) if parent.restore and "upsample" in parent.restore: parent.outpix_resize = parent.outpix.copy() else: @@ -458,7 +458,7 @@ def _masks_to_gui(parent, masks, outlines=None, colors=None): outlines = masks_to_outlines(parent.cellpix_orig[z]) parent.outpix_orig[z] = outlines * parent.cellpix_orig[z] if z % 50 == 0 and parent.NZ > 1: - print("GUI_INFO: plane %d outlines processed" % z) + parent.logger.info("plane %d outlines processed" % z) if parent.outpix.ndim == 2: parent.outpix = parent.outpix[np.newaxis, :, :] @@ -470,7 +470,7 @@ def _masks_to_gui(parent, masks, outlines=None, colors=None): parent.ncells.set(parent.cellpix.max()) colors = parent.colormap[:parent.ncells.get(), :3] if colors is None else colors - print("GUI_INFO: creating cellcolors and drawing masks") + parent.logger.info("creating cellcolors and drawing masks") parent.cellcolors = np.concatenate((np.array([[255, 255, 255]]), colors), axis=0).astype(np.uint8) if parent.ncells > 0: @@ -492,13 +492,13 @@ def _save_png(parent): base = os.path.splitext(filename)[0] if parent.NZ == 1: if parent.cellpix[0].max() > 65534: - print("GUI_INFO: saving 2D masks to tif (too many masks for PNG)") + parent.logger.info("saving 2D masks to tif (too many masks for PNG)") imsave(base + "_cp_masks.tif", parent.cellpix[0]) else: - print("GUI_INFO: saving 2D masks to png") + parent.logger.info("saving 2D masks to png") imsave(base + "_cp_masks.png", parent.cellpix[0].astype(np.uint16)) else: - print("GUI_INFO: saving 3D masks to tiff") + parent.logger.info("saving 3D masks to tiff") imsave(base + "_cp_masks.tif", parent.cellpix) @@ -506,14 +506,14 @@ def _save_flows(parent): """ save flows and cellprob to tiff """ filename = parent.filename base = os.path.splitext(filename)[0] - print("GUI_INFO: saving flows and cellprob to tiff") + parent.logger.info("saving flows and cellprob to tiff") if len(parent.flows) > 0: imsave(base + "_cp_cellprob.tif", parent.flows[1]) for i in range(3): imsave(base + f"_cp_flows_{i}.tif", parent.flows[0][..., i]) if len(parent.flows) > 2: imsave(base + "_cp_flows.tif", parent.flows[2]) - print("GUI_INFO: saved flows and cellprob") + parent.logger.info("saved flows and cellprob") else: print("ERROR: no flows or cellprob found") @@ -629,7 +629,7 @@ def _save_sets(parent): dat["img_restore"] = parent.stack_filtered try: np.save(base + "_seg.npy", dat) - print("GUI_INFO: %d ROIs saved to %s" % (parent.ncells.get(), base + "_seg.npy")) + parent.logger.info("%d ROIs saved to %s" % (parent.ncells.get(), base + "_seg.npy")) except Exception as e: print(f"ERROR: {e}") del dat diff --git a/cellpose/io.py b/cellpose/io.py index 1e7a369b..f31d73a2 100644 --- a/cellpose/io.py +++ b/cellpose/io.py @@ -81,18 +81,31 @@ def logger_setup(cp_path=".cellpose", logfile_name="run.log", stdout_file_replac log_file.unlink() except: print('creating new log file') - handlers = [logging.FileHandler(log_file),] + logfile_fh = logging.FileHandler(log_file) if stdout_file_replacement is not None: - handlers.append(logging.FileHandler(stdout_file_replacement)) + stdout_fh = logging.FileHandler(stdout_file_replacement) else: - handlers.append(logging.StreamHandler(sys.stdout)) - logging.basicConfig( - level=logging.INFO, - format="%(asctime)s [%(levelname)s] %(message)s", - handlers=handlers, - force=True - ) - logger = logging.getLogger(__name__) + stdout_fh = logging.StreamHandler(sys.stdout) + + formatter = logging.Formatter("%(asctime)s [%(module)s %(levelname)s] %(message)s") + debug_formatter = logging.Formatter("%(asctime)s %(levelname)s [%(filename)s:%(lineno)d - %(funcName)20s()] %(message)s") + logger = logging.getLogger('cellpose') + logger.setLevel(logging.INFO) + logger.handlers.clear() + + logfile_fh.setFormatter(debug_formatter) + logfile_fh.setLevel(logging.DEBUG) + logger.addHandler(logfile_fh) + + stdout_fh.setFormatter(formatter) + stdout_fh.setLevel(logging.INFO) + stdout_fh.addFilter(lambda r: r if 'gui' in r.name else None) + logger.addHandler(stdout_fh) + + logger.propagate = False + + print(f"[GUI INFO] : WRITING LOG OUTPUT TO {log_file}") + print(version_str) logger.info(f"WRITING LOG OUTPUT TO {log_file}") logger.info(version_str) From 4043ee796b037419d5de3a0860d662fa8e554441 Mon Sep 17 00:00:00 2001 From: Michael Rariden Date: Mon, 13 Apr 2026 17:23:13 -0400 Subject: [PATCH 4/6] migrate last few print statements to log --- cellpose/gui/io.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/cellpose/gui/io.py b/cellpose/gui/io.py index 016c9596..8751670a 100644 --- a/cellpose/gui/io.py +++ b/cellpose/gui/io.py @@ -167,7 +167,7 @@ def _initialize_images(parent, image, load_3D=False): load_3D = parent.load_3D if load_3D is False else load_3D parent.stack = image - print(f"GUI_INFO: image shape: {image.shape}") + parent.logger.info(f" : image shape: {image.shape}") if load_3D: parent.NZ = len(parent.stack) parent.scroll.setMaximum(parent.NZ - 1) @@ -208,17 +208,8 @@ def _initialize_images(parent, image, load_3D=False): if parent.autobtn.isChecked(): if parent.restore is None or parent.restore != "filter": - print( - "GUI_INFO: normalization checked: computing saturation levels (and optionally filtered image)" - ) + parent.logger.info(": normalization checked: computing saturation levels (and optionally filtered image)") parent.compute_saturation() - # elif len(parent.saturation) != parent.NZ: - # parent.saturation = [] - # for r in range(3): - # parent.saturation.append([]) - # for n in range(parent.NZ): - # parent.saturation[-1].append([0, 255]) - # parent.sliders[r].setValue([0, 255]) parent.compute_scale() parent.track_changes = [] From 1eabf4cccf951b75da4ed080138df3ad54940400 Mon Sep 17 00:00:00 2001 From: Michael Rariden Date: Mon, 13 Apr 2026 17:23:43 -0400 Subject: [PATCH 5/6] remove std out filter --- cellpose/io.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cellpose/io.py b/cellpose/io.py index f31d73a2..9dac2dd3 100644 --- a/cellpose/io.py +++ b/cellpose/io.py @@ -99,7 +99,6 @@ def logger_setup(cp_path=".cellpose", logfile_name="run.log", stdout_file_replac stdout_fh.setFormatter(formatter) stdout_fh.setLevel(logging.INFO) - stdout_fh.addFilter(lambda r: r if 'gui' in r.name else None) logger.addHandler(stdout_fh) logger.propagate = False From 29b65f6337f58987dd0837a7e6c015cb7e76f423 Mon Sep 17 00:00:00 2001 From: Michael Rariden Date: Mon, 13 Apr 2026 17:28:13 -0400 Subject: [PATCH 6/6] add check for scatter attribute in end_stroke method --- cellpose/gui/guiparts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cellpose/gui/guiparts.py b/cellpose/gui/guiparts.py index 3f204c1b..fcb67c96 100644 --- a/cellpose/gui/guiparts.py +++ b/cellpose/gui/guiparts.py @@ -724,7 +724,7 @@ def is_at_start(self, pos): return False def end_stroke(self): - if self.scatter is not None: + if hasattr(self, 'scatter') and self.scatter is not None: if self.scatter.scene() == self.parent.layer.scene(): self.parent.p0.removeItem(self.scatter) if not self.parent.stroke_appended: