
Multimodal deep learning framework, datasets, and models for plankton identification.
Part of Inria Challenge OcéanIA.
planktonzilla is a framework for managing datasets, training computer vision models, and evaluating performance on various plankton image identification tasks. Built on top of Hugging Face Transformers and Hydra for configuration management, it offers specialized tools for handling imbalanced plankton datasets and state-of-the-art imbalance learning loss functions.
planktonzilla-17Mdataset: 17.4 million plankton images drawn from 15 source datasets, all standardized and preprocessed for deep learning applications:project-oceania/planktonzilla-17M. To explore how those source labels map onto one taxonomy, build the Sankey locally withpz_sankey.- Models trained on
project-oceania/planktonzilla-17M: - Planktonzilla Hugging Face Collection: https://huggingface.co/collections/project-oceania/planktonzilla
- Project OcéanIA project website: https://oceania.inria.cl.
- Project OcéanIA on Hugging Face Hub (more datasets, trained models, and demos): https://huggingface.co/project-oceania.
If you use Planktonzilla in your research, please cite as:
A. G. Contreras Montanares, L. Valenzuela, L. Martí, and N. Sanchez‑Pi, Planktonzilla: Multimodal dataset and models for understanding plankton ecosystems, Inria Chile Research Center, Tech. Rep., May 2026, doi: 10.48550/arXiv.2606.00080, arXiv: 2606.00080 [cs.CV]. url: https://arxiv.org/abs/2606.00080
@techreport{contrerasmontanares:hal-05621003,
title = {Planktonzilla: {M}ultimodal dataset and models for understanding plankton ecosystems},
author = {Contreras Montanares, Alan Gerson and Valenzuela, Luis and Mart{\'i}, Luis and Sanchez-Pi, Nayat},
year = 2026,
month = {May},
keywords = {Explainable AI; XAI ; Plankton Classification ; CLIPS ; Multimodal Classification},
eprinttype = {arxiv},
hal_id = {hal-05621003},
hal_version = {v1},
eprint = {2606.00080},
archivePrefix = {arXiv},
primaryClass = {cs.CV},
url = {https://arxiv.org/abs/2606.00080},
doi = {10.48550/arXiv.2606.00080},
institution = {Inria Chile Research Center},
}from transformers import AutoModelForImageClassification, AutoImageProcessor
from PIL import Image
model_id = "project-oceania/<model-name>" # see https://huggingface.co/project-oceania
processor = AutoImageProcessor.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForImageClassification.from_pretrained(model_id, trust_remote_code=True)
image = Image.open("plankton.jpg").convert("RGB")
inputs = processor(images=image, return_tensors="pt")
outputs = model(**inputs)
predicted_idx = outputs.logits.argmax(-1).item()
print(model.config.id2label[predicted_idx])planktonzilla/ # repo root
├── configs/ # Hydra configuration tree (bundled into wheel)
│ ├── train.yaml # root config for pz_train
│ ├── import_dataset.yaml # root config for pz_import_dataset
│ ├── planktonzilla.yaml # root config for pz_planktonzilla (create or update)
│ ├── generate_planktonzilla.yaml # deprecated; still owns the `datasets` registry
│ ├── update_planktonzilla.yaml # deprecated root config for dataset update
│ ├── augmentation/ # data augmentation strategies
│ ├── custom_loss/ # imbalance-aware loss configs
│ ├── dataset/ # dataset-specific configs
│ ├── dataset_import/ # per-source import configs
│ ├── debug/ # debug-run configs
│ ├── experiment/ # composed experiment configs
│ ├── extras/ # misc extras (e.g. print config tree)
│ ├── hparams_search/ # hyperparameter-search configs
│ ├── hydra/ # Hydra runtime (help/, launcher/ for SLURM)
│ ├── local/ # machine-local overrides
│ ├── model/ # model architecture configs
│ ├── paths/ # path configs (PROJECT_ROOT etc.)
│ ├── peft/ # LoRA / PEFT adapter configs
│ ├── tracking/ # experiment tracking (W&B, MLflow, trackio)
│ ╰── training_arguments/ # HF TrainingArguments configs
├── planktonzilla/ # main package
│ ├── train.py # pz_train entry point (HF Trainer pipeline)
│ ├── dataset.py # DatasetWrapper: load/split/transform
│ ├── loss.py # imbalance-aware loss functions
│ ├── clip_model.py # ClipClassifier (open_clip encoder + head)
│ ├── dataset_import/ # pz_import_dataset entry point + DatasetImporter subclasses
│ │ ╰── public_data/ # bundled source-dataset metadata
│ ├── clip_train/ # SLURM contrastive CLIP pretraining (main.py, train.py)
│ ├── open_clip_ext/ # forward-compat seam around open_clip factory/transform
│ │ ╰── model_configs/ # open_clip model JSON configs
│ ├── planktonzilla_dataset/ # builds the master composite dataset from external sources
│ │ ├── make_planktonzilla.py # pz_planktonzilla — create or update (Hydra entry)
│ │ ├── generate_planktonzilla.py # deprecated build entry; hosts the shared pipeline
│ │ ├── gen_planktonzilla_only_plankton.py
│ │ ├── update_planktonzilla.py # deprecated taxonomy re-sync (Hydra entry)
│ │ ├── save_planktonzilla_for_clip.py # export to WebDataset for CLIP
│ │ ├── sankey.py # pz_sankey — live label-space Sankey (self-contained HTML)
│ │ ├── templates/sankey_flow.html # the page pz_sankey fills in
│ │ ├── constants.py # shared constants
│ │ ├── planktonzilla_taxonomy.csv # taxonomy mapping table
│ │ ╰── utils/ # extract_cox.py, extract_taxon_ids.py, KNOWN_ISSUES.md, RESOLVED_ISSUES.md
│ ╰── utils/ # hydra.py, resolvers.py, logger.py, rich_utils.py
├── scripts/ # train.sh, train_clip.sh, push_dataset.sh (SLURM launchers)
├── notebooks/ # exploratory analysis (metrics paper, sampling map)
├── docs/ # banner + figures used by this README
├── .devcontainer/ # CUDA 12.5 + cuDNN dev container
├── .github/workflows/ci.yml # CI: lint · test · dependency-isolation guard
╰── tests/ # pytest suite (mocks all network)
- Python 3.11-3.13
- uv for dependency management
- CUDA-compatible GPU (recommended for training)
# Clone the repository
git clone https://github.com/Inria-Chile/planktonzilla.git
cd planktonzilla
# Install dependencies (creates .venv automatically)
uv sync
# Install with development dependencies
uv sync --group dev
# Activate the virtual environment (optional — `uv run` works without it)
source .venv/bin/activateuv run <command> runs any project script inside the project venv without needing
to activate it manually. If you prefer an activated shell, run
source .venv/bin/activate.
# Import ISIISNET dataset
uv run pz_import_dataset dataset_import=isiisnet
# Import other available datasets
uv run pz_import_dataset dataset_import=flowcamnet
uv run pz_import_dataset dataset_import=lenslessEvery importable source has a config in configs/dataset_import/ — pass its filename (without
the .yaml) as dataset_import=.
planktonzilla-17M is assembled from the imported sources by mapping each source's own labels
onto the shared taxonomy in planktonzilla/planktonzilla_dataset/planktonzilla_taxonomy.csv.
One command creates or updates it (configs/planktonzilla.yaml):
uv run pz_planktonzillaThere is no mode switch. The run is described by three orthogonal parameters:
| Parameter | Meaning | Values |
|---|---|---|
base |
where already-built rows come from | null · hub · local · a path |
sources |
which sources are rebuilt this run | all · [] · [whoi] |
sync_taxonomy |
re-apply the CSV to carried-over rows | true · false |
# Create the whole dataset from scratch (all 16 sources) — the default
uv run pz_planktonzilla
# The taxonomy CSV changed: re-sync every row, rebuild nothing
uv run pz_planktonzilla base=hub sources=[]
# Re-import one source and splice it into what is already there
uv run pz_planktonzilla base=local sources=[whoi] refresh=redownload
# Same, plus a taxonomy re-sync of everything else (sync_taxonomy defaults true)
uv run pz_planktonzilla base=local sources=[whoi,zooscan] refresh=redownload num_proc=8
# Pre-flight: resolve the plan and check it, touching nothing
uv run pz_planktonzilla base=hub sources=[whoi] dry_run=true
# Same, plus: is every file actually downloadable right now?
uv run pz_planktonzilla dry_run=true check_downloads=all
# Stamp a version on the build, and tag it on the Hub
uv run pz_planktonzilla version=1.4.0 push_to_hub=trueA build takes hours, so the same command also answers whether it could succeed before
doing any of it. dry_run=true resolves the plan and checks its local prerequisites;
check_downloads adds the network ones:
| Value | What is probed |
|---|---|
none |
nothing (the default) — local checks only |
needed |
every file this run would fetch (sidecar inputs included), plus the Hub endpoints it would use |
all |
also the sources whose imagefolder is already built |
# Audit every source in the registry — the one to run on a schedule
uv run pz_planktonzilla dry_run=true check_downloads=all
# Gate a REAL run: refuse to start if something it needs is unreachable
uv run pz_planktonzilla base=local sources=[whoi] refresh=redownload check_downloads=neededIt reports one line per check and exits non-zero listing everything blocking, so it works as a CI gate. A dry run builds nothing, so every failure it finds is fatal to it; a real run is only stopped by a source it would actually fetch — an archive that is unreachable for a source whose imagefolder is already on disk is reported loudly and lets the build proceed. What it verifies:
- Downloads — one
HEADper URL, falling back to a one-byte rangedGETfor the hosts that refuseHEAD(several of these do), reporting status, media type and size. Local sources are checked too: the bundledlenslesszip and any hand-downloaded archive are opened, so a truncated one is caught here instead of hours later. A403or a dropped connection is reported as client filtering, with the manual-archive fallback spelled out; a200that returns HTML is flagged as the login wall it usually is.sykezooscan2024is checked through the Fairdata API read-only — the pre-flight never asks the service to package anything. - Sidecars — inputs a source needs on every run outside its archive (FREPJ's three
md5-pinned geodata tables and its committed site crosswalk), reported as
sidecars:<source>: on disk with their pin, or would be fetched — the run obtains them itself, md5-verified, into<data_dir>/frepj_tablesbefore the first import, so a source whose tables are missing counts as one this run fetches and is probed underdownload:<source>even when its imagefolder is already built. A verified table is never re-fetched (refresh=redownloadincluded — delete the file to force it); a committed file that is gone is a blocking failure, since no run can repair a checkout. - Taxonomy CSV — it exists, parses, has every lookup column (a missing rank silently builds that column blank), and has rows for each source being rebuilt.
base— on disk, that the path really is a saved dataset, with its shards, row count and embedded version read from JSON rather than by loading it; on the Hub, that the repo is readable, distinguishing gated from missing or invisible to this token.- Push target — that the token can write, and that
version=is not already a tag. Today that collision only surfaces after the whole dataset has been uploaded. - Disk — that the output and data directories are writable, with free space compared against the total download size the probes just measured (a build needs ~3× it).
Everything is side-effect free: nothing is downloaded, no dataset is written, nothing is POSTed. The only write is a zero-byte file created and removed to test a directory.
version= (default null, unversioned) is applied in two places:
- Embedded in the saved artifact as
DatasetInfo.version, so a copy on disk or pulled from the Hub knows which version it is. This needs thex.y.zformdatasets.utils.Versionaccepts — note it normalises, so2026.08.01becomes2026.8.1. - Pushed as a git tag on the Hub repo, after a successful push. Hub tags are free-form, so any non-empty string works.
A version that isn't x.y.z is therefore still a valid Hub tag but can't be embedded; the run
says so rather than dropping it silently. version_strict=true rejects anything non-embeddable.
The version is validated before any build work, so a malformed one fails in seconds rather than
after hours.
Tagging happens only after the push succeeds, so a tag always points at data that exists. An
existing tag is an error by default — re-tagging silently would make a version name point at
different data; version_overwrite=true moves it deliberately. If the push succeeds but tagging
fails, the error says so explicitly: the upload is done, don't re-run the build.
Source names are the name field of the datasets entries (whoi, zooscan,
planktonset1.0, global_uvp5) — not the configs/dataset_import/ stems
(whoi-plankton, zooscannet, …). Passing a stem by mistake is rejected with the name to
use instead.
The output holds exactly one contribution per source — freshly built for the sources in
sources, carried over from base for the rest — concatenated in the datasets declaration
order. Reassembling in registry order rather than appending rebuilt rows at the end is what
makes an incremental run row-for-row identical to a from-scratch one, which
tests/test_make_planktonzilla_splice.py asserts directly.
A per-source refresh needs refresh=redownload (or rebuild) to do anything: a non-empty
imagefolder short-circuits the import, and every _prepare_imagefolder except Lensless merges
into the existing tree, so without it a "refresh" could only add files, never drop ones deleted
upstream.
Two guards worth knowing, both of which stop a run before it does any I/O:
- A partial rebuild (
sourcesa strict subset) withbase=nullrefuses to overwrite an existingoutput_dir, becausesave_to_diskreplaces a dataset directory silently — a forgottenbase=would swap the 17M-row artifact for a fragment and report success. Usebase=local, a freshoutput_dir=, orallow_partial_overwrite=true. - A base whose columns diverge from the consolidated schema is a hard error, because
concatenate_datasetsnull-fills a missing column instead of raising.
The published dataset and the models trained on it are frozen artifacts. These commands are reproduction tooling — changing what they emit means republishing, not patching.
Every image is stamped with its source's license / license_url
(see Licensing). A from-scratch build writes them
alongside the taxonomy, in the same pass — no extra sweep over the images.
Adding those columns to the published dataset changes its schema, so publish it onto its own Hub branch rather than over the revision the paper and the released models are pinned to:
# Strictly additive: annotate the frozen data, change nothing else, publish to a v1.1 branch
uv run pz_planktonzilla base=hub sources=[] sync_taxonomy=false \
push_to_hub=true push_revision=v1.1 version=1.1.0sync_taxonomy=false is what makes that run additive: the taxonomy CSV is never read and every
published taxonomy/ID value is carried through untouched. Drop it only when you actually intend
to republish the taxonomy too.
The same holds for custom_metadata (added for v1.2): one JSON object per image holding what
only its source knows and no consolidated column covers — FREPJ's magnification and raw site
token; the literal {} for the fifteen existing sources. A base that predates the column is
filled with {} on its next pz_planktonzilla base=… run (logged loudly), so that run too
belongs on a new push_revision, not over the frozen one.
push_revision targets a branch; version tags it. Tag the frozen state first so v1.0
keeps pointing at the original bytes:
from huggingface_hub import HfApi
api = HfApi()
api.create_tag("project-oceania/planktonzilla-17M", tag="v1.0", revision="main", repo_type="dataset")Deprecated: pz_generate_planktonzilla and pz_update_planktonzilla
Both still work and behave exactly as before, but are removed in the next minor release:
uv run pz_generate_planktonzilla # == uv run pz_planktonzilla
uv run pz_update_planktonzilla # == uv run pz_planktonzilla base=hub sources=[] output_dir='${data_dir}'
# license-columns-only run, on the old command and the new one
uv run pz_update_planktonzilla sync_taxonomy=false
uv run pz_planktonzilla base=hub sources=[] sync_taxonomy=false output_dir='${data_dir}'The output_dir override matters: pz_update_planktonzilla saved to the bare data_dir,
whereas pz_planktonzilla saves to <data_dir>/planktonzilla-17M (where
pz_generate_planktonzilla wrote, and where base=local reads back).
pz_sankey writes one self-contained HTML file — no server, no CDN, no build step — that
follows every source label from the dataset that produced it, through root_class, and down
the Linnaean ranks:
Source dataset → root_class → Domain → Kingdom → Phylum → Class → Order → Family → Genus → Species
Rows whose root_class is not living have no lineage, so their proposed_label sits at the
Domain column and the ribbon ends there. Living ribbons also stop at the deepest rank the
taxonomy actually fills, so nothing drains into a fictitious "blank" node.
# Defaults: bundled taxonomy CSV + ./samples.json if present
uv run pz_sankey
# Explicit counts and output, opened when done
uv run pz_sankey --samples-json samples.json --out flow.html --open
# No image counts: ribbons are weighted by label mappings instead
uv run pz_sankey --no-samples
# Rescan the published dataset for fresh per-class counts and cache them
uv run pz_sankey --dataset-repo project-oceania/planktonzilla-17M --save-samples samples.json
# Name a different dataset on the page, with its version pinned instead of read from the Hub
uv run pz_sankey --dataset-name org/plankton-9K --dataset-version v1.2
# Fully offline: no font/logo fetch and no Hub lookup
uv run pz_sankey --no-assetsThe page names the dataset it describes, links it back to the Hub, and stamps its own provenance — dataset version, revision and the UTC build time — so a downloaded copy still says which data it came from and when.
Everything in the page recomputes in the browser: show or hide any column, pick the dimension that colours the ribbons, drag the merge threshold slider to pool small classes into a grey Other node per column, click any node to focus on that branch, and search for a taxon. Flow is conserved at every node on every change.
Whatever view is on screen exports three ways: SVG and PNG (both carrying the embedded
Inria typefaces, so they travel), and Mermaid — a .mmd file of sankey-beta source with
the same nodes, links and weights as text, ready to paste into any Markdown that renders
Mermaid. Names that would collide there (each column's pooled Other, a taxon a rank reuses)
are qualified by column, since Mermaid identifies a node by the string it prints.
# Basic training with default configuration
uv run pz_train
# Train with specific dataset and model
uv run pz_train dataset=isiisnet model=resnet18
# Use specialized loss for imbalanced data
uv run pz_train dataset=isiisnet model=resnet50 custom_loss=focal
# Override training parameters
uv run pz_train dataset=isiisnet model=resnet18 training_arguments.num_train_epochs=10 training_arguments.learning_rate=1e-4Planktonzilla uses Hydra for hierarchical configuration management. You can override any configuration parameter:
# Use different model architecture
uv run pz_train model=efficientnet
# Apply different augmentation strategy
uv run pz_train augmentation=autoaugment
# Combine multiple overrides
uv run pz_train dataset=isiisnet model=resnet50 custom_loss=ldam training_arguments.learning_rate=1e-4The training pipeline composes Hydra-configured datasets, models, and losses through the Hugging Face Trainer, then publishes the resulting checkpoint to the Hub — where external users load it with AutoModelForImageClassification.from_pretrained.
flowchart TB
subgraph Configure["1 · Configure"]
direction TB
CLI["CLI<br/>pz_import_dataset · pz_train"]:::entry
CFG["Hydra configs<br/>configs/"]:::cfg
end
subgraph Ingest["2 · Ingest"]
direction TB
DATA_IMPORT["planktonzilla/dataset_import/<br/>DatasetImporter subclasses"]:::code
HF_DATA[("HF Hub<br/>project-oceania datasets")]:::ext
end
subgraph Train["3 · Train"]
direction TB
DATA["planktonzilla/dataset.py<br/>DatasetWrapper"]:::code
MODEL["Model<br/>timm · HF · open_clip"]:::code
LOSS["planktonzilla/loss.py<br/>AbstractHFLoss subclasses"]:::code
TRAIN_LOOP["HF Trainer<br/>planktonzilla/train.py"]:::code
TRACK["Tracking<br/>W&B · MLflow · trackio"]:::ext
OUTPUTS["Local outputs<br/>logs/ · checkpoints/"]:::code
end
subgraph Publish["4 · Publish"]
direction TB
HF_MODEL[("HF Hub<br/>project-oceania models")]:::ext
end
SCRIPTS["scripts/*.sh<br/>SLURM launchers"]:::code
TESTS["tests/<br/>smoke runs"]:::code
CONSUMER(["AutoModelForImageClassification<br/>.from_pretrained"]):::consumer
SCRIPTS --> CLI
CLI --> CFG
CFG -.->|configures| DATA_IMPORT
CFG -.->|configures| TRAIN_LOOP
CFG -.->|selects| MODEL
CFG -.->|selects + params| LOSS
DATA_IMPORT --> HF_DATA
HF_DATA --> DATA
DATA --> TRAIN_LOOP
MODEL --> TRAIN_LOOP
LOSS --> TRAIN_LOOP
TRAIN_LOOP --> OUTPUTS
TRAIN_LOOP -.->|metrics| TRACK
TRAIN_LOOP --> HF_MODEL
HF_MODEL --> CONSUMER
TESTS -.->|smoke| TRAIN_LOOP
classDef entry fill:#27348b,stroke:#1b2461,color:#fff
classDef cfg fill:#e8eaf3,stroke:#27348b,color:#1b2461
classDef code fill:#f4f5f7,stroke:#8a8f98,color:#2b2f36
classDef ext fill:#fff3e0,stroke:#c9191e,color:#7a1013
classDef consumer fill:#ffffff,stroke:#2b2f36,color:#2b2f36,stroke-dasharray:4 3
Fifteen public plankton-imaging sources are assembled into planktonzilla-17M. A sixteenth,
FREPJ-Z, is in the build registry since v1.2 and enters the composite with the v1.2
release; until then it is published on its own as
project-oceania/planktonzilla-frepj.
Each has an importer config in configs/dataset_import/:
| Source | dataset value |
Images | Description | License |
|---|---|---|---|---|
| Global UVP5 | global_uvp5 |
7,414,467 | Underwater Vision Profiler 5, global deployment (largest contributor) | cc-by-4.0 |
| WHOI-Plankton | whoi |
3,563,595 | Woods Hole Oceanographic Institution IFCB imagery | mit |
| JEDI-Oceans | jedioceans |
1,915,882 | JEDI oceanic plankton (CPICS) | cc-by-sa-4.0 |
| ZooScanNet | zooscan |
1,451,745 | ZooScan scanned-sample plankton | cc-by-nc-4.0 |
| ZooCamNet | zoocamnet |
1,286,590 | ZooCam in-situ imaging | cc-by-4.0 |
| UVP6Net | uvp6net |
634,459 | Underwater Vision Profiler 6 | cc-by-nc-4.0 |
| ISIISNET | isiisnet |
408,166 | In-Situ Ichthyoplankton Imaging System Network | cc-by-nc-4.0 |
| FlowCamNet | flowcamnet |
301,247 | FlowCam imaging flow cytometry | cc-by-nc-4.0 |
| PlanktoScope | planktoscope |
179,720 | PlanktoScope open-hardware microscopy | cc-by-nc-4.0 |
| MedPlanktonSet | medplanktonset |
77,271 | Mediterranean plankton set | cc-by-4.0 |
| SYKE IFCB 2022 | syke_ifcb_2022 |
63,074 | Finnish Environment Institute, Imaging FlowCytobot | cc-by-4.0 |
| PlanktonSet 1.0 | planktonset1.0 |
60,736 | NOAA/Kaggle PlanktonSet | other |
| SYKE ZooScan 2024 | sykezooscan2024 |
22,753 | Finnish Environment Institute, ZooScan | cc-by-4.0 |
| ZooLake | zoolake |
17,942 | Lake Greifensee (Switzerland) zooplankton | cc-by-4.0 |
| Lensless | lensless |
6,400 | Lensless plankton microscopy (lab culture) | cc-by-4.0 |
| FREPJ-Z (v1.2) | frepj |
88,686 | Freshwater zooplankton of Japanese lakes and reservoirs, 40×/100× microscopy — registry only, not in the published 17M yet | cc-by-4.0 |
Note that the dataset column value does not always match the importer config stem (whoi vs
whoi-plankton.yaml, zooscan vs zooscannet.yaml, and three more). The mapping is recorded in
constants.DATASET_IMPORT_CONFIGS.
For training, configs/dataset/ selects either the composite planktonzilla dataset or a single
source; CIFAR-10 is also configured there as a generic sanity-check/smoke-test target.
The authority on this is the dataset's own
LICENSE.md
on the Hub, not this repository — read it before redistributing anything. It licenses the corpus in
three layers: each image keeps its source collection's licence with no aggregate override; the
planktonzilla contributions (harmonised taxonomy, derived metadata, splits, docs, scripts) are
CC BY 4.0; and the compilation itself, including any sui generis database right, is CC0 1.0.
planktonzilla-17M aggregates sources under five different sets of terms, so no single license
can lawfully cover it — it holds both share-alike and non-commercial material, and those conditions
are mutually incompatible. Every image therefore carries its source's terms in two columns —
license (the slug, verbatim from that source's importer config) and license_url (where those
terms are stated).
The shares below describe the published dataset. The registry covers all 15 of its
sources plus frepj (v1.2), so a from-scratch rebuild reproduces the same mix of terms —
frepj adds cc-by-4.0 images only.
| License | Images | Share | Reuse |
|---|---|---|---|
cc-by-4.0 |
8,870,555 | 50.97% | attribution |
mit |
3,563,595 | 20.48% | attribution — but see KI-14 |
cc-by-nc-4.0 |
2,975,337 | 17.10% | attribution, non-commercial only |
cc-by-sa-4.0 |
1,915,882 | 11.01% | attribution, share-alike |
other |
60,736 | 0.35% | unstated — see KI-15 |
cc0-1.0 |
17,942 | 0.10% | public domain — no attribution required |
The practical consequence: 17.1% of the corpus may not be used commercially and a further 11.0% imposes share-alike on derivatives. Filter before you train:
from datasets import load_dataset
ds = load_dataset("project-oceania/planktonzilla-17M", split="train")
commercial = ds.filter(lambda row: row["license"] in {"cc-by-4.0", "mit", "cc0-1.0"}) # 12,452,092 imagesTwo entries deserve a second look before you rely on them — whoi (mit is the license of a
code repository, and it covers a fifth of the corpus) and planktonset1.0 (other states
nothing at all). Both are recorded exactly as their importer config states them, and both are
written up as KI-14 / KI-15 in
KNOWN_ISSUES.md.
The slugs live in constants.DATASET_LICENSES, transcribed from configs/dataset_import/*.yaml,
which stay the upstream source of truth: tests/test_dataset_licenses.py fails if the two ever
drift apart, or if a source in the published dataset has no recorded terms.
Planktonzilla includes specialized loss functions designed for imbalanced plankton classification:
- FocalLoss: Addresses class imbalance through dynamic loss weighting
- LDAMLoss: Label-Distribution-Aware Margin loss
- AsymmetricLoss: For multi-label classification scenarios
- RobustAsymmetricLoss: Enhanced version of asymmetric loss
- MaximumMarginLoss: Margin-based learning approach
- BalancedMetaSoftmaxLoss: Meta-learning approach for class balance
Integrate with popular experiment tracking tools:
# Enable Weights & Biases tracking
uv run pz_train tracking.use_wandb=true
# Enable MLflow tracking
uv run pz_train tracking.use_mlflow=true
# Enable Trackio
uv run pz_train tracking.use_trackio=true# Run all tests
uv run pytest
# What CI runs — skips the slow HF Trainer / Hub integration matrices
uv run pytest tests/ --ignore=tests/test_train.py --ignore=tests/test_datasets.py
# Run with coverage
uv run pytest --cov=planktonzilla
# Run specific test file
uv run pytest tests/test_datasets.pyAll tests mock the network: no run reaches NCBI, Wikidata, WHOI, EcoTaxa or the Hugging Face Hub.
# Lint and format — the paths CI checks
uv run ruff check planktonzilla/ tests/
uv run ruff format planktonzilla/ tests/Pass the paths explicitly: notebooks/ is inside ruff's include (so notebooks can be linted
on demand) but CI checks only planktonzilla/ and tests/, and the notebooks are not currently
clean.
tests/test_dependency_isolation.py asserts that heavy visualization packages (gradio,
plotly, kaleido) appear in no dependency group and at no module scope under
planktonzilla/. pz_sankey renders its own SVG and embeds its own assets, so the training and
dataset core stays free of a viz stack. Function-local imports remain compliant if an opt-in
surface is ever reintroduced.
- Create a dataset configuration in
configs/dataset/your_dataset.yaml - Ensure your dataset is available on Hugging Face Hub
- Test with:
uv run pz_train dataset=your_dataset
- Implement your loss class inheriting from
AbstractHFLossinplanktonzilla/loss.py - Add configuration file in
configs/custom_loss/your_loss.yaml - Loss functions must handle
ImageClassifierOutputWithNoAttentioninput format - Test with:
uv run pz_train custom_loss=your_loss