Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fe7a5da
VisCy Modular: Transforms and Monorepo Skeleton (#356)
srivarra Feb 4, 2026
12b0279
docs: map existing codebase
edyoshikun Feb 8, 2026
4ac3050
Modularizing the model's folder (#365)
edyoshikun Feb 18, 2026
fd1faae
modularizing data package (#366)
alxndrkalinin Feb 27, 2026
09043d1
Applications: Cytoland refactor (#379)
alxndrkalinin Mar 19, 2026
1f8dfe8
Delete the .planning (#385)
edyoshikun Mar 19, 2026
3944e75
renaming INDEX_COLUMNS to ULTRACK_INDEX_COLUMNS
edyoshikun Mar 19, 2026
6543c0c
feat: rename applications/translation to applications/cytoland
alxndrkalinin Mar 19, 2026
222c6e8
feat: rename applications/translation to applications/cytoland
alxndrkalinin Mar 19, 2026
96ff4ae
chore: remove nfs artifacts
alxndrkalinin Mar 19, 2026
c766862
- Replace TrackingIndex with CellIndex + SampleMeta (all fields Not…
edyoshikun Mar 19, 2026
0e6c6af
chore: clean up cytoland rename references
alxndrkalinin Mar 19, 2026
c95e571
update fit wot use wandb
edyoshikun Mar 19, 2026
943b3eb
remove deprectaed files that were left over from the monorepo restruc…
edyoshikun Mar 19, 2026
43e0a7e
Address deferred review cleanup on staging (#386)
alxndrkalinin Mar 25, 2026
1a12ac8
Dynaclr-dino (#387)
edyoshikun Mar 30, 2026
c80108a
Add Dynacell models, training configs, and mixed-precision fixes (#388)
alxndrkalinin Apr 7, 2026
a10d4c4
Update packages/viscy-data/pyproject.toml
edyoshikun Apr 21, 2026
737cedf
Port to iohub 0.3.2 ImageArray API (#407)
alxndrkalinin Apr 23, 2026
0c5b834
bump iohub to 0.3.3
edyoshikun Apr 24, 2026
651f969
Port virtual_staining examples into applications/cytoland (#409)
edyoshikun May 18, 2026
393abdf
Refactor applications/DynaCell into a reusable benchmark package (#404)
alxndrkalinin May 22, 2026
eb07e1f
Dyanacell models and dynadtw (#432)
edyoshikun May 22, 2026
299b877
update the eval_registry_infectomics
edyoshikun May 22, 2026
3d7a9e3
Merge main (0.4.1 release line) into modular-viscy-staging for 0.5
edyoshikun May 22, 2026
22242b0
docs: added docs for how to tag and create wheels for packages
srivarra May 28, 2026
0f63d97
style: fixed failing ruff checks
srivarra May 28, 2026
5a1a3c9
refactor(dynacell/eval): unify pixel metrics and improve performance …
alxndrkalinin Jun 2, 2026
2d1e2ba
docs: Setting up Zensical and GitHub Pages (#444)
srivarra Jun 2, 2026
676e254
ci: green up modular-viscy-staging (CI matrix, deps, lint) (#435)
edyoshikun Jun 3, 2026
8f42e30
feat(cytoland): non-square rotation TTA + reuse helpers
ieivanov Jun 3, 2026
dc8892d
refactor(cytoland): unify VSUNet rotation TTA on rotation_tta_transforms
ieivanov Jun 3, 2026
cf1a4a7
add "product" to tta tests
ieivanov Jun 5, 2026
a31eaa4
Merge origin/main into feat/vs-unify-rotation-tta
ieivanov Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .claude/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type":"commonjs"}
178 changes: 178 additions & 0 deletions .claude/skills/airtable-build-collection/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
---
name: airtable-build-collection
description: Query Airtable MCP to fetch experiment metadata and generate a DynaCLR collection YAML for training
---

# Build Collection from Airtable

Build a collection YAML for DynaCLR training by querying the Computational Imaging Database on Airtable.

## Airtable Configuration

- **Base ID**: `app8vqaoWyOwa0sB5` (Computational Imaging Database)
- **Table ID**: `tblaFzrDMlVZHPZIj` (Datasets)

Key fields in the Datasets table:

| Field | Description |
|---|---|
| `dataset` | Experiment name (e.g. `2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV`) |
| `well_id` | Well path (e.g. `B/2`) |
| `fov` | FOV identifier |
| `cell_state` | Condition label (e.g. `infected`, `uninfected`) |
| `marker` | Protein marker (e.g. `SEC61B`, `TOMM20`, `pAL10`) |
| `organelle` | Target organelle |
| `perturbation` | Perturbation applied |
| `hours_post_perturbation` | HPI at imaging start |
| `moi` | Multiplicity of infection |
| `time_interval_min` | Minutes between frames |
| `data_path` | Path to HCS OME-Zarr store (FOV-level — extract zarr root by trimming well/fov) |
| `tracks_path` | Path to tracking zarr (may be absent) |
| `channel_0_name` .. `channel_N_name` | Zarr channel names |
| `channel_0_marker` .. `channel_N_marker` | Protein marker for each channel |
| `t_shape`, `c_shape`, `z_shape`, `y_shape`, `x_shape` | Array dimensions |
| `pixel_size_xy_um`, `pixel_size_z_um` | Physical pixel sizes |

## Usage

The user will describe what they want in natural language, e.g.:

- "fetch the dataset from 2025_07_24 with all the organelles from that experiment"
- "build a collection with the SEC61 and TOMM20 experiments from July 2025"
- "make a collection for all ZIKV infection datasets"

## Process

### Step 1: Query Airtable

Search for matching records using `mcp__airtable__list_records` with `filterByFormula`.

Common filter patterns:
- By dataset name: `SEARCH("2025_07_24", {dataset})`
- By organelle: `{organelle} = "SEC61"`
- By perturbation: `{perturbation} = "ZIKV"`
- Combined: `AND(SEARCH("2025_07", {dataset}), {organelle} = "TOMM20")`

Use `mcp__airtable__list_records` with `filterByFormula` for precise filtering.
Use `mcp__airtable__search_records` for fuzzy text matching.

### Step 2: Group and Summarize

Group records by `dataset`. **If a single dataset contains multiple markers/organelles** (different `marker` values across wells), split it into one experiment entry per marker. The experiment name gets a `_{MARKER}` suffix (e.g. `2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV_TOMM20`). All split entries share the same `data_path` and `tracks_path` but have different `perturbation_wells`, `marker`, and `organelle`.

This is handled automatically by `build_collection()` in `packages/viscy-data/src/viscy_data/collection.py` via the `_group_records()` helper.

Present a summary table to the user showing:

- Dataset names found (with split entries if multi-organelle)
- Number of FOVs per dataset
- Organelles and markers
- Channel names and markers
- Conditions (inferred from `perturbation` field — see note below)
- Wells per condition
- Whether `tracks_path` is available

**Note on cell_state**: In Airtable, `cell_state` is typically "Live" for all records. Infer infection status from the `perturbation` field: wells with a perturbation value are "infected", wells without are "uninfected".

Ask the user to confirm which datasets to include.

### Step 3: Determine Channels

Each experiment entry has a `channels` list where each entry maps a zarr channel name to a protein marker:

```yaml
channels:
- name: "Phase3D" # zarr channel name
marker: "Phase3D" # protein marker / semantic label
- name: "raw GFP EX488 EM525-45"
marker: "SEC61B"
```

Rules for mapping:
1. `channel_X_name` from Airtable → `name` field (the zarr channel name)
2. `channel_X_marker` from Airtable → `marker` field (the protein marker)
3. Only include channels relevant to the experiment — typically Phase3D (labelfree) and the fluorescence channel(s) for the marker of interest

Present the proposed channel mapping to the user for confirmation:
```
Channels per experiment:
2025_07_24_SEC61:
- Phase3D → Phase3D
- raw GFP EX488 EM525-45 → SEC61B
2024_08_14_ZIKV:
- Phase3D → Phase3D
- MultiCam_GFP_BF → pAL10
```

### Step 4: Determine tracks_path

Check the `tracks_path` field in Airtable. If missing, ask the user.

### Step 5: Naming Convention

Collection filenames follow: `{cell_line}_{perturbation}_{organelle}.yml`

- **Single organelle**: use the organelle name, e.g. `A549_ZIKV_SEC61.yml`
- **Multiple organelles**: use `multiorganelle`, e.g. `A549_ZIKV_multiorganelle.yml`
- **No version suffix** — versioning is handled by git history
- The `name` field inside the YAML should match the filename (without `.yml`)

### Step 6: Generate Collection YAML

Use the Collection schema from `packages/viscy-data/src/viscy_data/collection.py`.

The current schema uses per-experiment `channels` (list of `{name, marker}` entries), NOT `source_channels`:

```yaml
name: <filename without .yml>
description: "<what this collection contains>"

provenance:
airtable_base_id: app8vqaoWyOwa0sB5
airtable_query: "<the filter formula used>"
record_ids: []
created_at: "<ISO 8601 timestamp>"
created_by: "<user name if known>"

experiments:
- name: <dataset_name or dataset_marker split name>
data_path: <zarr store root — trim well/fov from airtable data_path>
tracks_path: <from airtable or user>
channels:
- name: <zarr_channel_name>
marker: <protein_marker>
- name: <zarr_channel_name>
marker: <protein_marker>
perturbation_wells:
uninfected:
- <well_id>
<perturbation_name>:
- <well_id>
interval_minutes: <time_interval_min>
start_hpi: <hours_post_perturbation or 0.0>
marker: <primary marker>
organelle: <organelle>
moi: <moi or 0.0>
pixel_size_xy_um: <from airtable>
pixel_size_z_um: <from airtable>
```

Key notes:
- `data_path` should be the zarr store root (up to `.zarr`), NOT the FOV-level path from Airtable
- `perturbation_wells` uses `uninfected` / `<perturbation>` keys inferred from the `perturbation` field
- `channels` lists only the channels needed for training (not all channels in the zarr)
- `marker` at the experiment level is the primary marker for this experiment entry

### Step 7: Save and Validate

1. Save to `applications/dynaclr/configs/collections/<name>.yml`
2. Validate by loading with `viscy_data.collection.load_collection(path)` using a quick Python check
3. Show the user the final YAML and validation result

## Important Notes

- `interval_minutes` must be > 0
- `perturbation_wells` must not be empty
- Zarr channel names in `channels[].name` must match actual zarr channel names
- For multi-marker datasets, split into separate experiment entries per marker
- Reference existing collections in `applications/dynaclr/configs/collections/` for format examples
192 changes: 192 additions & 0 deletions .claude/skills/airtable-register/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
---
name: airtable-register
description: Register zarr positions into the Computational Imaging Database on Airtable, write channels_metadata/experiment_metadata to zarr .zattrs, or bulk-update Airtable records via MCP. Use when the user asks to "register a dataset", "register zarr positions", "update airtable from zarr", "write metadata to zarr", "run register on", "sync airtable", "populate channel markers", "update airtable records", "backfill fields", or "fill in missing fields". Also use for Marker Registry questions.
version: 3.0.0
author: ai-x-imaging
tags: [Airtable, OME-Zarr, Metadata, Registration, DynaCLR, VisCy]
---

# Airtable Registration & Update Skill

Manages bidirectional metadata sync between OME-Zarr datasets and the Computational Imaging Database on Airtable, and supports bulk field updates via MCP.

## Airtable Configuration

- **Base ID**: `app8vqaoWyOwa0sB5` (Computational Imaging Database)
- **Datasets table ID**: `tblaFzrDMlVZHPZIj`
- **Collections table ID**: `tblu0Rbj9OnLl7vJf`
- **Models table ID**: `tblVZhRA48tDMWj8U`
- **Marker Registry table**: `tblmP8l2GmpCeERyD`
- **Script**: `applications/airtable/scripts/write_experiment_metadata.py`
- **Core logic**: `applications/airtable/src/airtable_utils/registration.py`
- **Schemas**: `applications/airtable/src/airtable_utils/schemas.py`
- **Database interface**: `applications/airtable/src/airtable_utils/database.py`
- **Channel parsing**: `packages/viscy-data/src/viscy_data/channel_utils.py`
- `MAX_CHANNELS = 8` (defined in `schemas.py`)
- `AIRTABLE_API_KEY` and `AIRTABLE_BASE_ID` must be set in environment

## Operations

### 1. Register (zarr -> Airtable)

Reads zarr metadata and writes per-FOV records to Airtable.

**Fields written by register:**
- `data_path` — full path to zarr position
- `channel_{i}_name` — zarr channel names (up to 8)
- `channel_{i}_marker` — protein marker, derived from Marker Registry
- `t_shape`, `c_shape`, `z_shape`, `y_shape`, `x_shape` — array dimensions
- `pixel_size_xy_um`, `pixel_size_z_um` — from zarr coordinate transforms

**Marker derivation rules:**
- **labelfree** channels -> marker = channel name (e.g. `"Phase3D"`, `"BF"`, `"DIC"`)
- **virtual_stain** channels -> marker = channel name (e.g. `"nuclei_prediction"`)
- **fluorescence** channels -> substring-match aliases against channel name -> protein marker from registry (e.g. `"TOMM20"`, `"SEC61B"`)

#### Commands

```bash
# Dry run first (always recommended)
uv run --package airtable-utils \
applications/airtable/scripts/write_experiment_metadata.py \
register --dry-run /path/to/dataset.zarr/*/*/*

# Register all positions
uv run --package airtable-utils \
applications/airtable/scripts/write_experiment_metadata.py \
register /path/to/dataset.zarr/*/*/*

# Single position
uv run --package airtable-utils \
applications/airtable/scripts/write_experiment_metadata.py \
register /path/to/dataset.zarr/A/1/000000

# Override dataset name (when zarr stem doesn't match Airtable)
uv run --package airtable-utils \
applications/airtable/scripts/write_experiment_metadata.py \
register --dataset my_dataset /path/to/dataset.zarr/*/*/*
```

#### Parquet Readiness Report

After registration, the CLI prints a **Parquet Readiness** report that flags any fields still needed before a flat parquet cell index can be built. Fields are split by source:

- **zarr** fields (auto-filled by `register`): `data_path`, `channel_N_name`, `channel_N_marker`, `pixel_size_xy_um`, `pixel_size_z_um`
- **platemap** fields (biologist fills in Airtable): `tracks_path`, `perturbation`, `time_interval_min`, `hours_post_perturbation`, `cell_type`

If any platemap fields are missing, the report shows what to fill in and how (Airtable UI or MCP bulk update).

### 2. Write (Airtable -> zarr)

Writes `channels_metadata` and `experiment_metadata` to zarr `.zattrs`.

```bash
uv run --package airtable-utils \
applications/airtable/scripts/write_experiment_metadata.py \
write /path/to/dataset.zarr/*/*/*
```

#### channels_metadata schema

```json
{
"Phase3D": {
"channel_type": "labelfree",
"biological_annotation": {"marker": "Phase3D"}
},
"raw GFP EX488 EM525-45": {
"channel_type": "fluorescence",
"biological_annotation": {
"marker": "TOMM20",
"marker_type": "protein_tag",
"fluorophore": null
}
}
}
```

#### experiment_metadata schema

```json
{
"perturbations": [
{"name": "ZIKV", "type": "unknown", "hours_post": 48.0, "moi": 5.0}
],
"time_sampling_minutes": 15.0
}
```

### 3. Bulk Update (via Airtable MCP)

For updating fields that don't come from zarr (e.g. `tracks_path`, `organelle`, manually-curated fields).

**Process:**

1. Fetch target records with `mcp__airtable__list_records` using `filterByFormula`
2. Compute new values (python/jq)
3. Batch update with `mcp__airtable__update_records` (max 10 per call, send all batches in parallel)
4. Verify with the same filter query (must return zero remaining records)

**Pagination warning:** `mcp__airtable__list_records` returns ~100 records max per call. If count equals ~100, re-query with tighter filters.

## Datasets Table Fields

| Field | Description | Written by register? |
|---|---|---|
| `dataset` | Experiment name | on create |
| `well_id` | Well path (e.g. `B/2`) | on create |
| `fov` | FOV identifier | on create |
| `data_path` | Path to HCS OME-Zarr position | yes |
| `tracks_path` | Path to tracking zarr | no (manual/MCP) |
| `channel_0_name` .. `channel_7_name` | Zarr channel names | yes |
| `channel_0_marker` .. `channel_7_marker` | Protein marker per channel | yes |
| `t_shape` .. `x_shape` | Array dimensions | yes |
| `pixel_size_xy_um` | Physical XY pixel size (um) | yes |
| `pixel_size_z_um` | Physical Z pixel size (um) | yes |
| `marker` | Well-level primary marker | template copy |
| `organelle` | Target organelle | template copy |
| `perturbation` | Perturbation applied | template copy |
| `cell_type` | Cell type (e.g. `A549`) | template copy |
| `cell_state` | Condition label | template copy |
| `time_interval_min` | Minutes between frames | template copy |
| `hours_post_perturbation` | HPI at imaging start | template copy |
| `moi` | Multiplicity of infection | template copy |
| `fluorescence_modality` | Imaging modality | template copy |

## Marker Registry

Table `tblmP8l2GmpCeERyD` — maps constructs to protein markers.

| Field | Type | Example |
|---|---|---|
| `marker-fluorophore` | text (primary) | `TOMM20-GFP` |
| `channel_name_aliases` | text | `GFP, FITC` |
| `marker` | text | `TOMM20` |

Matching is substring-based: `"GFP" in "raw GFP EX488 EM525-45"` -> match.

## Flat Parquet Alignment

The `register` command writes all fields needed to build a flat parquet cell index:
- `channel_{i}_name` -> parquet `channel_name`
- `channel_{i}_marker` -> parquet `marker`
- `pixel_size_xy_um`, `pixel_size_z_um` -> parquet pixel size columns
- `data_path` -> parquet `store_path`

## Dataset Directory Conventions

For **organelle_dynamics** datasets:
```
data_path: /hpc/projects/intracellular_dashboard/organelle_dynamics/{EXP}/2-assemble/{EXP}.zarr
tracks_path: /hpc/projects/intracellular_dashboard/organelle_dynamics/{EXP}/1-preprocess/label-free/3-track/{EXP}_cropped.zarr
```

Other families (organelle_box, viral-sensor) have non-standard structures — check filesystem.

## Example Invocations

- "register this dataset /path/to/dataset.zarr/*/*/*"
- "write metadata to zarr for dataset X"
- "update tracks_path for all organelle_dynamics datasets"
- "fill in pixel_size_xy_um for all records where it's missing"
- "set organelle = 'mitochondria' for all 2024_11_21 records"
Loading