{"id":51561771,"url":"https://github.com/dmis-lab/mmpl","last_synced_at":"2026-07-10T11:01:52.698Z","repository":{"id":367026232,"uuid":"1269859930","full_name":"dmis-lab/MMPL","owner":"dmis-lab","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-24T08:46:18.000Z","size":14,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-24T10:32:56.231Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmis-lab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-15T06:58:06.000Z","updated_at":"2026-06-24T08:46:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dmis-lab/MMPL","commit_stats":null,"previous_names":["dmis-lab/mmpl"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/dmis-lab/MMPL","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmis-lab%2FMMPL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmis-lab%2FMMPL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmis-lab%2FMMPL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmis-lab%2FMMPL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmis-lab","download_url":"https://codeload.github.com/dmis-lab/MMPL/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmis-lab%2FMMPL/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35329609,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-10T02:00:06.465Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-10T11:01:51.183Z","updated_at":"2026-07-10T11:01:52.692Z","avatar_url":"https://github.com/dmis-lab.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MMPL\nMarginal Constrained Morphological Prototype Learning for Patch Search in Whole Slide Images\n\n## Repository structure\n\n```\n.\n├── config/\n│   └── camelyon16_r50.yaml         # Experiment config (OmegaConf YAML)\n├── scripts/\n│   └── run_mil_camelyon.sh         # Convenience launcher\n├── sample_data/\n│   └── CAMELYON16/                 # Sample label / split files (format reference)\n│       ├── CAMELYON16_all_labels.csv\n│       └── CAMELYON16_split_train_val_test.csv\n├── src/\n│   ├── dataset.py                  # Bag dataset (.h5 features or .pkl raw JPEG bytes)\n│   ├── modeling.py                 # MMPL model, encoders, Sinkhorn-Knopp\n│   ├── training.py                 # Trainer (train / validate / test loops)\n│   ├── utils.py                    # Metrics, logging, checkpoint saver\n│   └── main.py                     # Entry point\n├── requirements.txt\n└── README.md\n```\n\n## Installation\n\n```bash\nconda create -n mmpl python=3.9 -y\nconda activate mmpl\n\n# Install PyTorch built for your CUDA version (CUDA 12.1 shown here):\npip install torch==2.5.1 torchvision==0.20.1 --index-url https://download.pytorch.org/whl/cu121\n\npip install -r requirements.txt\n```\n\n\u003e `PyTurboJPEG` is only required when training end-to-end from raw patch bytes\n\u003e (`.pkl` inputs). It depends on the system `libturbojpeg` library\n\u003e (`apt-get install libturbojpeg`). It can be skipped when using precomputed features.\n\n## Data\n\n### Preprocessing\n\nPatch extraction and feature embedding follow the **ZoomMIL** preprocessing pipeline:\n\u003chttps://github.com/histocartography/zoommil\u003e. We use it to tessellate each WSI into\npatches and embed them with a ResNet50 encoder, storing the resulting feature vectors\nper slide.\n\nAlternatively, the widely used **CLAM** toolkit (\u003chttps://github.com/mahmoodlab/CLAM\u003e)\ncan be used for patch extraction and feature embedding; just make sure the resulting\nfeatures are saved per slide under the dataset column expected by the config\n(`data.dataset_column`, default `20.0x_patches`).\n\n### Input format\n\nEach slide is a *bag* of patches. Two input formats are supported:\n\n- **`.h5`** — precomputed patch feature vectors stored under the dataset column\n  (default `20.0x_patches`). This is the default mode (`model.backbone: \"None\"`).\n- **`.pkl`** — raw JPEG bytes of the patches, for end-to-end training with a learnable\n  vision encoder (`model.backbone: \"ResNet50\"` or `\"UNI\"`).\n\nAll slides live in a single directory, one file per slide (`\u003cslide_id\u003e.h5` for\nprecomputed features, or `\u003cslide_id\u003e.pkl` for raw JPEG patches). Two CSV files describe\nthe labels and the splits:\n\n- `*_all_labels.csv` — indexed by `slide_id`, with a `type` column holding the class id.\n- `*_split_train_val_test.csv` — `train` / `val` / `test` columns listing slide names.\n\nSample files in this exact format are provided under\n[`sample_data/CAMELYON16/`](sample_data/CAMELYON16) for reference:\n\n```csv\n# CAMELYON16_all_labels.csv\nslide_id,type\nnormal_001,0\ntumor_001,1\n...\n```\n\n```csv\n# CAMELYON16_split_train_val_test.csv\ntrain,val,test\nnormal_001,normal_002,normal_003\ntumor_001,tumor_002,tumor_003\n```\n\n\u003e The sample directory ships only the label/split CSVs as a format reference; the\n\u003e corresponding `.h5` feature files are not included. Generate them with the ZoomMIL\n\u003e pipeline above and place them in the directory pointed to by `data.directory`.\n\nPoint the config at your files:\n\n```yaml\ndata:\n  directory: ./data/CAMELYON16/features-r50\n  dataset_splits: ./data/CAMELYON16/CAMELYON16_split_train_val_test.csv\n  dataset_labels: ./data/CAMELYON16/CAMELYON16_all_labels.csv\n  dataset_column: 20.0x_patches\n```\n\n### End-to-end training (raw JPEG patches)\n\nThe default mode (`model.backbone: \"None\"`) consumes **precomputed** feature vectors and\nkeeps the vision encoder frozen. To instead train the vision encoder **end to end**\n(`model.backbone: \"ResNet50\"` or `\"UNI\"`), the patches must be stored as **raw JPEG\nbytes** rather than features, so the encoder can decode and embed them on the fly. This\nlets the encoder be trained while only the retrieved patches are decoded each step,\navoiding the cost of processing every patch in a slide.\n\nIn this case, save the JPEG-encoded patches per slide as `\u003cslide_id\u003e.pkl` (keyed by\n`data.dataset_column`). At runtime these bytes are decoded with TurboJPEG (hence the\n`PyTurboJPEG` dependency) and embedded by the vision encoder. Point `data.directory` at\nthe folder of `.pkl` files and set `model.backbone` accordingly.\n\n## Training\n\nRun directly:\n\n```bash\nCUDA_VISIBLE_DEVICES=0 python src/main.py config/camelyon16_r50.yaml experiment.seed=0\n```\n\nOr use the launcher script (which accepts `GPU`, `SEEDS`, and `CONFIG` env vars):\n\n```bash\nGPU=0 SEEDS=\"0 1\" bash scripts/run_mil_camelyon.sh\n```\n\nAny config field can be overridden from the command line via OmegaConf dotlist syntax:\n\n```bash\npython src/main.py config/camelyon16_r50.yaml \\\n    model.num_prototypes=15 model.retrieve_k=100 experiment.seed=0\n```\n\nTraining runs validation each epoch, saves the best checkpoint (by\n`experiment.save_metric`) plus the latest one to `experiment.directory`, and finally\nevaluates on the test set, writing the scores to a `*_test_score.json` file.\n\n### Key configuration options\n\n| Field | Description |\n| --- | --- |\n| `model.backbone` | `\"None\"` (precomputed features), `\"ResNet50\"`, or `\"UNI\"` |\n| `model.num_prototypes` | Number of learnable prototypes |\n| `model.retrieve_k` | Total retrieval budget (patches selected per slide) |\n| `model.queue_size` | Size of the feature queue for Sinkhorn-Knopp |\n| `model.sinkhorn_weight` | Weight of the Sinkhorn pseudo-label loss |\n| `model.temperature` | Temperature for the prototype assignment loss |\n| `experiment.resume` | Resume from a checkpoint in `experiment.directory` |\n\n### Experiment tracking\n\nWeights \u0026 Biases logging is disabled by default (`wandb.use: false`). Set it to `true`\nin the config (and `wandb login`) to enable it.\n\n## Notes\n\n- The `UNI` backbone downloads gated weights from Hugging Face. Authenticate first via\n  `huggingface-cli login` or by exporting `HF_TOKEN` before running.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmis-lab%2Fmmpl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmis-lab%2Fmmpl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmis-lab%2Fmmpl/lists"}