Secondary object - #171
Open
akcd1 wants to merge 49 commits into
Open
Conversation
Collaborator
|
@akcd1 the tests are failing on something that suggests that you haven't properly rebased main. I would remerge asap. |
akcd1
force-pushed
the
secondary_object
branch
from
December 8, 2025 18:02
080a8ec to
77c2550
Compare
Collaborator
Author
|
re-based |
akcd1
force-pushed
the
secondary_object
branch
from
January 30, 2026 15:47
06e6390 to
33fb995
Compare
mat10d
marked this pull request as draft
March 30, 2026 17:07
…nnel_index elsewhere
* conditional cytoplasm mask * initial commit, refactoring second obj segmentation * added thresholding options * added visualization of thresholded output * ruff check and format * adds helper visualization function * snakemake integration * updated parameters and added scaffold for ML-based segmentation * ruff check and format * code consolidation * improved documentation * code consolidation and reformatting * remove foci channel from second_obj pheno extraction, rename foci_channel_index elsewhere * fixed bug foci_channel param passing * fixed import of second obj features * conditional cytoplasm mask * Integrated secondary object segmentation with ml models * ruff format * disabled secondary object detection for test data * consistent naming with config and notebook * fixed ML parameter passing in script identify_second_objs.py * fixed general config parameters --------- Co-authored-by: edeny <edeny@fry.wi.mit.edu> Co-authored-by: acepedadiaz <acepedadiaz@fry.wi.mit.edu>
* adds fine-tuning of Cellpose, code consolidation * optimization of model performance calculations * added image normalization for improved alignment performance * updated to _cp nomenclature * update to merge_phenotype_cp * added intermediate tsv reporting alignment offsets and confidence * added intermediate tsv reporting alignment offsets and confidence * removed params for percentile normalization * added alignment metrics * removed percentile normalization, added reporting, normalization bug fixed * minor formatting * added alignment metrics * minor formatting * added alignment metrics --------- Co-authored-by: acepedadiaz <acepedadiaz@fry.wi.mit.edu> Co-authored-by: acepedadiaz <acepedadiaz@c13b1.wi.mit.edu>
Route per-tile offset_y/offset_x from alignment_metrics TSV through extract_phenotype_cp so they flow naturally through merge_phenotype_cp into final_merge with zero merge module changes. - Add alignment metrics TSV as 5th input to extract_phenotype_cp rule - Broadcast offset columns to each cell row in extract_phenotype.py - Fix stitch_phenotype input to use align_phenotype[0] (image only) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce aggregate_cells_second_objs rule between final_merge and split_datasets with 4 strategies for handling one-to-many cell-object relationships: "none", "single", "all", "average". - New second_obj_utils.py with strategy dispatch and merge logic - New aggregate_cells_second_objs.py snakemake script - Conditional DAG: uses aggregate output when second_obj_detection is enabled, falls back to final_merge otherwise - Add cell_summary columns to metadata cols so they are excluded from feature aggregation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Test images have no secondary objects. Set second_obj_detection: false in test config and remove secondary object metadata columns from cell_data_metadata_cols.tsv since they won't exist in test data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- merge_phenotype rule now falls back to extract_phenotype_cp when second_obj_detection is disabled, fixing MissingInputException in CI - Add r-prefix to docstring containing backslashes in segment_cellpose.py Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Filter aggregate_cells_second_objs from targets/mappings when second_obj_detection is disabled, matching the pattern in phenotype targets. Fixes MissingInputException in CI tests. Also add period to cellpose_training.py docstring to satisfy ruff D415. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
calculate_offsets was changed to return (offsets, errors) but three call sites in align_cycles.py were not updated to destructure the tuple, causing ValueError in apply_offsets when offset elements were arrays instead of scalars. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The script was updated to load alignment_metrics from snakemake.input[1], but the SBS extract_sbs_info rule only provides one input (nuclei tiff). The phenotype extract_phenotype_info rule provides both. Guard the metrics loading with a length check so the script works for both callers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ary_object (joint scrubbed) (#222) * feat(aggregate): add compartment_combo_subset to filter feature columns by compartment * feat(aggregate): add resolve_aggregate_combos validation helper * feat(shared): register compartment_combo filename prefix (CmCo-) * feat(aggregate): add compartment_combo wildcard to aggregate output paths * feat(aggregate): add compartment_combo wildcard to aggregate rules * feat(shared): add compartment_combo to bootstrap rule_utils helpers * feat(cluster): add compartment_combo wildcard to cluster paths and rules Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(shared): propagate compartment_combo through metrics, targets, cluster_eval * feat(aggregate): apply compartment_combo_subset in split_datasets and read wildcard in bootstrap_gene * test(small_test_analysis): add compartment_combo to aggregate fixtures * test(small_test_analysis): add compartment_combo to cluster_combo fixture * fix(aggregate): preserve int64 plate dtype in aggregate_cells_second_objs The script cast second_objs_df's plate/well to str, then _aggregate_single merged that against cells_df (plate as int64) and failed with: ValueError: You are trying to merge on int64 and object columns for key 'plate' Both merge_final.parquet and phenotype_second_objs.parquet have plate as int64; the str coercion was unnecessary. Compare plate as int and leave well as its natural string form. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(aggregate): use known compartment prefixes as drop universe all_compartments_run was derived from the compartments present in the TSV. Compartments never requested in any combo (e.g. cytoplasm when all combos only use cell/nucleus/second_obj) were absent from the universe, so compartment_combo_subset never added them to `excluded` and their feature columns leaked through into every filtered parquet. Source the universe from COMPARTMENT_PREFIXES so undeclared compartments get dropped instead of passed through. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(aggregate): make montage generation opt-out via generate_montages Read config["aggregate"]["generate_montages"] (default True) in targets/aggregate.smk and evaluate MONTAGE_TARGETS_ALL to an empty list when False. This lets users skip prepare_montage_data, generate_montage, and initiate_montage when montages aren't wanted, without needing to invoke a different snakemake target. Default preserves existing behavior for configs that don't set the flag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(aggregate): harmonize pool schema at align + generate_feature_table Per-well missing_values_filter drops different columns in different wells when class composition varies (e.g. Uninfected-only wells vs mixed Bystander+Infected wells). Downstream pooling via pyarrow's dataset union then fills those cols with NaN across whole wells, which breaks centerscale_by_batch (zero-variance batch) and PCA (NaN input). Add harmonize_pool_schema(paths, metadata_cols, drop_cols_threshold) in lib/aggregate/filter.py that: 1. Intersects per-file schemas. Cols present in some but not all files are dropped up front and logged per-file so the operator can see which wells lacked which columns (the "missing class in a well" case the user explicitly wants to tolerate). 2. Applies drop_cols_threshold at the pool level on the intersected col set, matching the convention from missing_values_filter. Wire into align.py and generate_feature_table.py: call the helper once to produce a canonical metadata+feature col set, pass it as `columns=` to every ds.dataset scan (PCA sample, class read, batched transform, feature-table batch loop). Remove the per-batch .dropna(axis=1) in align.py's batch loop (redundant; could previously drop inconsistent col sets across batches and break PCA transform). Add defensive per-subset row-drop on residual NaN. Pass config["aggregate"]["drop_cols_threshold"] through as a rule param to both rules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(aggregate): plumb perturbation columns through perturbation_score perturbation_score.py hardcoded "gene_symbol_0", "nontargeting", and "cell_barcode_0" across prepare_alignment_data, centerscale_on_controls, and calculate_perturbation_scores — so any config where perturbation_name_col != "gene_symbol_0" ran the scorer on mismatched columns and either produced silently-wrong scores or blew up. Under pandas nullable-string dtypes, (gene_symbol_0 == <sgRNA barcode>) returned a BooleanArray with pd.NA, and .astype(int) collapsed it to {0, 1, <NA>} — which LogisticRegression(solver="liblinear") then rejected as n_classes >= 3. Plumb perturbation_name_col, perturbation_id_col, control_key, control_name_col, and batch_cols through perturbation_score and _process_gene_subset from the caller. calculate_perturbation_scores now requires perturbation_col (no default) and builds y via .eq(gene).fillna(False).astype(bool).astype(int) so NaN rows always map to 0 and the target is strictly binary. align.py passes the additional config values into perturbation_score in both the joint and non-joint paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(aggregate): dedupe construct_columns when pert_id_col == pert_col When perturbation_id_col and perturbation_name_col point at the same column (a valid config when aggregation is at the per-construct level, e.g. both "cell_barcode_0"), building construct_columns as [pert_id_col, pert_col, "cell_count"] + feature_cols listed that name twice. construct_table[construct_columns] then produced duplicate columns, and construct_table[pert_col] returned a 2-col DataFrame, breaking the subsequent .str.contains(control_key) call with AttributeError: 'DataFrame' object has no attribute 'str'. Use dict.fromkeys to dedupe while preserving order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(aggregate): use .loc instead of .iloc for keep_idx in perturbation_score keep_idx is built from perturbation_col.index[...] and nt_idx which hold pandas label indices, but line 102 used .iloc (positional). When _compute_perturbation_score_joint in align.py iterates groupby(\"class\") slices without reset_index, cell_data's labels exceed its length and .iloc raises IndexError: positional indexers are out-of-bounds. Lines 130-131 already use .loc correctly with the same gene_idx; make 102 consistent so the function works with either RangeIndex or preserved label index inputs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(aggregate): make eval_aggregate compartment-aware and NaN-safe eval_aggregate.py hardcoded a cell→nucleus fallback chain when picking *_mean feature columns for the distribution plot. For combos whose compartment_combo is second_obj or cytoplasm, neither 'cell_*_mean' nor 'nucleus_*_mean' columns survive compartment_combo_subset, so merge_feature_cols collapsed to [], and the downstream plt.subplots(nrows=0, ...) raised ValueError. Two layers: - Script: derive the prefix priority from snakemake.wildcards.compartment_combo and iterate all known compartments (cell, nucleus, cytoplasm, second_obj) as a fallback chain, using the first non-empty set of *_mean columns. - Lib plot_feature_distributions: guard empty inputs by returning a small placeholder figure with an explanatory label. eval_aggregate's other outputs (na_stats tsv/png) still write; the DAG proceeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(aggregate): carry secondary name column through aggregation When aggregate is grouped by a construct ID (perturbation_name_col = cell_barcode_0), the aggregated.tsv previously had no gene_symbol_0 column. Downstream consumers break: cluster benchmarking (evaluate_resolution -> filter_complexes) tries to intersect aggregated perturbation names with CORUM/KEGG gene_name fields and finds nothing; uniprot merge in notebook 10 cell 20 fails on missing gene_symbol_0. Both assume gene-level naming. Add a carry_cols parameter to lib/aggregate/aggregate.py:aggregate() that preserves columns functionally determined by group_cols in the output — taking the single unique value per group and raising ValueError if the invariant is violated. Wire the rule aggregate to pass carry_cols=[control_name_col] when control_name_col is different from perturbation_name_col (the normal setup for barcode-level aggregation + gene-level control naming). Net effect: when pert=cell_barcode_0 and control=gene_symbol_0, the aggregated.tsv gains a gene_symbol_0 column and downstream clustering/benchmarking/annotation all get the name they expect. When pert == control (gene-level aggregation), carry_cols stays None and behavior is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(aggregate): use positional indexing in missing_values_filter imputation The imputation block read/wrote with .loc[batch_na_idx, cols], which fails on inputs whose index has duplicate labels. Production callers always feed unique-indexed frames (scripts/aggregate/filter.py uses ignore_index=True), but any new caller that doesn't can trip a 'cannot reindex on an axis with duplicate labels' ValueError. Switch to positional iloc indexing — same behavior on clean inputs, robust to duplicate-indexed inputs. * fix(aggregate): plumb control_name_col through generate_feature_table centerscale_on_controls and the construct/gene control-table masks identified controls via perturbation_name_col. When perturbation_name_col != control_name_col (construct-level aggregation, e.g. cell_barcode_0 perturbations with gene_symbol_0 controls) no controls were found: centering-on-controls was silently skipped and the control gene table was empty. Carry control_name_col through the construct accumulation and use it for control identification, mirroring align.py. Falls back to perturbation_name_col when unset, so gene-level runs are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(aggregate): add type hints + pin schema in harmonize_pool_schema - Pass schema=pq.read_schema(paths[0]) to ds.dataset() in harmonize_pool_schema so files are read against one reference schema instead of pyarrow auto-unifying (which raises ArrowInvalid on dtype mismatch across pooled per-well parquets). - Add type hints to harmonize_pool_schema, compartment_combo_subset, and resolve_aggregate_combos for consistency with other functions in the PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: acepedadiaz <acepedadiaz@cheeserind.wi.mit.edu> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: acepedadiaz <acepedadiaz@wi.mit.edu>
akcd1
marked this pull request as ready for review
June 9, 2026 15:46
tests/test_compartment_filter.py was committed with the feature; brieflow's test harness is tests/small_test_analysis/, not standalone pytest files. Removing it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Intensity-scaling/noise augmentation clipped uint8 [0,255] training images to [0,1] and cast to float32, collapsing them to near-binary and mixing dtypes with the (uint8) originals — corrupting custom Cellpose training (on by default). Clip to the image's own range (np.iinfo max for integer dtypes, 1.0 for float), keep the augmented variants the same dtype as the originals, and scale the noise std to the range. Keeps training data in the same uint8 [0,255] space prepare_cellpose feeds the model downstream (cellpose normalizes internally at train + inference). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…minima apply_h_minima_suppression ran skimage h_minima on the distance/intensity peak map, marking valleys instead of peaks — so with maxima_reduction_factor set, the watershed seeds landed in background. Replace with the grayscale h-maxima transform (reconstruction of peak_map - h under peak_map), which flattens maxima shallower than h while keeping the map grayscale for peak_local_max. Rename the function + docstrings to h-maxima to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aggregate_second_objs validated merge keys on second_objs_df before use, so a well with no detected secondary objects (empty frame, no keys) raised 'Missing merge keys' and killed the whole aggregate. Short-circuit the empty case: keep all cells unchanged (as the 'none' strategy does for that well); they concat cleanly with object-bearing wells. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…all-NaN cells (from final_merge) and second_objs (from the phenotype parquet) can carry the plate/well/tile/cell keys at different dtypes (e.g. int32 vs int64, int vs float). A how='left' merge on mismatched dtypes matches nothing and silently fills every second-object feature with NaN. Coerce the second-object keys to the cells' dtypes before merging so matches land. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The secondary-object pipeline gated on config['phenotype'].get('second_obj_detection',
True), so any screen that never set the key silently ran the whole feature and
picked up its phenotype/aggregate schema + path changes. Default to False so
secondary objects are strictly opt-in; existing screens get the standard pipeline
unchanged. Screens using the feature set the flag explicitly (as the small_test
config does).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_info (symmetric with phenotype) Mirror the phenotype alignment-metrics pattern on the SBS side: - add shared offsets_to_metrics(offsets, unit) helper in lib/shared/align.py - align_cycles() gains return_metrics kwarg; captures per-cycle offsets from whichever alignment branch runs (DAPI or sbs_mean) - scripts/sbs/align_cycles.py writes a one-row alignment_metrics TSV - align_sbs rule gets a 2nd output (metrics TSV); extract_sbs_info consumes it as input[1] so the shared extract_phenotype_minimal broadcasts the offset columns onto every cell row - register the metrics TSV target in targets/sbs.smk - mark offset_* columns as metadata in cell_data_metadata_cols.tsv Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ment toggle
The secondary-object feature stamped a compartment_combo dimension onto every
aggregate/cluster/bootstrap output path (_CmCo- token, /{combo}/ cluster dir,
__{combo}__ bootstrap segment) and required the column in the combo TSVs. This
broke backward compatibility for screens that don't use secondary objects:
missing-column KeyErrors and a forced full re-run from changed paths.
Add config aggregate.split_by_compartment (default False). When off, paths are
byte-identical to pre-feature output and a missing compartment_combo column is
tolerated; when on, compartment-specific paths are emitted as before. Column
filtering is already a no-op with all compartments present, so default mode is
a pure path/plumbing change with no effect on the data.
- new lib/shared/compartment_utils.py owns the conditional token/dir/suffix
emission, the wildcard accessor, and combo-table/record normalization
- Snakefile initializes the toggle + all-compartments default and normalizes
the aggregate/cluster combo tables at read time
- aggregate/bootstrap/cluster targets and rules route paths through the helpers
and resolve the combo via the accessor instead of a required wildcard
- metrics/cluster eval resolve legacy or compartment paths and gate the
compartment label in stats keys/columns
- small_test config sets split_by_compartment: true so the fixture keeps
exercising the opt-in feature
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-cell alignment-offset columns (offset_y/x, offset_*_stepN, offset_*_cycleN) broadcast onto phenotype_cp / sbs_info are QC diagnostics, not clustering features. Their names depend on the screen's alignment step/cycle count, so enumerating them in the metadata_cols file is screen-specific and easy to under-specify. Any offset_* column not listed silently falls through into the feature space (split_cell_data, filter, perturbation_score), polluting aggregation/clustering. Reserve the offset_ prefix as metadata via a shared is_reserved_metadata_col predicate, applied wherever metadata is split from features. No-op where offsets are already enumerated (e.g. the small_test config); closes the silent-leak path for real screens whose cycle/step count differs from the list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…zed) The offset_* alignment-QC columns were hand-enumerated in the small_test metadata_cols file back when enumeration was the only way to keep them out of the feature space. The offset_ prefix rule makes that unnecessary, so remove the 32 offset_* lines: the fixture now models correct usage (screens shouldn't enumerate offsets) and actually exercises the prefix path. Also drop a stale num_nuclei mention in a comment. Verified: full small_test_analysis aggregate re-run 1221/1221, aggregated features carry 0 offset_ columns, per-cell tables still retain offset_* as metadata via the prefix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cleanup pass over the secondary-object feature code: - Move the ML/classical parameter routing out of scripts/phenotype/identify_second_objs.py into a lib helper (segment_second_objs_from_config); the script drops from 161 to 59 lines and the unused nuclei_id_col scaffolding is removed. - segment_second_objs and create_empty_results now always return a 3-tuple (updated cytoplasm masks are None when no cytoplasm masks are given), so callers that unpack three values can't hit a shape mismatch; two dead identical if/else return branches are collapsed. - Narrow a bare `except` in extract_phenotype_second_objs to IndexError, so only a sub-3D array falls back to channel 0 and real errors surface. - After declumping, fill_holes only fills background pixels, so a filled hole cannot overwrite a neighboring object's pixels. - cellpose_training uses a local np.random.Generator instead of seeding the global RNG, makes augmentation seedable/reproducible, and keeps at least one training sample when only a single sample is available. - _aggregate_single preserves the input cell order across its split/merge. Verified with py_compile, ruff, and focused in-memory checks. The small_test gate runs second_obj_detection=false, so it does not exercise this path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f43a39d removed `from lib.phenotype.constants import DEFAULT_METADATA_COLS` from cell_data_utils.py as an unused import. It was a re-export: metrics.py imported the constant *through* cell_data_utils, so both call sites now raise ImportError. Both are function-local imports, so py_compile and ruff do not see them -- get_all_stats() calls get_phenotype_stats() unconditionally and fails at runtime, and _calculate_batch_effects() fails under include_batch_effects=True. Import from lib.phenotype.constants directly rather than restoring the re-export. constants.py has no imports of its own, so there is no cycle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…r well 18b1b6d returned cells_df unchanged when a well had no secondary objects. That sits after the "none" short-circuit, so it only fires when the user explicitly asked for single/all/average, and it conflated two cases. A per-well empty slice is legal: second_objs_filtered is a boolean slice of the full parquet, so it keeps its columns, and _aggregate_single NaN-fills every cell -- the documented contract. Returning early instead emits a well whose parquet lacks those columns, and harmonize_pool_schema keeps only the intersection across wells, so one empty well silently strips the secondary object features from every well. A screen with a class-pure control well could lose them entirely and still complete. The case that should fail is the whole screen having no secondary objects while a non-"none" strategy is set. Check that in the script, where the unfiltered frame is still in scope, and drop the per-well early return so empty wells NaN-fill as documented. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ale returns 9d65852 converted file_utils.py, cellpose_training.py and second_obj_utils.py but left segment_secondary_object.py -- its only change there was relocating two functions. Convert the 12 remaining NumPy-style docstrings in that file. Ruff does not catch this: convention = "google" checks header punctuation, not body layout, which is why several docstrings carried a "Returns:" colon with NumPy dashes still underneath. Also correct descriptions the earlier commits invalidated: - segment_second_objs, segment_second_objs_ml and create_empty_results return an unconditional 3-tuple whose third element is None when no cytoplasm masks are given. Four Returns entries still said "if provided" / "optionally", which reads as a 2-tuple. Match _postprocess_secondary_objects, already correct. - harmonize_pool_schema documented kept_metadata_cols as a subset of the caller's metadata_cols, but 00c1b85 appends reserved offset_* columns that were never in that list. Docstrings only: ASTs with docstrings stripped are identical to b23a896 for both files. ruff check and ruff format clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bring main (5 commits, #227/#231/#232/#234/#240) into the branch ahead of #171. Merged rather than rebased: a rebase replays 45 commits and re-hits the same aggregate collisions repeatedly, while the merge conflicts in one file, and main only accepts squash merges so the linear history would be flattened on landing anyway. Both conflicts are in generate_feature_table.py and are complementary: - #234 added a warning when perturbation_id_col is unset; the branch added the control_name_col fallback (#222). Independent, both kept. - Column ordering for the construct table: the branch dedupes via dict.fromkeys over [pert_id_col, pert_col, control_name_col, ...], main dedupes only the id/name pair. Kept the branch version -- main's drops control_name_col, which the control filter downstream indexes on, so main's form would KeyError under a construct-level config. ruff check and ruff format clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Empty commit to fire a fresh pull_request event so the Test Analysis and Ruff workflows run against the current secondary_object HEAD (they last ran on the pre-merge head; the recent updates did not create runs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mat10d
approved these changes
Aug 7, 2026
| SECOND_OBJ_DETECTION = config["phenotype"].get("second_obj_detection", False) | ||
|
|
||
|
|
||
| def get_rule_compartment_combo(wildcards): |
Collaborator
There was a problem hiding this comment.
Strip functions from .smk if possible to observe conventions
|
|
||
| # Carry the control/gene name column through when it differs from the aggregation ID. | ||
| pert_col = snakemake.params.perturbation_name_col | ||
| control_name_col = snakemake.params.get("control_name_col") |
Collaborator
There was a problem hiding this comment.
Confirm this is passed through/flag for aggregate notebooks
| print(f" Secondary objects: {len(second_objs_df)} rows") | ||
|
|
||
| # Screen-level guard: asking to integrate secondary objects into a screen that has | ||
| # none is a misconfiguration, not a data condition. Checked here (before the |
…omments - Move format_rule_output into lib/shared/compartment_utils.py and bind the two compartment-config helpers with functools.partial in rules/aggregate.smk, so the rule file no longer defines Python functions (logic lives in lib; call sites unchanged). - Collapse the multi-line "#" comment blocks this branch introduced to single lines (second_obj_utils, segment_secondary_object x2, aggregate_cells_second_objs, generate_feature_table), per house style. Verified: py_compile + ruff clean; snakemake dry-run builds the aggregate DAG (the rule input functions resolve through the partials). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Review notes addressed (db073ba)
New config parameters this PR wires (for the analysis notebooks)Complete set of config knobs this PR threads end-to-end. All are read from
Combo TSVs
Already on
The remaining follow-up is analysis-side: surface these in the aggregate/phenotype notebooks so operators can set them. |
…rtial Match the convention used across the other rule files (inline `lambda wildcards: lib_fn(wildcards, config...)`): drop the two functools.partial bindings and call get_compartment_combo / format_rule_output directly with SPLIT_BY_COMPARTMENT + DEFAULT_COMPARTMENT_COMBO passed explicitly. No functions are defined in the .smk; the helpers live in lib/shared/compartment_utils.py. Verified: snakemake dry-run builds the aggregate DAG. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Thank you for your contribution to Brieflow!
Please succinctly summarize your proposed change.
What motivated you to make this change?
Added the option to segment ang phenotype additional objects contained within cells.
Please also link to any relevant issues that your code is associated with.
What is the nature of your change?
Checklist
Please ensure that all boxes are checked before indicating that a pull request is ready for review.
pyproject.tomlto reflect the change as designated by semantic versioning.ruff checkandruff format.