""" Base class for pipeline step providers. A pipeline is three fixed stages - segmentation, classification, post-processing. Each stage is filled by a swappable provider that declares what it produces and consumes (contract tokens), so the editor can offer only compatible combinations. New methods (StarDist, z-score, ...) are added by dropping a provider file into this folder; the step registry discovers it automatically. """ class StepProvider(object): """Interface every step provider implements.""" stage = None # "segmentation" | "classification" type_id = None # stable id stored in the definition JSON display_name = "Step" produces = [] # contract tokens this step outputs, e.g. ["probability_map"] consumes = [] # contract tokens this step requires from upstream def __init__(self, params=None): self.params = params or {} def available(self): """Whether the backing plugin/command is installed and usable.""" return True def validate(self): """Return a list of human-readable problems ([] means OK).""" return [] def run(self, ctx): """ Execute the step. `ctx` is a shared dict carrying at least: temp_path, prob_map_path, show_images, force_recalculate and accumulating outputs (probability_map_path, class_labels_imp, ...). Mutate and/or return ctx. """ raise NotImplementedError("Step providers must implement run(ctx)") # ---- editor support (optional to override) ---- def default_params(self): """Default parameter dict for a fresh instance of this provider.""" return {} def build_panel(self, params): """Return a Swing JPanel for editing this provider's params, or None.""" return None def gather_params(self, panel=None): """Read the editor panel back into a params dict.""" return dict(self.params) # ---- shared helpers (self-contained: only ij / java) ---- def _list_models(self): """{basename: full_path} for every .ilp in the plugin models folder.""" import os from ij import IJ md = os.path.join(IJ.getDirectory("plugins"), "Cell_Quantification_Toolkit", "models") out = {} if os.path.isdir(md): for f in os.listdir(md): if f.lower().endswith('.ilp'): out[f] = os.path.join(md, f) return out def _materialize(self, imp): """ Replace a virtual stack (as ilastik4ij returns for its HDF5 output) with a real in-memory stack. ilastik4ij's output is a lazy VirtualStack; when it is later closed, ImageJ2's colour tool tries to register/read it and crashes with 'Stack argument out of range' on the half-deleted virtual stack. Materializing up front avoids that entirely. """ try: stk = imp.getStack() if stk is not None and stk.isVirtual(): from ij.plugin import Duplicator real = Duplicator().run(imp) imp.setStack(real.getStack()) except Exception: pass return imp def _close_transient_windows(self, tokens): """Close any open image windows whose title contains one of `tokens`. ilastik4ij displays its output (e.g. 'predictions.h5/exported_data') regardless of IJ1 batch mode; this removes those stray windows.""" from ij import WindowManager ids = WindowManager.getIDList() if not ids: return toks = [t.lower() for t in tokens] for i in list(ids): img = WindowManager.getImage(i) if img is None: continue title = (img.getTitle() or "").lower() if any(t in title for t in toks): img.changes = False img.close() def _ilp_workflow_name(self, path): """Read /workflowName from an ilastik .ilp, or None (best-effort).""" from java.lang import Throwable reader = None try: from ch.systemsx.cisd.hdf5 import HDF5Factory from java.io import File reader = HDF5Factory.openForReading(File(path)) try: return reader.readString("/workflowName") except (Exception, Throwable): return None except (Exception, Throwable): return None finally: if reader is not None: try: reader.close() except (Exception, Throwable): pass