def __init__(self, min: float = 1, max: float = 100, base: float = math.e, **kwargs): for key in ("maximum", "minimum"): if key in kwargs: import warnings warnings.warn( f"The {key!r} keyword arguments has been changed to {key[:3]!r}. " "In the future this will raise an exception\n", FutureWarning, ) if key == "maximum": max = kwargs.pop(key) else: min = kwargs.pop(key) self._base = base app = use_app() assert app.native super().__init__( min=min, max=max, widget_type=app.get_obj("Slider"), **kwargs, )
def __init__(self, iterable: Iterable = None, *args, **kwargs) -> None: kwargs = kwargs.copy() pbar_kwargs = {k: kwargs.pop(k) for k in set(kwargs) - _tqdm_kwargs} self._mgui = _find_calling_function_gui() if self._in_visible_gui: kwargs["gui"] = True kwargs.setdefault("mininterval", 0.025) super().__init__(iterable, *args, **kwargs) if not self._in_visible_gui: return self.sp = lambda x: None # no-op status printer, required for older tqdm compat if self.disable: return # check if we're being instantiated inside of a magicgui container self.progressbar = self._get_progressbar(**pbar_kwargs) self._app = use_app() if self.total is not None: # initialize progress bar range self.progressbar.range = (self.n, self.total) self.progressbar.value = self.n else: # show a busy indicator instead of a percentage of steps self.progressbar.range = (0, 0) self.progressbar.show()
def _int_widget_to_float(name): app = use_app() assert app.native cls = app.get_obj(name) import builtins def update_precision(self, min=None, max=None, step=None): orig = self._precision if min is not None or max is not None: min = min or self._mgui_get_min() max = max or self._mgui_get_max() # make sure val * precision is within int32 overflow limit for Qt val = builtins.max([abs(min), abs(max)]) while abs(self._precision * val) >= 2**32 // 2: self._precision *= 0.1 elif step: while step < (1 / self._precision): self._precision *= 10 ratio = self._precision / orig if ratio != 1: self._mgui_set_value(self._mgui_get_value() * ratio) if not step: self._mgui_set_max(self._mgui_get_max() * ratio) self._mgui_set_min(self._mgui_get_min() * ratio) # self._mgui_set_step(self._mgui_get_step() * ratio) new_cls = type( f"Float{cls.__name__}", (cls, ), { "__module__": __name__, "_precision": 1e6, "_update_precision": update_precision, }, ) # patch the backend widget to convert between float/int for attr in ["value", "max", "min", "step"]: get_meth_name = f"_mgui_get_{attr}" set_meth_name = f"_mgui_set_{attr}" def new_getter(self, o_getter=getattr(new_cls, get_meth_name)): return o_getter(self) / self._precision def new_setter(self, val, o_setter=getattr(new_cls, set_meth_name), attr=attr): if attr in ("step", "max", "min"): self._update_precision(**{attr: val}) o_setter(self, int(val * self._precision)) setattr(new_cls, get_meth_name, new_getter) setattr(new_cls, set_meth_name, new_setter) return new_cls
def __init__(self, **kwargs): app = use_app() assert app.native widget = app.get_obj(widget_name or cls.__name__) if transform: widget = transform(widget) kwargs["widget_type"] = widget super(cls, self).__init__(**kwargs)
def _unify_label_widths(self, event=None): if not self._initialized: return need_labels = [w for w in self if not isinstance(w, ButtonWidget)] if self.layout == "vertical" and self.labels and need_labels: measure = use_app().get_obj("get_text_width") widest_label = max(measure(w.label) for w in need_labels) for w in self: labeled_widget = w._labeled_widget() if labeled_widget: labeled_widget.label_width = widest_label
def __init__( self, mode: FileDialogMode = FileDialogMode.EXISTING_FILE, filter=None, **kwargs ): self.line_edit = LineEdit(value=kwargs.pop("value", None)) self.choose_btn = PushButton() self.mode = mode # sets the button text too self.filter = filter kwargs["widgets"] = [self.line_edit, self.choose_btn] kwargs["labels"] = False kwargs["layout"] = "horizontal" super().__init__(**kwargs) self.margins = (0, 0, 0, 0) self._show_file_dialog = use_app().get_obj("show_file_dialog") self.choose_btn.changed.connect(self._on_choose_clicked)
def __init__( self, widget_type: Type[_protocols.WidgetProtocol], name: str = "", annotation: Any = None, label: str = None, tooltip: Optional[str] = None, visible: Optional[bool] = None, enabled: bool = True, gui_only=False, backend_kwargs=dict(), **extra, ): # for ipywidgets API compatibility label = label or extra.pop("description", None) if extra: warnings.warn( f"\n\n{self.__class__.__name__}.__init__() got unexpected " f"keyword arguments {set(extra)!r}.\n" "In the future this will raise an exception\n", FutureWarning, ) _prot = self.__class__.__annotations__["_widget"] if not isinstance(_prot, str): _prot = _prot.__name__ prot = getattr(_protocols, _prot.replace("_protocols.", "")) _protocols.assert_protocol(widget_type, prot) self.__magicgui_app__ = use_app() assert self.__magicgui_app__.native self._widget = widget_type(**backend_kwargs) self.name: str = name self.param_kind = inspect.Parameter.POSITIONAL_OR_KEYWORD self._label = label self.tooltip = tooltip self.enabled = enabled self.annotation: Any = annotation self.gui_only = gui_only self.parent_changed = EventEmitter(source=self, type="parent_changed") self.label_changed = EventEmitter(source=self, type="label_changed") self._widget._mgui_bind_parent_change_callback(self._emit_parent) # put the magicgui widget on the native object...may cause error on some backend self.native._magic_widget = self self._post_init() self._visible: bool = False self._explicitly_hidden: bool = False if visible is not None: self.visible = visible
def transform_get_set( cls, get_transform: Optional[Callable[[Any], Any]] = None, set_transform: Optional[Callable[[Any], Any]] = None, prefix: str = "Transformed", ): """Overly complicated decorator to transform the get/set methods of a class.""" if isinstance(cls, str): from magicgui.application import use_app app = use_app() assert app.native cls = app.get_obj(cls) if not issubclass(cls, ValueWidgetProtocol): raise TypeError( "Class must implement `BaseValueWidget` to transform getter and setter." ) suffixes = ["value"] if issubclass(cls, RangedWidgetProtocol): suffixes.extend(["max", "min", "step"]) new_cls = type(f"{prefix}{cls.__name__}", (cls, ), {"__module__": __name__}) for suffix in suffixes: if get_transform: meth_name = f"_mgui_get_{suffix}" old_getter = getattr(new_cls, meth_name, None) def new_getter(obj, old_getter=old_getter, transform=get_transform): return transform(old_getter(obj)) setattr(new_cls, meth_name, new_getter) if set_transform: meth_name = f"_mgui_set_{suffix}" old_setter = getattr(new_cls, meth_name, None) def new_setter(obj, val, old_setter=old_setter, transform=set_transform): old_setter(obj, transform(val)) setattr(new_cls, meth_name, new_setter) return new_cls
def __init__( self, value: Optional[TableData] = None, *, index: Collection = None, columns: Collection = None, **kwargs, ) -> None: app = use_app() assert app.native kwargs["widget_type"] = app.get_obj("Table") super().__init__(**kwargs) self._data = DataView(self) data, _index, _columns = normalize_table_data(value) self.value = { "data": data, "index": index if index is not None else _index, "columns": columns if columns is not None else _columns, }
def __init__(self, choices=(), orientation="vertical", **kwargs): app = use_app() assert app.native kwargs["widget_type"] = app.get_obj("RadioButtons") super().__init__(choices=choices, **kwargs) self.orientation = orientation
def plugin( viewer: napari.Viewer, label_head, image: napari.layers.Image, axes, label_nn, model_type, model2d, model3d, model_folder, model_axes, norm_image, perc_low, perc_high, input_scale, label_nms, prob_thresh, nms_thresh, output_type, label_adv, n_tiles, norm_axes, timelapse_opts, cnn_output, set_thresholds, defaults_button, progress_bar: mw.ProgressBar, ) -> List[napari.types.LayerDataTuple]: model = get_model(*model_selected) if model._is_multiclass(): warn( "multi-class mode not supported yet, ignoring classification output" ) lkwargs = {} x = get_data(image) axes = axes_check_and_normalize(axes, length=x.ndim) if not (input_scale is None or isinstance(input_scale, numbers.Number)): input_scale = tuple(s for a, s in zip(axes, input_scale) if a not in ("T", )) # print(f'scaling by {input_scale}') if not axes.replace("T", "").startswith( model._axes_out.replace("C", "")): warn( f"output images have different axes ({model._axes_out.replace('C','')}) than input image ({axes})" ) # TODO: adjust image.scale according to shuffled axes if norm_image: axes_norm = axes_check_and_normalize(norm_axes) axes_norm = "".join(set(axes_norm).intersection( set(axes))) # relevant axes present in input image assert len(axes_norm) > 0 # always jointly normalize channels for RGB images if ("C" in axes and image.rgb == True) and ("C" not in axes_norm): axes_norm = axes_norm + "C" warn("jointly normalizing channels of RGB input image") ax = axes_dict(axes) _axis = tuple(sorted(ax[a] for a in axes_norm)) # # TODO: address joint vs. channel/time-separate normalization properly (let user choose) # # also needs to be documented somewhere # if 'T' in axes: # if 'C' not in axes or image.rgb == True: # # normalize channels jointly, frames independently # _axis = tuple(i for i in range(x.ndim) if i not in (ax['T'],)) # else: # # normalize channels independently, frames independently # _axis = tuple(i for i in range(x.ndim) if i not in (ax['T'],ax['C'])) # else: # if 'C' not in axes or image.rgb == True: # # normalize channels jointly # _axis = None # else: # # normalize channels independently # _axis = tuple(i for i in range(x.ndim) if i not in (ax['C'],)) x = normalize(x, perc_low, perc_high, axis=_axis) # TODO: progress bar (labels) often don't show up. events not processed? if "T" in axes: app = use_app() t = axes_dict(axes)["T"] n_frames = x.shape[t] if n_tiles is not None: # remove tiling value for time axis n_tiles = tuple(v for i, v in enumerate(n_tiles) if i != t) def progress(it, **kwargs): progress_bar.label = "StarDist Prediction (frames)" progress_bar.range = (0, n_frames) progress_bar.value = 0 progress_bar.show() app.process_events() for item in it: yield item progress_bar.increment() app.process_events() app.process_events() elif n_tiles is not None and np.prod(n_tiles) > 1: n_tiles = tuple(n_tiles) app = use_app() def progress(it, **kwargs): progress_bar.label = "CNN Prediction (tiles)" progress_bar.range = (0, kwargs.get("total", 0)) progress_bar.value = 0 progress_bar.show() app.process_events() for item in it: yield item progress_bar.increment() app.process_events() # progress_bar.label = "NMS Postprocessing" progress_bar.range = (0, 0) app.process_events() else: progress = False progress_bar.label = "StarDist Prediction" progress_bar.range = (0, 0) progress_bar.show() use_app().process_events() # semantic output axes of predictions assert model._axes_out[-1] == "C" axes_out = list(model._axes_out[:-1]) if "T" in axes: x_reorder = np.moveaxis(x, t, 0) axes_reorder = axes.replace("T", "") axes_out.insert(t, "T") res = tuple( zip(*tuple( model.predict_instances( _x, axes=axes_reorder, prob_thresh=prob_thresh, nms_thresh=nms_thresh, n_tiles=n_tiles, scale=input_scale, sparse=(not cnn_output), return_predict=cnn_output, ) for _x in progress(x_reorder)))) if cnn_output: labels, polys = tuple(zip(*res[0])) cnn_output = tuple(np.stack(c, t) for c in tuple(zip(*res[1]))) else: labels, polys = res labels = np.asarray(labels) if len(polys) > 1: if timelapse_opts == TimelapseLabels.Match.value: # match labels in consecutive frames (-> simple IoU tracking) labels = group_matching_labels(labels) elif timelapse_opts == TimelapseLabels.Unique.value: # make label ids unique (shift by offset) offsets = np.cumsum([len(p["points"]) for p in polys]) for y, off in zip(labels[1:], offsets): y[y > 0] += off elif timelapse_opts == TimelapseLabels.Separate.value: # each frame processed separately (nothing to do) pass else: raise NotImplementedError( f"unknown option '{timelapse_opts}' for time-lapse labels" ) labels = np.moveaxis(labels, 0, t) if isinstance(model, StarDist3D): # TODO poly output support for 3D timelapse polys = None else: polys = dict( coord=np.concatenate( tuple( np.insert(p["coord"], t, _t, axis=-2) for _t, p in enumerate(polys)), axis=0, ), points=np.concatenate( tuple( np.insert(p["points"], t, _t, axis=-1) for _t, p in enumerate(polys)), axis=0, ), ) if cnn_output: pred = (labels, polys), cnn_output else: pred = labels, polys else: # TODO: possible to run this in a way that it can be canceled? pred = model.predict_instances( x, axes=axes, prob_thresh=prob_thresh, nms_thresh=nms_thresh, n_tiles=n_tiles, show_tile_progress=progress, scale=input_scale, sparse=(not cnn_output), return_predict=cnn_output, ) progress_bar.hide() # determine scale for output axes scale_in_dict = dict(zip(axes, image.scale)) scale_out = [scale_in_dict.get(a, 1.0) for a in axes_out] layers = [] if cnn_output: (labels, polys), cnn_out = pred prob, dist = cnn_out[:2] dist = np.moveaxis(dist, -1, 0) assert len(model.config.grid) == len(model.config.axes) - 1 grid_dict = dict( zip(model.config.axes.replace("C", ""), model.config.grid)) # scale output axes to match input axes _scale = [ s * grid_dict.get(a, 1) for a, s in zip(axes_out, scale_out) ] # small translation correction if grid > 1 (since napari centers objects) _translate = [0.5 * (grid_dict.get(a, 1) - 1) for a in axes_out] layers.append(( dist, dict( name="StarDist distances", scale=[1] + _scale, translate=[0] + _translate, **lkwargs, ), "image", )) layers.append(( prob, dict( name="StarDist probability", scale=_scale, translate=_translate, **lkwargs, ), "image", )) else: labels, polys = pred if output_type in (Output.Labels.value, Output.Both.value): layers.append(( labels, dict(name="StarDist labels", scale=scale_out, opacity=0.5, **lkwargs), "labels", )) if output_type in (Output.Polys.value, Output.Both.value): n_objects = len(polys["points"]) if isinstance(model, StarDist3D): surface = surface_from_polys(polys) layers.append(( surface, dict( name="StarDist polyhedra", contrast_limits=(0, surface[-1].max()), scale=scale_out, colormap=label_colormap(n_objects), **lkwargs, ), "surface", )) else: # TODO: sometimes hangs for long time (indefinitely?) when returning many polygons (?) # seems to be a known issue: https://github.com/napari/napari/issues/2015 # TODO: coordinates correct or need offset (0.5 or so)? shapes = np.moveaxis(polys["coord"], -1, -2) layers.append(( shapes, dict( name="StarDist polygons", shape_type="polygon", scale=scale_out, edge_width=0.75, edge_color="yellow", face_color=[0, 0, 0, 0], **lkwargs, ), "shapes", )) return layers
def create_widget( value: Any = None, annotation: Any = None, name: str = "", param_kind: str | inspect._ParameterKind = "POSITIONAL_OR_KEYWORD", label=None, gui_only=False, app=None, widget_type: str | type[_protocols.WidgetProtocol] | None = None, options: WidgetOptions = dict(), ): """Create and return appropriate widget subclass. This factory function can be used to create a widget appropriate for the provided ``value`` and/or ``annotation`` provided. Parameters ---------- value : Any, optional The starting value for the widget, by default ``None`` annotation : Any, optional The type annotation for the parameter represented by the widget, by default ``None`` name : str, optional The name of the parameter represented by this widget. by default ``""`` param_kind : str, optional The :attr:`inspect.Parameter.kind` represented by this widget. Used in building signatures from multiple widgets, by default "``POSITIONAL_OR_KEYWORD``" label : str A string to use for an associated Label widget (if this widget is being shown in a :class:`~magicgui.widgets.Container` widget, and labels are on). By default, ``name`` will be used. Note: ``name`` refers the name of the parameter, as might be used in a signature, whereas label is just the label for that widget in the GUI. gui_only : bool, optional Whether the widget should be considered "only for the gui", or if it should be included in any widget container signatures, by default False app : str, optional The backend to use, by default ``None`` widget_type : str or Type[WidgetProtocol] or None A class implementing a widget protocol or a string with the name of a magicgui widget type (e.g. "Label", "PushButton", etc...). If provided, this widget type will be used instead of the type autodetermined from ``value`` and/or ``annotation`` above. options : WidgetOptions, optional Dict of options to pass to the Widget constructor, by default dict() Returns ------- Widget An instantiated widget subclass Raises ------ TypeError If the provided or autodetected ``widget_type`` does not implement any known widget protocols from widgets._protocols. """ kwargs = locals() _kind = kwargs.pop("param_kind", None) _app = use_app(kwargs.pop("app")) assert _app.native if isinstance(widget_type, _protocols.WidgetProtocol): wdg_class = kwargs.pop("widget_type") else: from magicgui.type_map import get_widget_class if widget_type: options["widget_type"] = widget_type wdg_class, opts = get_widget_class(value, annotation, options) if issubclass(wdg_class, Widget): opts.update(kwargs.pop("options")) kwargs.update(opts) kwargs.pop("widget_type", None) widget = wdg_class(**kwargs) if _kind: widget.param_kind = _kind return widget # pick the appropriate subclass for the given protocol # order matters for p in ("Categorical", "Ranged", "Button", "Value", ""): prot = getattr(_protocols, f"{p}WidgetProtocol") if isinstance(wdg_class, prot): options = kwargs.pop("options", {}) cls = getattr(_bases, f"{p}Widget") widget = cls(widget_type=wdg_class, **kwargs, **options) if _kind: widget.param_kind = _kind return widget raise TypeError( f"{wdg_class!r} does not implement any known widget protocols")