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
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ picks2sphere = "copick_utils.cli.conversion_commands:picks2sphere"
picks2ellipsoid = "copick_utils.cli.conversion_commands:picks2ellipsoid"
picks2plane = "copick_utils.cli.conversion_commands:picks2plane"
picks2surface = "copick_utils.cli.conversion_commands:picks2surface"
seg2fil = "copick_utils.cli.conversion_commands:seg2fil"
fil2picks = "copick_utils.cli.conversion_commands:fil2picks"
fil2seg = "copick_utils.cli.conversion_commands:fil2seg"

[project.entry-points."copick.process.commands"]
separate_components = "copick_utils.cli.processing_commands:separate_components"
Expand Down
7 changes: 7 additions & 0 deletions src/copick_utils/cli/conversion_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
This module imports all conversion commands from specialized files for better organization.
"""

from copick_utils.cli.fil2picks import fil2picks
from copick_utils.cli.fil2seg import fil2seg
from copick_utils.cli.mesh2caps import mesh2caps
from copick_utils.cli.mesh2picks import mesh2picks
from copick_utils.cli.mesh2seg import mesh2seg
Expand All @@ -12,6 +14,7 @@
from copick_utils.cli.picks2seg import picks2seg
from copick_utils.cli.picks2sphere import picks2sphere
from copick_utils.cli.picks2surface import picks2surface
from copick_utils.cli.seg2fil import seg2fil
from copick_utils.cli.seg2mesh import seg2mesh
from copick_utils.cli.seg2picks import seg2picks

Expand All @@ -32,4 +35,8 @@
"seg2picks",
"mesh2seg",
"seg2mesh",
# Filament commands
"seg2fil",
"fil2picks",
"fil2seg",
]
135 changes: 135 additions & 0 deletions src/copick_utils/cli/fil2picks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"""CLI command for sampling picks along filaments."""

import click
import copick
from click_option_group import optgroup
from copick.cli.util import add_config_option, add_debug_option, add_run_names_option
from copick.util.log import get_logger
from copick.util.uri import parse_copick_uri

from copick_utils.cli.util import (
add_filament_sampling_options,
add_input_option,
add_output_option,
add_workers_option,
)
from copick_utils.util.config_models import create_simple_config


@click.command(
context_settings={"show_default": True},
short_help="Sample picks along filaments.",
no_args_is_help=True,
)
@add_config_option
@add_run_names_option
@optgroup.group("\nInput Options", help="Options related to the input filaments.")
@add_input_option("filaments")
@optgroup.group("\nTool Options", help="Options related to this tool.")
@add_filament_sampling_options
@add_workers_option
@optgroup.group("\nOutput Options", help="Options related to the output picks.")
@add_output_option("picks", default_tool="fil2picks")
@add_debug_option
def fil2picks(
config,
run_names,
input_uri,
spacing,
anchor,
roll,
seed,
length_unit,
workers,
output_uri,
debug,
):
"""
Sample picks along filaments.

Places picks along each filament at a fixed `--spacing`, measured along the filament's
curve. Each pick's orientation has its +Z axis along the filament, in the filament's point
order, and its rotation about that axis follows `--roll`. Every pick carries its filament's
ID as its instance ID and the filament's score, and the picks are written filament by
filament, in order along each one. Exported to RELION with
`copick export picks --filament-columns on`, they carry RELION's filament conventions.

A filament's stored curve is used when it still matches the filament: the B-spline that
`copick convert seg2fil` fitted, or control points from an editor. A filament without one is
sampled along a smooth curve derived from its points. Sampling is cheap and does not change
the filaments, so different spacings can be tried without tracing again.

URI Format:

\b
Filaments: object_name:user_id/session_id
Picks: object_name:user_id/session_id

Examples:

\b
# Picks every 82 Å (one tubulin dimer), in the filaments' own session
copick convert fil2picks -i "microtubule:trace/1" -o "microtubule:trace/1" --spacing 82

\b
# Every 4 nm with a random rotation about the axis
copick convert fil2picks -i "microtubule:trace/1" -o "microtubule:picks/4nm" --spacing 40 --roll random --seed 1

See Also:

\b
copick convert seg2fil: trace filaments in a segmentation
copick convert fil2seg: paint tubes around filaments into an instance segmentation
copick export picks: export picks to RELION with --filament-columns
"""
from copick_utils.converters.picks_from_filaments import picks_from_filaments_lazy_batch

logger = get_logger(__name__, debug=debug)

root = copick.from_file(config)
run_names_list = list(run_names) if run_names else None

try:
task_config = create_simple_config(
input_uri=input_uri,
input_type="filaments",
output_uri=output_uri,
output_type="picks",
command_name="fil2picks",
)
except ValueError as e:
raise click.BadParameter(str(e)) from e

input_params = parse_copick_uri(input_uri, "filaments")
logger.info(f"Sampling filaments '{input_params['object_name']}' every {spacing} ({length_unit})")

results = picks_from_filaments_lazy_batch(
root=root,
config=task_config,
run_names=run_names_list,
workers=workers,
spacing=spacing,
anchor=anchor,
roll=roll,
seed=seed,
length_unit=length_unit,
)

successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
filaments = sum(result.get("filaments_sampled", 0) for result in results.values() if result)
points = sum(result.get("points_created", 0) for result in results.values() if result)

all_errors = []
for result in results.values():
if result and result.get("errors"):
all_errors.extend(result["errors"])

logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
logger.info(f"Total: {points} picks from {filaments} filaments")

if all_errors:
logger.warning(f"Encountered {len(all_errors)} errors during processing")
for error in all_errors[:5]:
logger.warning(f" - {error}")
if len(all_errors) > 5:
logger.warning(f" ... and {len(all_errors) - 5} more errors")
139 changes: 139 additions & 0 deletions src/copick_utils/cli/fil2seg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""CLI command for painting tubes around filaments into an instance segmentation."""

