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
38 changes: 32 additions & 6 deletions src/copick_utils/cli/fit_spline.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
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.cli.util import (
add_config_option,
add_debug_option,
add_run_names_option,
resolve_deprecated_option,
)
from copick.util.log import get_logger
from copick.util.uri import parse_copick_uri

Expand All @@ -18,12 +23,15 @@
@add_run_names_option
@optgroup.group("\nInput Options", help="Options related to the input segmentation.")
@add_input_option("segmentation")
# TODO:remove once deprecation takes effect -- legacy --voxel-spacing/-vs override (vs now comes from the input URI)
@optgroup.option(
"--voxel-spacing",
"-vs",
type=float,
required=True,
help="Voxel spacing for coordinate scaling.",
required=False,
default=None,
hidden=True,
help="Deprecated: the @voxel_spacing in -i/--input is used instead.",
)
@optgroup.group("\nTool Options", help="Options related to this tool.")
@optgroup.option(
Expand Down Expand Up @@ -111,14 +119,14 @@ def fit_spline(
Examples:

\b
# Fit splines to skeletonized components
# Fit splines to skeletonized components (voxel spacing from the @10.0 in -i)
copick process fit_spline -i "skeleton:skel/inst-.*@10.0" \\
-o "skeleton:spline/spline-{input_session_id}" --spacing-distance 4.4 --voxel-spacing 10.0
-o "skeleton:spline/spline-{input_session_id}" --spacing-distance 4.4

\b
# Process a single skeleton component
copick process fit_spline -i "skeleton:skel/skel-0@10.0" \\
-o "skeleton:spline/spline-0" --spacing-distance 2.0 --voxel-spacing 10.0
-o "skeleton:spline/spline-0" --spacing-distance 2.0

See Also:

Expand Down Expand Up @@ -147,6 +155,24 @@ def fit_spline(

# Extract parameters for logging only
input_params = parse_copick_uri(input_uri, "segmentation")

# Voxel spacing comes from the @voxel_spacing in the input URI; the legacy
# --voxel-spacing/-vs flag remains a deprecated override.
uri_vs_raw = input_params.get("voxel_spacing")
uri_vs = float(uri_vs_raw) if uri_vs_raw not in (None, "*") else None
# TODO:remove once deprecation takes effect -- legacy -vs override (drop the voxel_spacing param; use uri_vs directly)
voxel_spacing = resolve_deprecated_option(
uri_vs,
voxel_spacing,
old_flag="--voxel-spacing/-vs",
new_flag="the @voxel_spacing in -i/--input",
logger=logger,
)
if voxel_spacing is None:
raise click.BadParameter(
"Input URI must include a specific voxel spacing (e.g., @10.0), or pass --voxel-spacing/-vs.",
)

logger.info(f"Fitting splines to segmentations '{input_params['name']}'")
logger.info(
f"Source segmentation pattern: {input_params['name']} ({input_params['user_id']}/{input_params['session_id']})",
Expand Down
28 changes: 26 additions & 2 deletions src/copick_utils/cli/picks2mesh.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
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.cli.util import (
add_config_option,
add_debug_option,
add_run_names_option,
resolve_deprecated_option,
)
from copick.util.log import get_logger
from copick.util.uri import parse_copick_uri

Expand All @@ -26,11 +31,20 @@
@optgroup.group("\nTool Options", help="Options related to this tool.")
@optgroup.option(
"--mesh-type",
"-t",
"-mt",
type=click.Choice(["convex_hull", "alpha_shape"]),
default="convex_hull",
help="Type of mesh to create.",
)
# TODO:remove once deprecation takes effect -- legacy -t alias (reserve -t for --tomogram)
@optgroup.option(
"-t",
"legacy_mesh_type",
type=click.Choice(["convex_hull", "alpha_shape"]),
default=None,
hidden=True,
help="Deprecated: use -mt/--mesh-type instead.",
)
@optgroup.option(
"--alpha",
"-a",
Expand All @@ -54,6 +68,7 @@ def picks2mesh(
run_names,
input_uri,
mesh_type,
legacy_mesh_type,
alpha,
use_clustering,
clustering_method,
Expand Down Expand Up @@ -114,6 +129,15 @@ def picks2mesh(

logger = get_logger(__name__, debug=debug)

# TODO:remove once deprecation takes effect -- legacy -t alias resolution (also drop the legacy_mesh_type param)
mesh_type = resolve_deprecated_option(
mesh_type,
legacy_mesh_type,
old_flag="-t",
new_flag="-mt/--mesh-type",
logger=logger,
)

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

Expand Down
33 changes: 30 additions & 3 deletions src/copick_utils/cli/thickness_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
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.cli.util import (
add_config_option,
add_debug_option,
add_run_names_option,
resolve_deprecated_option,
)
from copick.util.log import get_logger
from copick.util.uri import parse_copick_uri

Expand All @@ -23,11 +28,21 @@
@optgroup.group("\nTool Options", help="Options related to this tool.")
@optgroup.option(
"--min-thickness",
"-t",
"-mt",
type=float,
required=True,
required=False,
default=None,
help="Minimum thickness (diameter) of structures to keep. Unit set by --thickness-unit.",
)
# TODO:remove once deprecation takes effect -- legacy -t alias (reserve -t for --tomogram); also restore --min-thickness required=True
@optgroup.option(
"-t",
"legacy_min_thickness",
type=float,
default=None,
hidden=True,
help="Deprecated: use -mt/--min-thickness instead.",
)
@optgroup.option(
"--thickness-unit",
type=click.Choice(["angstrom", "voxel"]),
Expand All @@ -43,6 +58,7 @@ def thickness_filter(
run_names,
input_uri,
min_thickness,
legacy_min_thickness,
thickness_unit,
workers,
output_uri,
Expand Down Expand Up @@ -88,6 +104,17 @@ def thickness_filter(

logger = get_logger(__name__, debug=debug)

# TODO:remove once deprecation takes effect -- legacy -t alias resolution (drop legacy_min_thickness + None-check)
min_thickness = resolve_deprecated_option(
min_thickness,
legacy_min_thickness,
old_flag="-t",
new_flag="-mt/--min-thickness",
logger=logger,
)
if min_thickness is None:
raise click.BadParameter("Missing required option: -mt/--min-thickness.")

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

Expand Down
35 changes: 24 additions & 11 deletions src/copick_utils/cli/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,17 +335,20 @@ def add_distance_options(func: click.Command) -> click.Command:
# ============================================================================


def add_input_option(object_type: str, func: click.Command = None) -> Callable:
def add_input_option(object_type: str, func: click.Command = None, required: bool = True) -> Callable:
"""
Add --input/-i option for URI-based input selection.

Supports copick URI format with pattern matching:
- Picks/Meshes: object_name:user_id/session_id
- Segmentations: name:user_id/session_id@voxel_spacing?multilabel=true
- Tomograms: tomo_type@voxel_spacing

Args:
object_type (str): Type of object ('picks', 'mesh', 'segmentation').
object_type (str): Type of object ('picks', 'mesh', 'segmentation', 'tomogram').
func (click.Command, optional): The Click command to which the option will be added.
required (bool): Whether the option is required. Default is True. Pass False for
commands that keep deprecated fallback flags (e.g. --tomo-alg/--voxel-size).

Returns:
Callable: The Click command with the input option added.
Expand All @@ -358,6 +361,7 @@ def add_input_option_decorator(_func: click.Command) -> click.Command:
"picks": "object_name:user_id/session_id",
"mesh": "object_name:user_id/session_id",
"segmentation": "name:user_id/session_id@voxel_spacing",
"tomogram": "tomo_type@voxel_spacing",
}

help_text = (
Expand All @@ -369,7 +373,7 @@ def add_input_option_decorator(_func: click.Command) -> click.Command:
"-i",
"input_uri",
type=CopickURI(object_type, "input"),
required=True,
required=required,
help=help_text,
)
return opt(_func)
Expand All @@ -380,7 +384,12 @@ def add_input_option_decorator(_func: click.Command) -> click.Command:
return add_input_option_decorator(func)


def add_output_option(object_type: str, func: click.Command = None, default_tool: str = None) -> Callable:
def add_output_option(
object_type: str,
func: click.Command = None,
default_tool: str = None,
required: bool = True,
) -> Callable:
"""
Add --output/-o option for URI-based output specification with smart defaults.

Expand Down Expand Up @@ -411,21 +420,25 @@ def add_output_option_decorator(_func: click.Command) -> click.Command:
"picks": '"ribosome", "ribosome/my-session", or "/my-session"',
"mesh": '"membrane", "membrane/my-session", or "/my-session"',
"segmentation": '"membrane", "membrane/my-session", or "/my-session"',
"tomogram": '"wbp@20.0" or "denoised@10.0"',
}

voxel_suffix = "@voxel_spacing" if object_type == "segmentation" else ""
help_text = (
f"Output {object_type} URI. "
f"Supports smart defaults (e.g., {shorthand_examples.get(object_type, 'shorthand')}). "
f"Full format: object_name:user_id/session_id{voxel_suffix}."
)
voxel_suffix = "@voxel_spacing" if object_type in ("segmentation", "tomogram") else ""
if object_type == "tomogram":
help_text = f"Output tomogram URI (format: tomo_type{voxel_suffix}). Example: 'wbp@20.0'."
else:
help_text = (
f"Output {object_type} URI. "
f"Supports smart defaults (e.g., {shorthand_examples.get(object_type, 'shorthand')}). "
f"Full format: object_name:user_id/session_id{voxel_suffix}."
)

opt = optgroup.option(
"--output",
"-o",
"output_uri",
type=CopickURI(object_type, "output"),
required=True,
required=required,
help=help_text,
)
return opt(_func)
Expand Down
57 changes: 57 additions & 0 deletions tests/test_flag_normalization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Tests for the Part 2 CLI flag normalization in copick-utils.

- picks2mesh / thickness_filter: `-t` is reserved for tomogram refs; their old
`-t` short flag moved to `-mt` with a hidden `-t` deprecated alias.
- fit_spline: voxel spacing now comes from the input URI's `@voxel_spacing`; the
`--voxel-spacing/-vs` flag is a hidden deprecated override.
"""

import pytest
from click.testing import CliRunner


@pytest.fixture
def runner():
return CliRunner()


def _hidden_alias(cmd, flag):
param = next((p for p in cmd.params if flag in getattr(p, "opts", [])), None)
return param, (param.hidden if param else None)


def test_picks2mesh_reserves_t_for_tomogram(runner):
from copick_utils.cli.picks2mesh import picks2mesh

out = runner.invoke(picks2mesh, ["--help"]).output
assert "--mesh-type" in out and "-mt" in out
assert "-t " not in out # -t no longer shown (reserved for --tomogram)

param, hidden = _hidden_alias(picks2mesh, "-t")
assert param is not None and hidden is True
assert param.name == "legacy_mesh_type"


def test_thickness_filter_reserves_t_for_tomogram(runner):
from copick_utils.cli.thickness_filter import thickness_filter

out = runner.invoke(thickness_filter, ["--help"]).output
assert "--min-thickness" in out and "-mt" in out
assert "-t " not in out

param, hidden = _hidden_alias(thickness_filter, "-t")
assert param is not None and hidden is True
assert param.name == "legacy_min_thickness"


def test_fit_spline_derives_vs_from_uri(runner):
from copick_utils.cli.fit_spline import fit_spline

out = runner.invoke(fit_spline, ["--help"]).output
assert "-i" in out and "--input" in out
# voxel spacing is derived from the input URI; the flag is hidden + deprecated
assert "--voxel-spacing" not in out
assert "-vs" not in out

param, hidden = _hidden_alias(fit_spline, "--voxel-spacing")
assert param is not None and hidden is True
Loading