Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
784adc2
Transform labels instead of images
joshua-gould Jun 9, 2026
5a00ffe
Merge branch 'main' of https://github.com/Genentech/scallops into wor…
joshua-gould Jun 9, 2026
0fb20ab
Transform labels instead of images
joshua-gould Jun 9, 2026
92a4b35
Transform labels instead of images
joshua-gould Jun 9, 2026
fd1fa64
Transform labels instead of images
joshua-gould Jun 9, 2026
e6f66ab
Transform labels instead of images
joshua-gould Jun 9, 2026
ce6634f
registration transform across times
joshua-gould Jun 11, 2026
cc23675
Registration transform across times
joshua-gould Jun 12, 2026
e524243
Registration transform across times
joshua-gould Jun 12, 2026
6fc0e55
storage options
joshua-gould Jun 12, 2026
baa48d3
pattern
joshua-gould Jun 12, 2026
a6cdfb7
handle timepoint in segmentation
joshua-gould Jun 15, 2026
c9db0a2
handle timepoint in segmentation
joshua-gould Jun 15, 2026
3815857
handle timepoint in segmentation
joshua-gould Jun 15, 2026
3cea7ec
handle timepoint in segmentation
joshua-gould Jun 16, 2026
6778f6a
Merge branch 'main' of https://github.com/Genentech/scallops into wor…
joshua-gould Jun 16, 2026
13a32ca
Segmentation time
joshua-gould Jun 16, 2026
33ce260
Segmentation time
joshua-gould Jun 16, 2026
5f88d47
Segmentation time
joshua-gould Jun 16, 2026
970a5bd
Segmentation time
joshua-gould Jun 16, 2026
eda1e38
Segmentation time
joshua-gould Jun 17, 2026
6a7cd74
Segmentation time
joshua-gould Jun 17, 2026
c376bcc
Segmentation time
joshua-gould Jun 18, 2026
f70e984
Merge branch 'main' of https://github.com/Genentech/scallops into wor…
joshua-gould Jun 22, 2026
0141e89
Merge branch 'main' of https://github.com/Genentech/scallops into wor…
joshua-gould Jun 24, 2026
166a8b5
read merged
joshua-gould Jun 25, 2026
24d84f1
read merged
joshua-gould Jun 25, 2026
e54d030
features by t
joshua-gould Jun 26, 2026
38c5ea7
features by t
joshua-gould Jun 29, 2026
3d448fb
features by t
joshua-gould Jun 29, 2026
622de5d
features by t
joshua-gould Jun 29, 2026
d2eabbe
features by t
joshua-gould Jun 29, 2026
8cc4561
features by t
joshua-gould Jun 29, 2026
f00406b
features by t
joshua-gould Jun 29, 2026
942d4fd
features by t
joshua-gould Jun 29, 2026
968219f
features by t
joshua-gould Jun 29, 2026
d1f5802
features by t
joshua-gould Jun 30, 2026
b99c110
features by t
joshua-gould Jun 30, 2026
e38302d
features by t
joshua-gould Jun 30, 2026
216f43d
features by t
joshua-gould Jun 30, 2026
bca1ac4
features by t
joshua-gould Jun 30, 2026
9f60bc7
segment t
joshua-gould Jul 1, 2026
dee7e1c
Merge branch 'main' of https://github.com/Genentech/scallops into wor…
joshua-gould Jul 1, 2026
19cb963
added suffix
joshua-gould Jul 1, 2026
6713cd5
features by t
joshua-gould Jul 1, 2026
b9e10c7
features by t
joshua-gould Jul 1, 2026
6b9baa9
features by t
joshua-gould Jul 1, 2026
498f063
Merge branch 'main' of https://github.com/Genentech/scallops into wor…
joshua-gould Jul 1, 2026
829fd5e
features by t
joshua-gould Jul 1, 2026
7d02c5b
features by t
joshua-gould Jul 1, 2026
630b7d8
features by t
joshua-gould Jul 2, 2026
04838ba
features by t
joshua-gould Jul 6, 2026
9f7b21e
features by t
joshua-gould Jul 6, 2026
678ae4a
features by t
joshua-gould Jul 6, 2026
a815cca
features by t
joshua-gould Jul 7, 2026
18e9121
features by t
joshua-gould Jul 7, 2026
b387d71
features by t
joshua-gould Jul 7, 2026
a0aafc1
features by t
joshua-gould Jul 7, 2026
ae5fab3
features by t
joshua-gould Jul 7, 2026
9f6cf22
features by t
joshua-gould Jul 7, 2026
09b8808
features by t
joshua-gould Jul 7, 2026
007debc
features by t
joshua-gould Jul 7, 2026
4ab58cc
features by t
joshua-gould Jul 7, 2026
f0ce208
features by t
joshua-gould Jul 7, 2026
9571b94
features by t
joshua-gould Jul 7, 2026
cf4beff
features by t
joshua-gould Jul 7, 2026
816ebc9
features by t
joshua-gould Jul 7, 2026
9de85bd
features by t
joshua-gould Jul 7, 2026
f464658
features by t
joshua-gould Jul 7, 2026
66b04f5
features by t
joshua-gould Jul 7, 2026
dd7ef4b
features by t
joshua-gould Jul 7, 2026
f9a50a8
features by t
joshua-gould Jul 7, 2026
6d5f788
features by t
joshua-gould Jul 7, 2026
62eb1aa
features by t
joshua-gould Jul 7, 2026
3ff87fa
features by t
joshua-gould Jul 7, 2026
aa7afe0
features by t
joshua-gould Jul 7, 2026
2cee938
Merge branch 'main' of https://github.com/Genentech/scallops into wor…
joshua-gould Jul 8, 2026
3806e85
features by t
joshua-gould Jul 8, 2026
868819c
features by t
joshua-gould Jul 8, 2026
5ac1301
features by t
joshua-gould Jul 9, 2026
c4aa449
Don't convert time to int
joshua-gould Jul 9, 2026
9f2e54a
Merge branch 'main' of https://github.com/Genentech/scallops into wor…
joshua-gould Jul 9, 2026
af6a4d4
features by t
joshua-gould Jul 9, 2026
e093476
features by t
joshua-gould Jul 9, 2026
52c99d8
features by t
joshua-gould Jul 9, 2026
ccf6d55
features by t
joshua-gould Jul 10, 2026
ad4f13b
features by t
joshua-gould Jul 10, 2026
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
79 changes: 53 additions & 26 deletions scallops/cli/extract_crops.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
from typing import Literal

