From 6c4934af8e279b305df9319e72ab357bd4308cb6 Mon Sep 17 00:00:00 2001
From: uermel
Filaments are traced centrelines (e.g. microtubules, actin) of objects declared as filaments
@@ -27,6 +29,15 @@ Usage:
+ [ pickSpacing Å ]
+
copick filament (new | select | focus | go | next | previous |
reverse | delete | convert) [ filament-id ]
copick filament save [ userId id ] [ sessionId id ]
- [ pickSpacing Å ]
copick filament style [ color instance | object ]
+ [ transparency percent ]
Usage:
coloured by its filament ID; picks sampled from a filament carry the same ID and colour.
+ Colour and transparency: copick filament style color object draws all tubes in the object's colour + instead (color instance switches back), and copick filament style transparency percent makes + the tubes transparent. Instance colours take their alpha from the object's colour, so the transparency is the same + in both modes; it reduces the alpha of the object's colour (0 keeps it, 100 hides the tubes). Both settings apply + to all filament sets and are remembered; control points stay opaque. The Copick Annotate window has the same + controls (Colour by instance above the filament list, Transparency). Without options the command + reports the current style. +
+Tracing: copick filament trace on binds the right mouse button to trace copick filament. Click on the shown tomogram plane (single plane, orthoplanes or tilted slab) to add a control point to the active filament; drag a control point to move it; shift-click a control point to remove it. The centreline is @@ -82,6 +93,8 @@
copick open filaments microtubule:tracer/1+ copick filament save sessionId manual-2 pickSpacing 82
copick new filaments microtubule
copick filament trace on
- copick filament save sessionId manual-2 pickSpacing 82
The Filaments tab, the Copick Edit toolbar tab and the shortcuts - tf nf fn fp rv xf run the same commands.
+ tf cf nf fn fp rv jf xf run the same commands.See also: copick instance, copick open / show / hide, diff --git a/src/filaments/controller.py b/src/filaments/controller.py index 8626418..af738d7 100644 --- a/src/filaments/controller.py +++ b/src/filaments/controller.py @@ -98,6 +98,33 @@ def edit_session(self) -> Optional[FilamentEditSession]: def tube_scale(self) -> float: return float(getattr(self.tool.settings, "filament_tube_scale", 0.5)) + def color_by_instance(self) -> bool: + return bool(getattr(self.tool.settings, "filament_color_by_instance", True)) + + def transparency(self) -> float: + return float(getattr(self.tool.settings, "filament_transparency", 0.0)) + + def _new_model(self, edit: FilamentEditSession) -> FilamentSetModel: + return FilamentSetModel( + self.session, + edit, + tube_scale=self.tube_scale(), + color_by_instance=self.color_by_instance(), + transparency=self.transparency(), + ) + + def set_style(self, color_by_instance: Optional[bool] = None, transparency: Optional[float] = None) -> None: + """Colour every filament set by instance or by object, and set the tubes' transparency (percent; saved).""" + settings = self.tool.settings + if color_by_instance is not None: + settings.filament_color_by_instance = bool(color_by_instance) + if transparency is not None: + settings.filament_transparency = min(max(float(transparency), 0.0), 100.0) + for _edit, model in list(self.entries.values()): + if not model.deleted: + model.set_style(self.color_by_instance(), self.transparency()) + self.notify() + # -- open / close ---------------------------------------------------------------------------------------------- def show(self, copick_filaments: Any) -> FilamentSetModel: @@ -107,7 +134,7 @@ def show(self, copick_filaments: Any) -> FilamentSetModel: step = copick_filaments.voxel_spacing or self._tomogram_voxel_size() or 10.0 edit = FilamentEditSession.from_filaments(copick_filaments, step=step) self._track_history(edit) - model = FilamentSetModel(self.session, edit, tube_scale=self.tube_scale()) + model = self._new_model(edit) self.session.models.add([model]) model.redraw() self.entries[copick_filaments] = (edit, model) @@ -135,7 +162,7 @@ def new(self, object_name: str, user_id: str, session_id: str) -> FilamentEditSe step = self._tomogram_voxel_size() or 10.0 edit = FilamentEditSession(run, object_name, user_id, session_id, step=step) self._track_history(edit) - model = FilamentSetModel(self.session, edit, tube_scale=self.tube_scale()) + model = self._new_model(edit) self.session.models.add([model]) self.entries[edit] = (edit, model) self.active_key = edit diff --git a/src/filaments/model.py b/src/filaments/model.py index 7c3d11d..b97e6b8 100644 --- a/src/filaments/model.py +++ b/src/filaments/model.py @@ -35,11 +35,20 @@ class FilamentSetModel(Model): """Tubes for the filaments of one ``FilamentEditSession``.""" - def __init__(self, session, edit_session, tube_scale: float = 0.5): + def __init__( + self, + session, + edit_session, + tube_scale: float = 0.5, + color_by_instance: bool = True, + transparency: float = 0.0, + ): name = f"{edit_session.object_name} filaments ({edit_session.user_id}/{edit_session.session_id})" Model.__init__(self, name, session) self.edit_session = edit_session self.tube_scale = float(tube_scale) + self.color_by_instance = bool(color_by_instance) + self.transparency = min(max(float(transparency), 0.0), 100.0) self._tubes: Dict[int, Surface] = {} self._controls: Optional[Surface] = None self._handle_radius: Optional[float] = None @@ -70,8 +79,30 @@ def handle_radius(self) -> float: tubes = [self.tube_radius(i) for i in self.edit_session.filaments] or [self.radius * self.tube_scale] return max(HANDLE_OVER_TUBE * max(tubes), HANDLE_MIN_STEPS * self.edit_session.step) + @property + def single_rgba(self) -> np.ndarray: + """The set's single colour: the object's colour, its alpha reduced by ``transparency`` (percent).""" + rgba = self.base_rgba.astype(float) + rgba[3] *= (100.0 - self.transparency) / 100.0 + return np.round(rgba).astype(np.uint8) + def color_of(self, instance_id: int) -> np.ndarray: - return instance_colors([instance_id], self.base_rgba, dtype=np.uint8)[0] + """A tube's colour: its instance colour, with the alpha of the single colour, or the single colour itself.""" + single = self.single_rgba + if not self.color_by_instance: + return single + return instance_colors([instance_id], single, dtype=np.uint8)[0] + + def set_style(self, color_by_instance: Optional[bool] = None, transparency: Optional[float] = None) -> None: + """Recolour the tubes (and handles) without rebuilding them.""" + if color_by_instance is not None: + self.color_by_instance = bool(color_by_instance) + if transparency is not None: + self.transparency = min(max(float(transparency), 0.0), 100.0) + for instance_id, surf in self._tubes.items(): + if not surf.deleted: + surf.color = self.color_of(instance_id) + self._draw_controls() # ---------------------------------------------------------------------------------------------------------------- # Drawing @@ -136,7 +167,12 @@ def _draw_controls(self) -> None: k = ACTIVE_HANDLE_SCALE if i == active else 1.0 places.append(Place(axes=np.eye(3) * k, origin=p)) self._controls.positions = Places(places) - colors = instance_colors(ids, self.base_rgba, dtype=np.uint8) + opaque = self.base_rgba.copy() + opaque[3] = 255 # handles stay opaque, so editing stays readable on transparent tubes + if self.color_by_instance: + colors = instance_colors(ids, opaque, dtype=np.uint8) + else: + colors = np.tile(opaque, (len(ids), 1)) colors[ids == active] = (255, 255, 255, 255) # the active filament's handles are white self._controls.colors = colors self._controls.display = True diff --git a/src/misc/settings.py b/src/misc/settings.py index c4babbc..e191be3 100644 --- a/src/misc/settings.py +++ b/src/misc/settings.py @@ -14,6 +14,8 @@ class CoPickSettings(Settings): "spotlight_particles": False, # Also hide particles outside the spotlight sphere "filament_tube_scale": 0.5, # Filament tube radius as a fraction of the object radius "filament_pick_spacing": 0.0, # Default spacing of picks sampled along filaments (0 = object radius) + "filament_color_by_instance": True, # Colour filament tubes by filament ID (False: the object's colour) + "filament_transparency": 0.0, # Transparency of filament tubes, percent (0 = the object colour's alpha) "paint_radius": 50.0, # Instance paint brush radius (Å) "color_picks_by_instance": False, # Colour every particle list by instance ID (filaments always are) } diff --git a/src/ui/annotation_panels.py b/src/ui/annotation_panels.py index b084ecf..25c5a75 100644 --- a/src/ui/annotation_panels.py +++ b/src/ui/annotation_panels.py @@ -146,12 +146,21 @@ def __init__(self, tool: Any, parent=None): lambda: self._cmd("copick filament convert"), ) self.save_btn = _button("💾", "Save the filament set…", self._save) + self.transparency_spin = QSpinBox() + self.transparency_spin.setRange(0, 100) + self.transparency_spin.setSuffix(" %") + self.transparency_spin.setKeyboardTracking(False) # one command per committed value + self.transparency_spin.setToolTip( + "Transparency of the filament tubes, whether coloured by instance or by object (all filament sets)", + ) + self.transparency_spin.valueChanged.connect(lambda v: self._cmd(f"copick filament style transparency {v}")) layout.addWidget( _flow_row( self.trace_btn, self.cut_btn, _labelled("Filament", self.active_spin), _labelled("New points", self.mode_combo), + _labelled("Transparency", self.transparency_spin), self.convert_btn, self.save_btn, ), @@ -164,6 +173,10 @@ def __init__(self, tool: Any, parent=None): reverse=True, merge=True, merge_tip="Join the selected filaments end to end (into the current one)", + color_toggle=True, + ) + self.browser.color_by_instance_toggled.connect( + lambda on: self._cmd(f"copick filament style color {'instance' if on else 'object'}"), ) self.browser.current_changed.connect(lambda k: self._cmd(f"copick filament select {k}")) self.browser.focus_requested.connect(lambda k: self._cmd(f"copick filament focus {k}")) @@ -231,6 +244,10 @@ def refresh(self) -> None: edit = self.ctl.edit_session from ..filaments.controller import CUT_MODE, TRACE_MODE + self.browser.set_color_by_instance(self.ctl.color_by_instance()) + self.transparency_spin.blockSignals(True) + self.transparency_spin.setValue(round(self.ctl.transparency())) + self.transparency_spin.blockSignals(False) right = self.ctl.right_mode if self.ctl.editing else None for btn, mode in ((self.trace_btn, TRACE_MODE), (self.cut_btn, CUT_MODE)): btn.blockSignals(True) @@ -261,6 +278,10 @@ def refresh(self) -> None: from copick_shared_ui.util.instances import InstanceRow, rows_from_filaments rows = rows_from_filaments(edit.to_list()) + if not self.ctl.color_by_instance(): # the swatches show what the tubes show + rgba = tuple(float(c) / 255 for c in (getattr(edit.object, "color", None) or (255, 255, 255, 255))[:4]) + for r in rows: + r.color = rgba known = {r.instance_id for r in rows} rows += [InstanceRow(i, count=len(c), label="pending") for i, c in edit.pending.items() if i not in known] self.browser.set_rows(sorted(rows, key=lambda r: r.instance_id), kind="filaments")