Skip to content

fix: pick tools keep identity and place particles at their centre - #105

Merged
uermel merged 3 commits into
mainfrom
uermel/pick-identity
Oct 6, 2026
Merged

uermel merged 3 commits into
mainfrom
uermel/pick-identity

Conversation

@uermel

@uermel uermel commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Summary

Part 1 of 3 of filament support in copick-utils. Stack (main): #105 → #106 → #107; v2.0 twins #108 → #109 → #110. CLI docs: copick/copick#496.

Requires copick 1.28.0, which ships the filament stack (copick/copick#479, #480, #492, #494). The first commit built against the unreleased stack and the last one sets the floor, so squashed, the dependency change is copick>=1.28.0.

The pick tools now treat a pick as the particle it describes:

  • picksin, picksout and clippicks test each particle's centre (location plus the transform's translation) and write the surviving points unchanged: transform, instance ID, score and order. An empty result writes an empty pick set instead of failing and leaving stale output behind.
  • clippicks --ref-seg indexed a (Z, Y, X) volume with (X, Y, Z) indices; it now indexes it correctly, also for non-cubic volumes.
  • picks2seg and the pick-to-mesh converters (picks2sphere, picks2ellipsoid, picks2plane, picks2surface, picks2mesh) place each particle at its centre.
  • Shared helpers in util/picks.py.

Tests

tests/test_pick_tools.py: identity and order survive the filters, a shifted particle is filtered and painted at its centre, a non-cubic --ref-seg, an empty result. Each fails on main. The suite passes locally (45 tests); main has no pytest CI.

🤖 Generated with Claude Code

uermel and others added 2 commits October 5, 2026 18:03
copick-utils' filament, instance and pick-identity work uses APIs from the open
copick PRs #479, #480, #492 and #494 (filament objects, pick identity, the
Filaments entity with curves, instance and panoptic segmentations). Until
those are released, depend on their stack's tip. Replace this with a version
floor once copick publishes them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
picksin, picksout and clippicks rebuilt their output from picks.numpy()/
from_numpy(), which dropped every pick's instance_id and score. For a filament
object that loses which filament a pick belongs to. They also tested the
pick's location, not its particle centre: copick defines the centre as the
location plus the transform's translation (a shift, e.g. from a RELION
import). Now they test the centre and copy the surviving points unchanged and
in order, so orientation, shift, instance_id and score survive.

Also:
- An empty result writes an empty pick set. Before, the run was reported as an
  error and an earlier output in the same place was left on disk.
- An instance segmentation works as a reference (any non-zero ID is inside),
  and a panoptic one is tested on its label channel.
- clippicks --ref-seg built its distance field on the segmentation's (z, y, x)
  grid but indexed it with (x, y, z) indices, which is wrong for any non-cubic
  volume. It now indexes (z, y, x) and rounds to the nearest voxel, matching
  copick's voxel-to-Angstrom mapping (voxel i at i * spacing).
- picks2seg, picks2mesh, picks2sphere, picks2ellipsoid, picks2plane,
  picks2surface and io.readers.coordinates use particle centres.