import dask.array as da
Expand All @@ -8,9 +9,13 @@
import pyarrow.parquet as pq
from array_api_compat import get_namespace
from skimage.util import img_as_ubyte
from zarr import Group

from scallops.cli.features import _read_merged_or_objects, get_labels
from scallops.cli.features import (
_find_labels,
_image_key_without_time_and_selected_time,
_read_merged_or_objects,
)
from scallops.cli.find_objects import get_path
from scallops.cli.util import (
_get_cli_logger,
cli_metadata,
Expand Down Expand Up @@ -39,11 +44,10 @@ def single_crop(
group: str, # NOT USED
file_list: list[str],
metadata: dict,
labels_group: Group | None,
label_paths: list[str] | None,
output_dir: str,
output_sep: str,
merge_dir: str,
merge_dir_sep: str,
merge_paths: list[str],
crop_size: tuple[int, int],
output_format: Literal["tiff", "npy"],
label_name: str,
Expand All @@ -57,38 +61,61 @@ def single_crop(
force: bool,
):
image_key = metadata["id"]

output_dir = f"{output_dir}{output_sep}{label_name}{output_sep}{image_key}"
image_key_without_t, selected_timepoint = _image_key_without_time_and_selected_time(
metadata
)

output_parquet_path = f"{output_dir}.parquet"
output_parquet_path = get_path(
output_dir,
output_sep,
label_name,
image_key_without_t if image_key_without_t is not None else image_key,
selected_timepoint,
".parquet",
)
if not force and is_parquet_file(output_parquet_path):
logger.info(f"Skipping features for {image_key} {label_name}")
return

output_fs, _ = fsspec.core.url_to_fs(output_dir)
output_fs.makedirs(output_dir, exist_ok=True)
image = _images2fov(file_list, metadata, dask=True).squeeze().data
logger.info(f"{image_key} image shape {image.shape}")
label_image = None
if labels_group is not None:
zarr_labels = get_labels(
labels_group=labels_group,
name=image_key,
suffix=label_name, # e.g. nuclei
)

if zarr_labels is None:
raise ValueError(f"Unable to read {label_name} labels for {image_key}.")
label_image = da.from_zarr(zarr_labels)
merged_df = _read_merged_or_objects(
merge_dir=merge_dir,
merge_dir_sep=merge_dir_sep,
paths=merge_paths,
timepoint=selected_timepoint,
label_name=label_name,
image_key=image_key,
image_key_without_t=image_key_without_t,
label_filter=label_filter,
add_timepoint_suffix=False,
)
if merged_df is None:
raise ValueError(f"Unable to read merged data for {image_key}.")
output_fs, _ = fsspec.core.url_to_fs(output_dir)
output_fs.makedirs(output_dir, exist_ok=True)
image = _images2fov(file_list, metadata, dask=True).squeeze().data
logger.info(f"{image_key} image shape {image.shape}")
label_image = None
if label_paths is not None:
g, timepoints = _find_labels(
label_paths=label_paths,
image_key=image_key,
label_name=label_name,
image_key_without_t=image_key_without_t,
selected_timepoint=selected_timepoint,
)
if g is None:
raise ValueError(f"No labels found for {image_key}")

if len(timepoints) != 1:
raise ValueError(f"More than one timepoint found for {image_key}")
label_image = da.from_array(g[list(g.keys())[0]])
timepoint = timepoints[0]
if timepoint is not None and label_image.ndim == 3:
index = -1
for i in range(len(timepoints)):
if str(timepoints[i]) == str(timepoint):
index = i
break
label_image = label_image[index]

n_labels_before_filtering = len(merged_df)
if label_filter is not None:
merged_df = merged_df.query(label_filter)
Expand Down Expand Up @@ -152,7 +179,6 @@ def single_crop(
)

output_metadata = cli_metadata() if not no_version else dict()

table = pa.Table.from_pandas(merged_df, preserve_index=True)
table = table.replace_schema_metadata(
{
Expand All @@ -162,6 +188,7 @@ def single_crop(
)

fs, output_parquet_path = fsspec.url_to_fs(output_parquet_path)
fs.mkdirs(os.path.dirname(output_parquet_path.rstrip(fs.sep)), exist_ok=True)
pq.write_table(
table,
output_parquet_path,
Expand Down
33 changes: 13 additions & 20 deletions scallops/cli/extract_crops_main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import argparse

import fsspec
import zarr
from dask.bag import from_sequence

from scallops.cli.arg_parser import _sort_groups
Expand Down Expand Up @@ -39,7 +38,7 @@ def run_pipeline_extract_crops(arguments: argparse.Namespace):

image_patterns = arguments.image_pattern
output_dir = arguments.output
merge_dir = arguments.merge
merge_paths = arguments.merge
subset = arguments.subset
force = arguments.force
groupby = arguments.groupby
Expand Down Expand Up @@ -71,24 +70,17 @@ def run_pipeline_extract_crops(arguments: argparse.Namespace):
if dask_server_url is None and arguments.dask_cluster is None:
dask_cluster_parameters = _dask_workers_threads()

merge_dir_sep = None
if merge_dir is not None:
merge_dir_sep = fsspec.core.url_to_fs(merge_dir)[0].sep
merge_dir = merge_dir.rstrip(merge_dir_sep)

output_fs, _ = fsspec.core.url_to_fs(output_dir)
output_dir = output_dir.rstrip(output_fs.sep)
output_fs.mkdirs(output_dir, exist_ok=True)

labels_path = arguments.labels
label_paths = arguments.labels
no_version = arguments.no_version
labels_group = None

if not mask:
labels_path = None
if labels_path is None and mask:
label_paths = None
if label_paths is None and mask:
raise ValueError("Labels must be provided when `mask` is true.")
if labels_path is not None:
label_root = zarr.open(labels_path, mode="r")
labels_group = label_root["labels"]

image_seq = from_sequence(
_set_up_experiment(
Expand All @@ -109,9 +101,8 @@ def run_pipeline_extract_crops(arguments: argparse.Namespace):
single_crop,
output_dir=output_dir,
output_sep=output_fs.sep,
merge_dir=merge_dir,
merge_dir_sep=merge_dir_sep,
labels_group=labels_group,
merge_paths=merge_paths,
label_paths=label_paths,
label_filter=label_filter,
label_name=label_name,
percentile_normalize=percentile_normalize,
Expand Down Expand Up @@ -145,22 +136,24 @@ def _create_parser(subparsers: argparse.ArgumentParser, default_help: bool) -> N

required.add_argument(
"--merge",
help="Path to directory containing output from `merge`",
nargs="+",
help="Path(s) to directory containing output from `find-objects`, `merge`, or `features`",
)
parser.add_argument(
"--labels",
dest="labels",
nargs="*",
help="Path to zarr directory containing labels. Required when `mask` is true",
)
parser.add_argument(
"--mask",
action="store_true",
help="Set pixels not belonging to target label to zero",
help="Set crop pixels not belonging to target label to zero",
)
parser.add_argument(
"--label-name",
help="Name of labels to use. For example `nuclei` or `cell`",
default="cell",
default="nuclei",
)
parser.add_argument(
"--crop-size",
Expand Down
Loading