import click
import copick
from click_option_group import optgroup
from copick.cli.util import add_config_option, add_debug_option, add_run_names_option
from copick.util.log import get_logger
from copick.util.uri import parse_copick_uri

from copick_utils.cli.util import add_input_option, add_output_option, add_workers_option
from copick_utils.util.config_models import create_simple_config


@click.command(
context_settings={"show_default": True},
short_help="Paint tubes around filaments into an instance segmentation.",
no_args_is_help=True,
)
@add_config_option
@add_run_names_option
@optgroup.group("\nInput Options", help="Options related to the input filaments.")
@add_input_option("filaments")
@optgroup.group("\nTool Options", help="Options related to this tool.")
@optgroup.option(
"--radius",
type=float,
default=None,
help="Tube radius in angstroms for filaments without their own radius. Unset: the object's radius.",
)
@optgroup.option(
"--tomo-type",
"-tt",
default="wbp",
help="Type of tomogram whose shape the segmentation takes.",
)
@add_workers_option
@optgroup.group("\nOutput Options", help="Options related to the output segmentation.")
@add_output_option("segmentation", default_tool="fil2seg")
@add_debug_option
def fil2seg(
config,
run_names,
input_uri,
radius,
tomo_type,
workers,
output_uri,
debug,
):
"""
Paint tubes around filaments into an instance segmentation.

Each filament becomes a tube around its centreline, labelled with the filament's ID, so the
result is an instance segmentation with the same IDs as the filaments and their picks.
A filament's tube radius is its own radius (`copick convert seg2fil` records the radius of
the segmentation it traced), otherwise `--radius`, otherwise the object's radius. Where
tubes overlap, each voxel goes to the nearest centreline. The segmentation has the shape of
the run's `--tomo-type` tomogram at the voxel spacing given in the output URI.

Useful for filaments drawn or edited by hand, or imported, that have no segmentation yet:
for example as per-filament masks, or as input to `copick convert seg2fil`.

URI Format:

\b
Filaments: object_name:user_id/session_id
Segmentations: name:user_id/session_id@voxel_spacing?instance=true

Examples:

\b
# Tubes of each filament's own radius, at 10 Å
copick convert fil2seg -i "microtubule:manual/1" -o "microtubule:tubes/1@10.0?instance=true"

\b
# 125 Å tubes against a denoised tomogram
copick convert fil2seg -i "microtubule:manual/1" -o "microtubule:tubes/1@10.0?instance=true" \\
--radius 125 --tomo-type denoised

See Also:

\b
copick convert seg2fil: trace filaments in a segmentation
copick convert fil2picks: sample picks along filaments
copick convert picks2seg: paint spheres around picks
"""
from copick_utils.converters.segmentation_from_filaments import segmentation_from_filaments_lazy_batch

logger = get_logger(__name__, debug=debug)

root = copick.from_file(config)
run_names_list = list(run_names) if run_names else None

output_params = parse_copick_uri(output_uri, "segmentation")
if output_params.get("multilabel") or output_params.get("panoptic"):
raise click.BadParameter("fil2seg writes an instance segmentation (?instance=true).")
if not output_params.get("instance"):
output_uri = output_uri + ("&" if "?" in output_uri else "?") + "instance=true"

try:
task_config = create_simple_config(
input_uri=input_uri,
input_type="filaments",
output_uri=output_uri,
output_type="segmentation",
command_name="fil2seg",
)
except ValueError as e:
raise click.BadParameter(str(e)) from e

input_params = parse_copick_uri(input_uri, "filaments")
logger.info(f"Painting filaments '{input_params['object_name']}' into an instance segmentation")

results = segmentation_from_filaments_lazy_batch(
root=root,
config=task_config,
run_names=run_names_list,
workers=workers,
radius=radius,
tomo_type=tomo_type,
)

successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
filaments = sum(result.get("filaments_painted", 0) for result in results.values() if result)

all_errors = []
for result in results.values():
if result and result.get("errors"):
all_errors.extend(result["errors"])

logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
logger.info(f"Total filaments painted: {filaments}")

if all_errors:
logger.warning(f"Encountered {len(all_errors)} errors during processing")
for error in all_errors[:5]:
logger.warning(f" - {error}")
if len(all_errors) > 5:
logger.warning(f" ... and {len(all_errors) - 5} more errors")
Loading
Loading