Skip to content
Draft
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
56 changes: 56 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Publish to PyPI

# Lint, test, and build the distribution on every pull request, and publish to PyPI when a GitHub
# Release is published. Publishing uses PyPI Trusted Publishing (OIDC), so no API token is stored in
# the repository; the "pypi" environment must be configured as a trusted publisher for the sstrack
# project on PyPI before the first release.

on:
pull_request:
release:
types: [published]

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
# The test suite is intentionally torch-free (see tests/), so install only the lightweight
# dependencies the tests touch rather than the full deep-learning stack.
- name: Lint
run: uv run --no-project --with ruff ruff check src/sst tests
- name: Test
run: >
uv run --no-project
--with pytest --with numpy --with opencv-python-headless
--with pillow --with matplotlib --with tqdm
pytest

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- name: Build sdist and wheel
run: uv build
- name: Check distribution metadata
run: uvx twine check dist/*
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
needs: [check, build]
if: github.event_name == 'release'
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
__pycache__/
out/

# Build and packaging artifacts
dist/
build/
*.egg-info/
.pytest_cache/
.ruff_cache/

# Local clones of upstream model repos, no longer vendored into the package
/GroundingDINO/
/segment-anything-2/

# *.jpg
*.gif
# *.png
Expand Down
6 changes: 3 additions & 3 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ authors:
cff-version: 1.2.0
date-released: "2025-01-12"
identifiers:
- description: "The GitHub release URL of tag v1.0.0."
- description: "The GitHub release URL of tag v2.0.0."
type: url
value: "https://github.com/Imageomics/SST/releases/tag/v1.0.0"
value: "https://github.com/Imageomics/SST/releases/tag/v2.0.0"
keywords:
- imageomics
- biological vision
Expand All @@ -66,7 +66,7 @@ license: "MIT"
message: "If you find this software helpful in your research, please cite both the software and our paper."
repository-code: "https://github.com/Imageomics/SST"
title: "SST: Static Segmentation by Tracking"
version: v1.0.0
version: v2.0.0
type: software
preferred-citation:
type: article
Expand Down
85 changes: 22 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,26 @@
- [x] Release butterfly trait segmentation dataset

## 🛠️ Installation
Set `CUDA_HOME` to your cuda path (this is for grounding DINO)

For example:
Install from PyPI:

```
export CUDA_HOME=/usr/local/cuda
pip install sstrack
```

Then sync uv packages:
or with [uv](https://docs.astral.sh/uv/):

```
uv sync
uv pip install sstrack
```

Download weights into checkpoints folder:
For raw camera formats (CR2, NEF, ARW, DNG) in `sst segment-and-crop`, install the optional extra:

For `wget`
```
cd checkpoints
wget https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt
wget -q https://github.com/IDEA-Research/GroundingDINO/releases/download/v0.1.0-alpha/groundingdino_swint_ogc.pth
pip install "sstrack[raw]"
```

For `curl`:
```
cd checkpoints
curl https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt --output sam2_hiera_large.pt
curl https://github.com/IDEA-Research/GroundingDINO/releases/download/v0.1.0-alpha/groundingdino_swint_ogc.pth --output groundingdino_swint_ogc.pth
```
SST builds on SAM2 and Grounding DINO through HuggingFace `transformers`. Model weights are downloaded from the HuggingFace Hub the first time a model is used and cached under `~/.cache/huggingface` (override with `HF_HOME`), so there is no manual checkpoint download step. The first run needs network access and will fetch a few hundred MB depending on the chosen model; subsequent runs reuse the cache and work offline. The default SAM2 model is `facebook/sam2.1-hiera-tiny`; pass `--model facebook/sam2.1-hiera-large` (or another variant) for higher quality, and `--device cpu`/`--device cuda` to choose hardware (auto-detected by default).


## 🧑‍💻 Usage
Expand All @@ -61,7 +53,7 @@ See the two examples[^1] below:
Then run the following two commands to generate the mask (like a guide for the model in segmentation shape--note the final processed image will _appear_ to be an all black image):

```
uv run python src/sst/get_mask_from_crop.py \
sst mask-from-crop \
--image_path img001.png \
--image_crop_path img001_extracted.png \
--mask_image_path_out img001_extracted_processed.png
Expand All @@ -74,7 +66,7 @@ Example output:


```
uv run python src/sst/prepare_starter_mask.py \
sst prepare-mask \
--mask_image_path img001_extracted_processed.png \
--mask_image_path_out img001_extracted_processed.png
```
Expand All @@ -87,72 +79,39 @@ Example output (NOTE: the color is very faint):
Now that the mask has been generated, the following command can be run to segment your remaining images.

```
uv run python src/sst/segment_and_crop.py \
sst segment-and-crop \
--support_image img001.png \
--support_mask img001_extracted_processed.png \
--query_images [PATH_TO_IMAGE_DIRECTORY] \
--output [PATH_TO_SEGMENTED_OUTPUT_DIRECTORY]
```

The above script is RAM intensive on large datasets. To process individually run the above with `src/sst/segment_and_crop_individual.py`
The default mode loads all query images at once. On large datasets, add `--per-image` to walk the folder recursively and process one image at a time (this also supports raw formats and can resume with `--no-reprocess`).

### Trait Segmentation
For one-shot trait/part segmentation, please run the following demo code:
```bash
python src/sst/segment.py --support_image /path/to/sample/image.png \
--support_mask /path/to/greyscale_mask.png \
sst segment --support_image /path/to/sample/image.png \
--support_mask /path/to/greyscale_mask.png \
--query_images /path/to/query/images/folder \
--output /path/to/output/folder \
--output_format "png" # png or gif, optional
```
### Fine-tuning with OC-CCL
OC-CCL (Open-Close Cycle Consistency Loss) fine-tunes SAM2 on a target species. The cycle opens with `reference → query` (predict the query mask) and closes with `query → reference` (predict the closing mask back on the reference), supervised against the reference's GT mask with BCE + Dice.

**1. Get the butterfly images.** Mask annotations are already tracked under `data/cambridge_butterfly/DataSet_Butterfly/`. The image manifest with Zenodo URLs and md5 checksums is committed at `data/cambridge_butterfly/images.csv`. Download with [`cautious-robot`](https://github.com/Imageomics/cautious-robot):
```bash
pip install cautious-robot
cautious-robot -i data/cambridge_butterfly/images.csv \
-o data/cambridge_butterfly/images \
--checksum-algorithm md5 --verifier-col md5
```
Images land at `data/cambridge_butterfly/images/<image_id>.<ext>`. cautious-robot skips existing files, retries 429/5xx responses, and verifies every download against the committed md5. The manifest can be regenerated from the per-species `train_test_separate/*.json` files via `python data/cambridge_butterfly/build_download_csv.py` (queries the Zenodo API for fresh checksums).

**2. Train on one or more species.**
```bash
python src/sst/oc_ccl.py \
--checkpoint checkpoints/sam2_hiera_large.pt \
--species "(malleti x plesseni) x malleti" \
--epochs 10 --lr 1e-5 \
--output_dir outputs/oc_ccl
```
Best checkpoint is written to `<output_dir>/best_model.pt`. Defaults: `--lr 1e-5`, `--batch_size 1`, `--epochs 10`.

**3. Reproduce the ablation grid.** 16 runs across 8 GPUs sweeping learning rate, BCE/Dice weighting, LoRA rank, and memory reset:
```bash
bash experiments/launch_ablations.sh
python experiments/eval_all_ablations.py # writes outputs/ablation/eval_results.json
```

**4. Curriculum variant (top-n% by reconstruction quality).** Precomputes per-sample cycle reconstruction IoU, then trains only on the highest-quality fraction:
```bash
python experiments/curriculum_oc_ccl.py --gpu 0 --epochs 10 --lr 1e-6
```

### Trait-Based Retrieval
For trait-based retrieval, please refer to the demo code below:
```bash
python src/sst/trait_retrieval.py --support_image /path/to/sample/image.png \
--support_mask /path/to/greyscale_mask.png \
--trait_id 1 \ # target trait to retrieve, denote by the value in support mask \
sst retrieve --support_image /path/to/sample/image.png \
--support_mask /path/to/greyscale_mask.png \
--trait_id 1 \
--query_images /path/to/query/images/folder \
--output /path/to/output/folder \
--output_format "png" \ # png or gif, optional
--top_k 5 # n top retrievals to save as results
--output_format "png" \
--top_k 5
```

### Low-Code Implementation

In the [`gui/`](gui) directory, there is a low-code option for users. Follow the directions in that `README` to install and run the interface.
### Fine-tuning with OC-CCL
OC-CCL (Open-Close Cycle Consistency Loss) fine-tuning is not part of the installable package. Its scripts (`experiments/oc_ccl.py`, ablations, and the curriculum variant) predate the v2.0.0 migration to HuggingFace `transformers` and still depend on the vendored SAM2 copy that shipped with the v1.1.0 scripts-era release. See [`experiments/README.md`](experiments/README.md) for details; porting OC-CCL to the `transformers` backend is tracked as a follow-up.

## 📊 Dataset
Beetle part segmentation dataset is available [here](data/neon_beetles/).
Expand All @@ -162,7 +121,7 @@ Butterfly trait segmentation dataset can be accessed [here](data/cambridge_butte
The instructions and appropriate citations for these datasets are provided in the Citation section of their respective READMEs.

## ❤️ Acknowledgements
This project makes use of the [SAM2](https://github.com/facebookresearch/sam2) and [GroundingDINO](https://github.com/IDEA-Research/GroundingDINO) codebases. We are grateful to the developers and maintainers of these projects for their contributions to the open-source community.
This project builds on [SAM2](https://github.com/facebookresearch/sam2) and [GroundingDINO](https://github.com/IDEA-Research/GroundingDINO) through their [HuggingFace `transformers`](https://github.com/huggingface/transformers) implementations. We are grateful to the developers and maintainers of these projects for their contributions to the open-source community.
We thank [LoRA](https://github.com/microsoft/LoRA) for their great work.

We also thank [David Carlyn](https://davidcarlyn.wordpress.com/) for his contributions to improving the repository’s ease of setup, workflows, and overall usability; and [Sam Stevens](https://samuelstevens.me/) for developing a nice interactive tool for mask generation, selection, and visualization.
Expand Down
7 changes: 7 additions & 0 deletions experiments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Experiments

These are research scripts for OC-CCL (Open-Close Cycle Consistency Loss) finetuning and ablations. They are not part of the installable `sstrack` package and are excluded from the built distribution.

They predate the v2.0.0 transformers migration and still expect the vendored SAM2 copy (`sst.segment_anything_2`) and the local Hydra configs that lived under `src/sst/sam2_configs/`, both of which were removed when the package moved to the HuggingFace `transformers` backend. To run them as written, check out the pre-2.0.0 `v1.1.0` tag where that vendored code still exists.

Porting OC-CCL finetuning to the `transformers` `Sam2VideoModel` backend is tracked as a follow-up. `oc_ccl.py` and `butterfly_dataset.py` were moved here from `src/sst/` during packaging.
File renamed without changes.
File renamed without changes.
91 changes: 71 additions & 20 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,27 +1,78 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "sst"
version = "0.1.0"
description = "Add your description here"
name = "sstrack"
dynamic = ["version"]
description = "Static Segmentation by Tracking: segment biological specimen traits by propagating a reference mask with SAM2."
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
authors = [
{ name = "David Carlyn", email = "davidecarlyn@gmail.com" }
{ name = "David Carlyn", email = "davidecarlyn@gmail.com" },
{ name = "Matthew J. Thompson", email = "thompson.m.j@outlook.com" },
]
keywords = [
"imageomics",
"segmentation",
"sam2",
"tracking",
"specimen",
"biology",
"computer-vision",
]
classifiers = [
"Development Status :: 4 - Beta",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
requires-python = ">=3.12"
dependencies = [
"addict>=2.4.0",
"hydra-core>=1.3.2",
"matplotlib>=3.10.5",
"opencv-python>=4.12.0.88",
"pycocotools>=2.0.10",
"rawpy>=0.25.1",
"supervision>=0.26.1",
"timm>=1.0.19",
"torch>=2.8.0",
"torchvision>=0.23.0",
"transformers>=4.55.2",
"yapf>=0.43.0",
"torch",
"torchvision",
"transformers>=4.56",
"pillow",
"numpy",
"opencv-python-headless",
"matplotlib",
"tqdm",
]

[build-system]
requires = ["uv_build>=0.8.11,<0.9.0"]
build-backend = "uv_build"
[project.optional-dependencies]
raw = ["rawpy"]
dev = ["pytest", "ruff", "pre-commit"]

[project.scripts]
sst = "sst.__main__:main"

[project.urls]
Source = "https://github.com/Imageomics/SST"
Issues = "https://github.com/Imageomics/SST/issues"

[tool.hatch.version]
path = "src/sst/__init__.py"

[tool.hatch.build.targets.wheel]
packages = ["src/sst"]

[tool.hatch.build.targets.sdist]
include = [
"/src/sst",
"/README.md",
"/LICENSE.md",
"/CITATION.cff",
]

[tool.ruff]
line-length = 120
src = ["src", "tests"]
extend-exclude = ["experiments", "gui", "GroundingDINO", "segment-anything-2"]

[tool.ruff.lint]
select = ["E", "F", "I", "W"]

[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]
Loading
Loading