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 fabcd9c5..0326d90e 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) @@ -1242,7 +1243,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/guiparts.py b/cellpose/gui/guiparts.py index ad669cc4..14b125cd 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 """ @@ -628,8 +630,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())\ @@ -665,9 +670,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() @@ -713,7 +721,9 @@ def is_at_start(self, pos): return False def end_stroke(self): - self.parent.p0.removeItem(self.scatter) + 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: self.parent.strokes.append(self.parent.current_stroke) self.parent.stroke_appended = True diff --git a/cellpose/gui/io.py b/cellpose/gui/io.py index 958fef23..359df5f2 100644 --- a/cellpose/gui/io.py +++ b/cellpose/gui/io.py @@ -170,7 +170,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) @@ -187,7 +187,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() @@ -205,13 +205,15 @@ def _initialize_images(parent, image, load_3D=False): parent.Lyr, parent.Lxr = parent.Ly, parent.Lx parent.clear_all() + if not hasattr(parent, "stack_filtered") and parent.restore: + parent.logger.info(": no 'img_restore' found, applying current settings") + parent.compute_restore() + 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() - + parent.compute_scale() parent.track_changes = [] @@ -312,7 +314,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: @@ -398,7 +400,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) @@ -423,7 +425,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 @@ -437,7 +439,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: @@ -449,7 +451,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, :, :] @@ -461,7 +463,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: @@ -483,13 +485,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) @@ -497,14 +499,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") @@ -620,7 +622,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..9dac2dd3 100644 --- a/cellpose/io.py +++ b/cellpose/io.py @@ -81,18 +81,30 @@ 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) + 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)