Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ synopsis = "Join filaments end to end (default: those selected in the Annotate w
category = "General"
synopsis = "Cut mode on or off, or cut a filament in two at a point."

[chimerax.command."copick filament style"]
category = "General"
synopsis = "Colour filaments by instance or object, and set their transparency."

[chimerax.command."copick filament save"]
category = "General"
synopsis = "Save the active filament set, optionally with sampled picks."
Expand Down
25 changes: 25 additions & 0 deletions src/cmd/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,22 @@ def copick_filament_cut(session, state: str = "toggle", at=None, instance_id: Op
ctl.stop_tracing()


def copick_filament_style(session, color: Optional[str] = None, transparency: Optional[float] = None):
"""Colour filaments by instance ID or by their object's colour, and set their transparency (percent).

Applies to every filament set and is remembered. Instance colours take their alpha from the object's colour, so
the transparency is the same in both modes. Without options, reports the current style.
"""
tool = _get_running_tool(session)
if tool is None:
return
ctl = tool.filaments
if color is not None or transparency is not None:
ctl.set_style(color_by_instance=None if color is None else color == "instance", transparency=transparency)
mode = "instance" if ctl.color_by_instance() else "object"
session.logger.info(f"copick: filaments coloured by {mode}, transparency {ctl.transparency():g}%")


def copick_filament_save(
session,
user_id: Optional[str] = None,
Expand Down Expand Up @@ -1157,6 +1173,15 @@ def register_annotation_commands():
),
copick_filament_cut,
)
register(
"copick filament style",
CmdDesc(
keyword=[("color", EnumOf(["instance", "object"])), ("transparency", FloatArg)],
synopsis="Colour filaments by instance or object, and set their transparency.",
url=entity_url,
),
copick_filament_style,
)
register(
"copick filament save",
CmdDesc(
Expand Down
20 changes: 17 additions & 3 deletions src/docs/user/commands/copick_filaments.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,25 @@ <h3 class="usage"><a href="usageconventions.html">Usage</a>:
<br><b>copick filament</b> (<b>new</b> | <b>select</b> | <b>focus</b> | <b>go</b> | <b>next</b> | <b>previous</b> |
<b>reverse</b> | <b>delete</b> | <b>convert</b>) [ <i>filament-id</i> ]
<br><b>copick filament save</b> [ <b>userId</b> <i>id</i> ] [ <b>sessionId</b> <i>id</i> ]
[ <b>pickSpacing</b> <i>Å</i> ]</h3>
[ <b>pickSpacing</b> <i>Å</i> ]
<br><b>copick filament style</b> [ <b>color</b> <b>instance</b> | <b>object</b> ]
[ <b>transparency</b> <i>percent</i> ]</h3>

<p>
<b>Filaments</b> are traced centrelines (e.g. microtubules, actin) of objects declared as filaments
(<b>Edit Object Types</b> &rarr; <b>Is Filament</b>). Each filament set is drawn as one tube per filament,
coloured by its filament ID; picks sampled from a filament carry the same ID and colour.
</p>
<p>
<b>Colour and transparency</b>: <b>copick filament style color object</b> draws all tubes in the object's colour
instead (<b>color instance</b> switches back), and <b>copick filament style transparency</b> <i>percent</i> 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 (<b>Colour by instance</b> above the filament list, <b>Transparency</b>). Without options the command
reports the current style.
</p>
<p>
<b>Tracing</b>: <b>copick filament trace on</b> binds the right mouse button to <em>trace copick filament</em>.
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
Expand Down Expand Up @@ -82,6 +93,8 @@ <h3 class="usage"><a href="usageconventions.html">Usage</a>:
<tr><td><b>reverse</b> [<i>id</i>]</td><td>Reverse the point order (polarity) of a filament.</td></tr>
<tr><td><b>delete</b> [<i>id</i>]</td><td>Delete a filament.</td></tr>
<tr><td><b>convert</b> [<i>id</i>]</td><td>Convert a B-spline fit to an editable Catmull-Rom curve.</td></tr>
<tr><td><b>style</b></td><td><b>copick filament style</b>: colour by instance or by object, and the tubes'
transparency (all sets, remembered).</td></tr>
<tr><td><b>save</b></td><td>Save the active set. With <b>pickSpacing</b>, picks are also sampled every
<i>Å</i> along each filament (+Z along the filament, instance ID = filament ID); they replace the whole
picks set of the same object, user and session. Session 0 (tool output) cannot be written.</td></tr>
Expand All @@ -92,10 +105,11 @@ <h3 class="usage"><a href="usageconventions.html">Usage</a>:
<blockquote> <b>copick open filaments microtubule:tracer/1<br>
copick new filaments microtubule<br>
copick filament trace on<br>
copick filament save sessionId manual-2 pickSpacing 82</b> </blockquote>
copick filament save sessionId manual-2 pickSpacing 82<br>
copick filament style color object transparency 50</b> </blockquote>

<p>The <b>Filaments</b> tab, the <b>Copick Edit</b> toolbar tab and the shortcuts
<b>tf nf fn fp rv xf</b> run the same commands.</p>
<b>tf cf nf fn fp rv jf xf</b> run the same commands.</p>
<p>See also:
<a href="copick_instance.html"><b>copick instance</b></a>,
<a href="copick_open.html"><b>copick open / show / hide</b></a>,
Expand Down
31 changes: 29 additions & 2 deletions src/filaments/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
42 changes: 39 additions & 3 deletions src/filaments/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/misc/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
21 changes: 21 additions & 0 deletions src/ui/annotation_panels.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
Expand All @@ -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}"))
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
Loading