The shared helpers are in copick_utils.util.picks. Covered by
tests/test_pick_tools.py; every case fails on the previous code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
copick 1.28.0 ships the filament stack this builds on (copick#479, #480,
#492, #494), so the build-against pin to the unreleased branch goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@uermel
uermel added this pull request to stack #113 October 6, 2026 09:06
@uermel
uermel marked this pull request as ready for review October 6, 2026 09:07
@uermel
uermel merged commit 8a944f0 into main Oct 6, 2026
2 of 3 checks passed
uermel added a commit that referenced this pull request Oct 6, 2026
## Summary

Part 2 of 3 of filament support in copick-utils. Stack (`main`): #105 →
#106 → #107; `v2.0` twins #108 → #109 → #110. CLI docs:
copick/copick#496.

copick now has instance segmentations (voxel value = instance ID) and
panoptic segmentations (a label channel plus an instance channel),
selected with `?instance=true` / `?panoptic=true` in a URI (copick
1.28.0: copick/copick#479, #480, #492, #494). This PR teaches
copick-utils to read and write them:

- **Framework.** Segmentation inputs and outputs take their type from
the URI; a command writes the type its `-o` names or refuses one it
cannot write. `filaments` joins picks, meshes and segmentations as a
worker input type. `io.readers.segmentation` and
`io.writers.segmentation` take the instance/panoptic flags. Untyped
selections stay binary and multilabel.
- **`separate-components`** with `-o ...?instance=true` writes one
instance segmentation (IDs 1..K by size) instead of K binary ones;
panoptic output from a multilabel or binary input. The per-component
loops are vectorised.
- **`filter-components`** drops whole instances by volume or by the new
`--min-skeleton-length`, keeping the surviving IDs; multilabel input
keeps its labels.
- **`seg-stats`** reports per-instance rows; `--skeleton` adds skeleton
length and label radius per component, for choosing thresholds.
- **`combine -o ...?panoptic=true`** builds a panoptic segmentation from
stuff labels plus per-object instance segmentations (`--instances`);
**`split`** takes a panoptic input apart again.
- `process/skeleton_graph.py`: a sparse 26-neighbour skeleton graph
(branches, junctions, spur pruning, junction merging), shared with the
filament tracer in part 3.

## Tests

`tests/test_instance_tools.py` (separate, filter and stats keep IDs; a
panoptic combine/split round trip; an untyped selection ignores a
same-key instance store) and `tests/test_skeleton_graph.py`. The suite
passes locally (55 tests).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
uermel added a commit that referenced this pull request Oct 6, 2026
## Summary

Part 3 of 3 of filament support in copick-utils. Stack (`main`): #105 →
#106 → #107; `v2.0` twins #108 → #109 → #110. CLI docs:
copick/copick#496.

Filaments such as microtubules had no real path to picks: `fit-spline`
kept one path per volume, and `seg2picks` treats every object as a
sphere. Three new `copick convert` commands trace, sample and paint
filaments, sharing one set of IDs between copick's Filaments entity,
instance segmentations and picks (copick 1.28.0: copick/copick#479,
#480, #492, #494):

- **`seg2fil`** traces every filament in a binary, multilabel, panoptic
or instance segmentation. Lumen-sized holes are filled first
(`--fill-lumen`), so a tube labelled by its wall traces as one filament.
Components are skeletonized and split into branches; spurs are pruned,
nearby junctions merged, and branches continue through a junction where
they run most nearly straight (`--max-bend`), so crossing filaments stay
separate. Ends are extended to the label's edge. Short (`--min-length`),
blob-like (`--min-aspect`) and thin (`--min-radius`) pieces are
rejected. Each filament's smoothing spline is stored exactly as an
editable `bspline` curve; `--instances` also writes the instance
segmentation of the traced filaments. Tracing an instance segmentation
keeps its IDs.
- **`fil2picks`** samples each filament's curve (any curve kind, or one
derived from its points) at a required `--spacing`, with +Z along the
filament and rotation-minimising frames, grouped by filament ID and
ordered along it.
- **`fil2seg`** paints tubes around filaments into an instance
segmentation.

The old commands are kept and fixed, each pointing to the new ones:

- `process fit-spline` fits every filament instead of the longest
hop-count path, with no O(N³) fallback; spacing is exact (in voxels, now
documented); orientations come from the spline's derivative with
rotation-minimising frames; sharp fits are smoothed further instead of
deleting skeleton points; picks carry filament IDs. New `--label` and
`--filaments`. `--connectivity-radius` is hidden and has no effect.
- `process skeletonize`: new `--prune-length` prunes spurs
(`--remove-short-branches` only ever removed whole small pieces, as its
help now says); the `-o` name is honoured.

The tracer (`process/filament_tracing.py`, numpy/scipy) is public for
copick-pipeliner.

## Acceptance

On the 58 easymode microtubule segmentations of CZ cryoET Data Portal
dataset 10521, run through the CLI:

- 102 filaments (34.5 µm) give 4,255 picks at 82 Å, none on specks.
Consecutive picks are 79.9–82 Å apart, frames turn at most 29° between
neighbours, and the median distance to the label's medial axis is 5.6 Å
(octopi's watershed picks: 6.1 Å; `fit-spline` 1.8.1: 7.1 Å, covering
42% of the traced length).
- IDs agree across the instance segmentation, Filaments and picks;
re-tracing the instance segmentation returns the same IDs.
- Every stored curve is current; resampling at 41 Å needs no re-trace.
- `copick export picks --filament-columns on` writes valid RELION
filament columns, and re-importing them keeps IDs, order and poses
exactly.

## Tests

`tests/test_filaments.py` on synthetic tubes (straight, along Y, arc,
crossing, ring, thick end, hollow tube, speck, blob, sliver), plus the
converter-signature, run-names and legacy-flag pinning tests. The suite
passes locally (84 tests).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
uermel added a commit that referenced this pull request Oct 6, 2026
….0) (#108)

## Summary

Part 1 of 3 of filament support in copick-utils, `v2.0` line
(cherry-picked twin of the `main` PR). Stack (`v2.0`): #108 → #109 →
#110; `main` twins #105 → #106 → #107. CLI docs: copick/copick#496.

Requires copick 2.0.0a3, which ships the v2.0 filament stack
(copick/copick#481, #482, #493, #495). The first commit built against
the unreleased stack and the last one sets the floor, so squashed, the
dependency change is `copick>=2.0.0a3,<3`, with `uv.lock` resolving it
from PyPI.

The pick tools now treat a pick as the particle it describes:

- `picksin`, `picksout` and `clippicks` test each particle's centre
(`location` plus the transform's translation) and write the surviving
points unchanged: transform, instance ID, score and order. An empty
result writes an empty pick set instead of failing and leaving stale
output behind.
- `clippicks --ref-seg` indexed a (Z, Y, X) volume with (X, Y, Z)
indices; it now indexes it correctly, also for non-cubic volumes.
- `picks2seg` and the pick-to-mesh converters (`picks2sphere`,
`picks2ellipsoid`, `picks2plane`, `picks2surface`, `picks2mesh`) place
each particle at its centre.
- Shared helpers in `util/picks.py`.

## Tests

`tests/test_pick_tools.py`: identity and order survive the filters, a
shifted particle is filtered and painted at its centre, a non-cubic
`--ref-seg`, an empty result. Each fails on `v2.0`. The suite passes
locally (68 tests).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
uermel added a commit that referenced this pull request Oct 6, 2026
## Summary

Part 2 of 3 of filament support in copick-utils, `v2.0` line
(cherry-picked twin of the `main` PR). Stack (`v2.0`): #108 → #109 →
#110; `main` twins #105 → #106 → #107. CLI docs: copick/copick#496.

copick now has instance segmentations (voxel value = instance ID) and
panoptic segmentations (a label channel plus an instance channel),
selected with `?instance=true` / `?panoptic=true` in a URI
(copick/copick#481 → #482 → #493 → #495). This PR teaches copick-utils
to read and write them:

- **Framework.** Segmentation inputs and outputs take their type from
the URI; a command writes the type its `-o` names or refuses one it
cannot write. `filaments` joins picks, meshes and segmentations as a
worker input type. `io.readers.segmentation` and
`io.writers.segmentation` take the instance/panoptic flags. Untyped
selections stay binary and multilabel.
- **`separate-components`** with `-o ...?instance=true` writes one
instance segmentation (IDs 1..K by size) instead of K binary ones;
panoptic output from a multilabel or binary input. The per-component
loops are vectorised.
- **`filter-components`** drops whole instances by volume or by the new
`--min-skeleton-length`, keeping the surviving IDs; multilabel input
keeps its labels.
- **`seg-stats`** reports per-instance rows; `--skeleton` adds skeleton
length and label radius per component, for choosing thresholds.
- **`combine -o ...?panoptic=true`** builds a panoptic segmentation from
stuff labels plus per-object instance segmentations (`--instances`);
**`split`** takes a panoptic input apart again.
- `process/skeleton_graph.py`: a sparse 26-neighbour skeleton graph
(branches, junctions, spur pruning, junction merging), shared with the
filament tracer in part 3.

## Tests

`tests/test_instance_tools.py` (separate, filter and stats keep IDs; a
panoptic combine/split round trip; an untyped selection ignores a
same-key instance store) and `tests/test_skeleton_graph.py`. The suite
passes locally (78 tests).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
uermel added a commit that referenced this pull request Oct 6, 2026
## Summary

Part 3 of 3 of filament support in copick-utils, `v2.0` line
(cherry-picked twin of the `main` PR; `fil2seg` reads the tomogram shape
through `get_level_array`, and the package smoke test counts 35 entry
points). Stack (`v2.0`): #108 → #109 → #110; `main` twins #105 → #106 →
#107. CLI docs: copick/copick#496.

Filaments such as microtubules had no real path to picks: `fit-spline`
kept one path per volume, and `seg2picks` treats every object as a
sphere. Three new `copick convert` commands trace, sample and paint
filaments, sharing one set of IDs between copick's Filaments entity,
instance segmentations and picks (copick/copick#481 → #482 → #493 →
#495):

- **`seg2fil`** traces every filament in a binary, multilabel, panoptic
or instance segmentation. Lumen-sized holes are filled first
(`--fill-lumen`), so a tube labelled by its wall traces as one filament.
Components are skeletonized and split into branches; spurs are pruned,
nearby junctions merged, and branches continue through a junction where
they run most nearly straight (`--max-bend`), so crossing filaments stay
separate. Ends are extended to the label's edge. Short (`--min-length`),
blob-like (`--min-aspect`) and thin (`--min-radius`) pieces are
rejected. Each filament's smoothing spline is stored exactly as an
editable `bspline` curve; `--instances` also writes the instance
segmentation of the traced filaments. Tracing an instance segmentation
keeps its IDs.
- **`fil2picks`** samples each filament's curve (any curve kind, or one
derived from its points) at a required `--spacing`, with +Z along the
filament and rotation-minimising frames, grouped by filament ID and
ordered along it.
- **`fil2seg`** paints tubes around filaments into an instance
segmentation.

The old commands are kept and fixed, each pointing to the new ones:

- `process fit-spline` fits every filament instead of the longest
hop-count path, with no O(N³) fallback; spacing is exact (in voxels, now
documented); orientations come from the spline's derivative with
rotation-minimising frames; sharp fits are smoothed further instead of
deleting skeleton points; picks carry filament IDs. New `--label` and
`--filaments`. `--connectivity-radius` is hidden and has no effect.
- `process skeletonize`: new `--prune-length` prunes spurs
(`--remove-short-branches` only ever removed whole small pieces, as its
help now says); the `-o` name is honoured.

The tracer (`process/filament_tracing.py`, numpy/scipy) is public for
copick-pipeliner.

## Acceptance

On the 58 easymode microtubule segmentations of CZ cryoET Data Portal
dataset 10521, run through the CLI:

- 102 filaments (34.5 µm) give 4,255 picks at 82 Å, none on specks.
Consecutive picks are 79.9–82 Å apart, frames turn at most 29° between
neighbours, and the median distance to the label's medial axis is 5.6 Å
(octopi's watershed picks: 6.1 Å; `fit-spline` 1.8.1: 7.1 Å, covering
42% of the traced length).
- IDs agree across the instance segmentation, Filaments and picks;
re-tracing the instance segmentation returns the same IDs.
- Every stored curve is current; resampling at 41 Å needs no re-trace.
- `copick export picks --filament-columns on` writes valid RELION
filament columns, and re-importing them keeps IDs, order and poses
exactly.

## Tests

`tests/test_filaments.py` on synthetic tubes (straight, along Y, arc,
crossing, ring, thick end, hollow tube, speck, blob, sliver), plus the
converter-signature, run-names and legacy-flag pinning tests. The suite
passes locally (107 tests).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
uermel pushed a commit that referenced this pull request Oct 6, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.9.0](copick-utils-v1.8.1...copick-utils-v1.9.0)
(2026-10-06)


### ✨ Features

* filament tracing (seg2fil, fil2picks, fil2seg)
([#107](#107))
([314885e](314885e))
* instance and panoptic segmentations in copick-utils
([#106](#106))
([100d838](100d838))


### 🐞 Bug Fixes

* bump chanzuckerberg/github-actions from 6.32.0 to 6.38.0
([#91](#91))
([20d8189](20d8189))
* bump chanzuckerberg/github-actions from 6.38.0 to 6.41.2
([#104](#104))
([c7ba9af](c7ba9af))
* pick tools keep identity and place particles at their centre
([#105](#105))
([8a944f0](8a944f0))

---
Merging this PR will release copick-utils on PyPI.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
uermel added a commit to copick/copick that referenced this pull request Oct 6, 2026
## Summary

The CLI reference for the copick-utils filament release
(copick/copick-utils#105 → #106 → #107, with `v2.0` twins #108 → #109 →
#110):

- New pages for `copick convert seg2fil`, `fil2picks` and `fil2seg`,
with nav entries, convert-index rows, processing-gallery cards and
before/after ChimeraX renders from the CLI gallery (`util/gallery`),
which now knows filaments and instance segmentations. The three renders
share one filament network, so each filament keeps its ID and colour
from the trace to the picks and the painted instances.
- An editorial partial for `seg2fil`: how the three commands share IDs,
which thresholds to check, and a figure of microtubules traced in CZ
cryoET Data Portal dataset 10521.
- The `convert`, `process` and `logical` pages that copick-utils
changes: typed segmentation URIs (`?instance=true`, `?panoptic=true`),
`fit-spline`, `skeletonize`, `separate-components`, `filter-components`,
`seg-stats`, `combine`, `split`, and pick tools that test the particle
centre and keep identity. They also pick up the current `--run-names`
help.
- The docs workflow installs `copick-utils>=1.10.0`: 1.9.0 shipped these
commands and 1.10.0 adds `seg2fil --curve`. The copick-utils pages here
match what `make_cli_docs` generates from 1.10.0.

Only copick-utils pages are regenerated here. Core command pages change
with the core pull requests (#479, #480, #492, #494), and pages of
plugins not installed when generating (copick-torch, copick-mcp) are
untouched.

copick-utils 1.10.0 is on PyPI, so this can merge once reviewed: the
docs build regenerates the reference from plugins installed from PyPI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)




Update: the seg2fil page also documents `--curve` (Catmull-Rom by
default, `bspline` for the exact fit), from copick-utils #116 / #117.


Also folds in the docs dependency updates from dependabot: zensical
0.0.30 → 0.0.68 (#488) and pygments 2.20.0 → 2.21.0 (#451, which
supersedes #255). The docs build passes with them.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant