From e93da21df71cd317c0ed60091b4f5db058cc1892 Mon Sep 17 00:00:00 2001 From: luandalmazo Date: Mon, 1 Jun 2026 11:06:50 -0300 Subject: [PATCH 1/2] Add DeepLabV3Plus one-line trainer (DAT-896) --- .gitignore | 5 +- datamint/lightning/__init__.py | 2 + datamint/lightning/trainers/__init__.py | 2 + .../trainers/lightning_modules/__init__.py | 4 +- .../segmentation_modules/__init__.py | 3 +- .../segmentation_modules/deeplabv3plus.py | 72 +++ .../trainers/specialized/deeplabv3plus.py | 153 +++++ ..._trainer_BUSI_deeplabv3plus_tutorial.ipynb | 574 ++++++++++++++++++ ..._trainer_BUSI_unetplusplus_tutorial.ipynb} | 0 9 files changed, 811 insertions(+), 4 deletions(-) create mode 100644 datamint/lightning/trainers/lightning_modules/segmentation_modules/deeplabv3plus.py create mode 100644 datamint/lightning/trainers/specialized/deeplabv3plus.py create mode 100644 notebooks/use_cases/segmentation_2d_trainer_BUSI_deeplabv3plus_tutorial.ipynb rename notebooks/use_cases/{segmentation_2d_trainer_BUSI_tutorial.ipynb => segmentation_2d_trainer_BUSI_unetplusplus_tutorial.ipynb} (100%) diff --git a/.gitignore b/.gitignore index f2ee2044..64035f71 100644 --- a/.gitignore +++ b/.gitignore @@ -112,4 +112,7 @@ junit/ dev/ # vscode -.vscode/ \ No newline at end of file +.vscode/ + +# claude +CLAUDE.md diff --git a/datamint/lightning/__init__.py b/datamint/lightning/__init__.py index d1df3069..97b6bc65 100644 --- a/datamint/lightning/__init__.py +++ b/datamint/lightning/__init__.py @@ -9,6 +9,7 @@ SemanticSegmentation3DTrainer, SegmentationTrainer, UNetPPTrainer, + DeepLabV3PlusTrainer, ) __all__ = [ @@ -20,4 +21,5 @@ "SemanticSegmentation3DTrainer", "SegmentationTrainer", "UNetPPTrainer", + "DeepLabV3PlusTrainer", ] diff --git a/datamint/lightning/trainers/__init__.py b/datamint/lightning/trainers/__init__.py index e2822441..ae8c1dd7 100644 --- a/datamint/lightning/trainers/__init__.py +++ b/datamint/lightning/trainers/__init__.py @@ -6,6 +6,7 @@ from .seg3d_trainer import SemanticSegmentation3DTrainer from .classification_trainer import ClassificationTrainer, ImageClassificationTrainer from .specialized.unetpp import UNetPPTrainer +from .specialized.deeplabv3plus import DeepLabV3PlusTrainer __all__ = [ "BaseTrainer", @@ -13,6 +14,7 @@ "SemanticSegmentation2DTrainer", "SemanticSegmentation3DTrainer", "UNetPPTrainer", + "DeepLabV3PlusTrainer", "ClassificationTrainer", "ImageClassificationTrainer", ] diff --git a/datamint/lightning/trainers/lightning_modules/__init__.py b/datamint/lightning/trainers/lightning_modules/__init__.py index f000be50..2b382e74 100644 --- a/datamint/lightning/trainers/lightning_modules/__init__.py +++ b/datamint/lightning/trainers/lightning_modules/__init__.py @@ -1,6 +1,6 @@ from .base import DatamintLightningModule from .segmentation_module import SegmentationModule -from .segmentation_modules import SMPSegmentationModule, UNetPPModule +from .segmentation_modules import SMPSegmentationModule, UNetPPModule, DeepLabV3PlusModule from .classification_module import ClassificationModule -__all__ = ["DatamintLightningModule", "SegmentationModule", "SMPSegmentationModule", "UNetPPModule", "ClassificationModule"] +__all__ = ["DatamintLightningModule", "SegmentationModule", "SMPSegmentationModule", "UNetPPModule", "DeepLabV3PlusModule", "ClassificationModule"] diff --git a/datamint/lightning/trainers/lightning_modules/segmentation_modules/__init__.py b/datamint/lightning/trainers/lightning_modules/segmentation_modules/__init__.py index 1da35006..d091c6c4 100644 --- a/datamint/lightning/trainers/lightning_modules/segmentation_modules/__init__.py +++ b/datamint/lightning/trainers/lightning_modules/segmentation_modules/__init__.py @@ -1,4 +1,5 @@ from .smp_module import SMPSegmentationModule from .unetpp import UNetPPModule +from .deeplabv3plus import DeepLabV3PlusModule -__all__ = ["SMPSegmentationModule", "UNetPPModule"] +__all__ = ["SMPSegmentationModule", "UNetPPModule", "DeepLabV3PlusModule"] diff --git a/datamint/lightning/trainers/lightning_modules/segmentation_modules/deeplabv3plus.py b/datamint/lightning/trainers/lightning_modules/segmentation_modules/deeplabv3plus.py new file mode 100644 index 00000000..9f54aca7 --- /dev/null +++ b/datamint/lightning/trainers/lightning_modules/segmentation_modules/deeplabv3plus.py @@ -0,0 +1,72 @@ +"""DeepLab v3+ segmentation module.""" +from __future__ import annotations +from collections.abc import Callable +from typing import Any + +import albumentations as A +from torch import Tensor, nn +from typing_extensions import override + +import segmentation_models_pytorch as smp + +from .smp_module import SMPSegmentationModule + + +class DeepLabV3PlusModule(SMPSegmentationModule): + """Segmentation module using the DeepLab v3+ architecture from ``segmentation_models_pytorch``. + + Args: + in_channels: Number of input image channels. + num_classes: Number of segmentation classes excluding background. + loss_fn: Loss module. + metrics_factories: ``{name: callable}`` where each callable returns a fresh metric. + class_names: Human-readable label for each class. + image_size: ``(height, width)`` used during inference. + lr: Learning rate for AdamW. + encoder_name: SMP encoder backbone (e.g. ``'resnet34'``, ``'efficientnet-b4'``). + encoder_weights: Pre-trained weights to load. ``'imagenet'`` by default. + decoder_atrous_rates: Dilation rates for the ASPP module. + Controls the multi-scale receptive field that is DeepLab v3+'s + core architectural feature. SMP default is ``(12, 24, 36)``. + transform: Albumentations transform applied during inference. + """ + + def __init__( + self, + in_channels: int, + num_classes: int, + loss_fn: nn.Module | None = None, + metrics_factories: dict[str, Callable[[], Any]] = {}, + class_names: list[str] | None = None, + image_size: tuple[int, int] | None = None, + lr: float = 1e-4, + encoder_name: str = 'resnet34', + encoder_weights: str | None = 'imagenet', + decoder_atrous_rates: tuple[int, int, int] = (12, 24, 36), + transform: A.BasicTransform | A.BaseCompose | None = None, + ) -> None: + self.in_channels = in_channels + self.num_classes = num_classes + self.encoder_name = encoder_name + self.encoder_weights = encoder_weights + self.decoder_atrous_rates = decoder_atrous_rates + + super().__init__( + transform=transform, + loss_fn=loss_fn, + metrics_factories=metrics_factories, + class_names=class_names, + lr=lr, + ) + + self.model = smp.DeepLabV3Plus( + encoder_name=self.encoder_name, + encoder_weights=self.encoder_weights, + in_channels=self.in_channels, + classes=self.num_classes, + decoder_atrous_rates=self.decoder_atrous_rates, + ) + + @override + def forward(self, x: Tensor) -> Tensor: + return self.model(x) diff --git a/datamint/lightning/trainers/specialized/deeplabv3plus.py b/datamint/lightning/trainers/specialized/deeplabv3plus.py new file mode 100644 index 00000000..1cafa878 --- /dev/null +++ b/datamint/lightning/trainers/specialized/deeplabv3plus.py @@ -0,0 +1,153 @@ +from collections.abc import Callable +from typing import Any, TYPE_CHECKING + +import lightning as L +from torch import nn +from typing_extensions import override + +from ..lightning_modules import DeepLabV3PlusModule +from ..seg2d_trainer import SemanticSegmentation2DTrainer + +if TYPE_CHECKING: + from albumentations import BaseCompose + from datamint.dataset.base import DatamintBaseDataset + from datamint.entities import Project + from medimgkit import ViewPlane + from datamint.lightning.trainers.lightning_modules.base import DatamintLightningModule + + +class DeepLabV3PlusTrainer(SemanticSegmentation2DTrainer): + """Convenience trainer pre-configured for DeepLab v3+. + + Uses the ASPP-based DeepLab v3+ architecture from + ``segmentation_models_pytorch``. The ``decoder_atrous_rates`` parameter + controls the dilation rates of the Atrous Spatial Pyramid Pooling module, + which is DeepLab v3+'s core multi-scale context mechanism. + + Example:: + + trainer = DeepLabV3PlusTrainer( + project='BUS_Segmentation', + encoder_name='resnet50', + ) + results = trainer.fit() + """ + + def __init__( + self, + dataset: 'DatamintBaseDataset | None' = None, + project: 'str | Project | None' = None, + *, + image_size: int | tuple[int, int] | None = None, + slice_axis: 'ViewPlane | int | None' = None, + model: L.LightningModule | type[L.LightningModule] | None = None, + in_channels: int = 3, + loss_fn: nn.Module | None = None, + batch_size: int = 16, + num_workers: int = 4, + train_transform: 'BaseCompose | None' = None, + eval_transform: 'BaseCompose | None' = None, + split_as_of_timestamp: str | None = None, + max_epochs: int = 1, + early_stopping_patience: int | None = 10, + mlflow_experiment_name: str | None = None, + register_model_name: str | None = None, + auto_deploy_adapter: bool = True, + trainer_kwargs: dict[str, Any] | None = None, + dataset_kwargs: dict[str, Any] | None = None, + # DeepLab v3+ specific: + encoder_name: str = 'resnet34', + decoder_atrous_rates: tuple[int, int, int] = (12, 24, 36), + **kwargs: Any, + ) -> None: + """ + Builds a DeepLab v3+ trainer with sensible defaults for segmentation tasks. + + Args: + dataset: A pre-built :class:`DatamintBaseDataset`. Mutually + exclusive with *project*. + project: Project name or :class:`Project` object used to + auto-build a dataset when *dataset* is ``None``. + model: A user-provided :class:`~lightning.LightningModule`. + When ``None`` the trainer builds a default one via + :meth:`_build_model`. + loss_fn: Custom loss function forwarded to the default model. + Ignored when *model* is provided. + batch_size: Training batch size. + num_workers: DataLoader workers. + train_transform: Albumentations transform for training. When + ``None`` the trainer uses :meth:`_train_transform`. + eval_transform: Albumentations transform for val/test. When + ``None`` the trainer uses :meth:`_eval_transform`. + image_size: Target image size ``(H, W)`` or a single int for + square images. Forwarded to default transforms. + split_as_of_timestamp: Historical timestamp used to resolve + project-scoped dataset splits during training. + max_epochs: Maximum number of training epochs. + early_stopping_patience: Epochs without improvement before + stopping. Set to ``None`` to disable early stopping. + mlflow_experiment_name: MLflow experiment name. Auto-generated + from the project name when ``None``. + register_model_name: Name for MLflow Model Registry. + Auto-generated when ``None``. + auto_deploy_adapter: When ``True``, auto-generate a + :class:`~datamint.mlflow.flavors.model.DatamintModel` + adapter after training. + trainer_kwargs: Extra keyword arguments forwarded to + :class:`lightning.Trainer`. + encoder_name: SMP encoder backbone name (e.g. ``'resnet34'``, + ``'resnet50'``, ``'efficientnet-b4'``). + decoder_atrous_rates: Dilation rates for the ASPP module. + Controls the multi-scale receptive field sizes. Defaults to + ``(12, 24, 36)``. Smaller values capture finer-grained context; + larger values capture coarser context. + """ + super().__init__( + dataset=dataset, + project=project, + model=model, + in_channels=in_channels, + image_size=image_size, + slice_axis=slice_axis, + loss_fn=loss_fn, + batch_size=batch_size, + num_workers=num_workers, + train_transform=train_transform, + eval_transform=eval_transform, + split_as_of_timestamp=split_as_of_timestamp, + max_epochs=max_epochs, + early_stopping_patience=early_stopping_patience, + mlflow_experiment_name=mlflow_experiment_name, + register_model_name=register_model_name, + auto_deploy_adapter=auto_deploy_adapter, + trainer_kwargs=trainer_kwargs, + dataset_kwargs=dataset_kwargs, + **kwargs, + ) + self.encoder_name = encoder_name + self.decoder_atrous_rates = decoder_atrous_rates + + @override + def _build_model( + self, + loss_fn: nn.Module, + metrics: dict[str, Callable], + ) -> 'DatamintLightningModule': + num_classes = len(self.dataset.seglabel_list) + if num_classes == 0: + raise ValueError( + "No segmentation labels found in the dataset. " + "DeepLabV3Plus requires at least one segmentation label to train. " + "Make sure your project has annotated resources with segmentation labels, " + "or check that 'include_unannotated' is not masking all annotated data." + ) + return DeepLabV3PlusModule( + encoder_name=self.encoder_name, + in_channels=self.in_channels, + num_classes=num_classes, + loss_fn=loss_fn, + metrics_factories=metrics, + class_names=list(self.dataset.seglabel_list), + image_size=self.image_size, + decoder_atrous_rates=self.decoder_atrous_rates, + ) diff --git a/notebooks/use_cases/segmentation_2d_trainer_BUSI_deeplabv3plus_tutorial.ipynb b/notebooks/use_cases/segmentation_2d_trainer_BUSI_deeplabv3plus_tutorial.ipynb new file mode 100644 index 00000000..0b1bc16d --- /dev/null +++ b/notebooks/use_cases/segmentation_2d_trainer_BUSI_deeplabv3plus_tutorial.ipynb @@ -0,0 +1,574 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0263cedb", + "metadata": {}, + "source": [ + "# 2D Segmentation with the Datamint Trainer API\n", + "\n", + "This notebook shows how to train a **DeepLab V3+ semantic segmentation** model on the **BUSI** (Breast Ultrasound Images) dataset using Datamint's **Trainer API** — a high-level wrapper that handles dataset loading, model creation, training, experiment tracking, and model registration from a small amount of code.\n", + "\n", + "## Comparison with the manual Workflow\n", + "\n", + "The conventional way requires you to:\n", + "1. Define transforms, loss function, metrics, and a full LightningModule (~150 lines)\n", + "2. Configure MLflow logger, callbacks, and Trainer (~50 lines)\n", + "3. Build and wire together `DatamintDataModule`, `L.Trainer`, etc.\n", + "\n", + "With the Trainer API, **all of that is replaced by ~3 lines**:\n", + "\n", + "```python\n", + "from datamint.lightning.trainers import DeepLabV3PlusTrainer\n", + "\n", + "trainer = DeepLabV3PlusTrainer(project='MyProject')\n", + "results = trainer.fit()\n", + "```\n", + "\n", + "## What You'll Learn\n", + "\n", + "1. Upload data to Datamint\n", + "2. Train with `DeepLabV3PlusTrainer` using **zero configuration**\n", + "3. Train with `SemanticSegmentation2DTrainer` while swapping in a custom external model\n", + "4. Visualise predictions\n", + "5. Register and deploy the trained model\n", + "\n", + "## Required Dependencies\n", + "\n", + "```bash\n", + "pip install datamint\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "ca7b0298", + "metadata": {}, + "outputs": [], + "source": [ + "from datamint import Api\n", + "\n", + "PROJECT_NAME = \"deeplabv3plus_Segmentation_Tutorial\"\n", + "api = Api()" + ] + }, + { + "cell_type": "markdown", + "id": "19abf11f", + "metadata": {}, + "source": [ + "## 1. Setup: Create Project\n", + "\n", + "Create (or retrieve) a Datamint project for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "af13262e", + "metadata": {}, + "outputs": [ + { + "ename": "HTTPStatusError", + "evalue": "Client error '403 Forbidden' for url 'https://api.datamint.io/projects'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mHTTPStatusError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m proj = api.projects.create(\n\u001b[32m 2\u001b[39m name=PROJECT_NAME,\n\u001b[32m 3\u001b[39m description=\u001b[33m\"Tutorial project for DeepLabV3+ segmentation on BUSI dataset\"\u001b[39m,\n\u001b[32m 4\u001b[39m exists_ok=\u001b[38;5;28;01mTrue\u001b[39;00m \u001b[38;5;66;03m# Just return the existing project if it already exists\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/datamint/api/endpoints/projects_api.py:124\u001b[39m, in \u001b[36mProjectsApi.create\u001b[39m\u001b[34m(self, name, description, resources_ids, is_active_learning, two_up_display, segmentation_spec, return_entity, exists_ok)\u001b[39m\n\u001b[32m 111\u001b[39m project_data = {\u001b[33m'\u001b[39m\u001b[33mname\u001b[39m\u001b[33m'\u001b[39m: name,\n\u001b[32m 112\u001b[39m \u001b[33m'\u001b[39m\u001b[33mis_active_learning\u001b[39m\u001b[33m'\u001b[39m: is_active_learning,\n\u001b[32m 113\u001b[39m \u001b[33m'\u001b[39m\u001b[33mresource_ids\u001b[39m\u001b[33m'\u001b[39m: resources_ids,\n\u001b[32m (...)\u001b[39m\u001b[32m 120\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mrequire_review\u001b[39m\u001b[33m\"\u001b[39m: \u001b[38;5;28;01mFalse\u001b[39;00m,\n\u001b[32m 121\u001b[39m \u001b[33m'\u001b[39m\u001b[33mdescription\u001b[39m\u001b[33m'\u001b[39m: description}\n\u001b[32m 123\u001b[39m \u001b[38;5;66;03m# type: ignore[return-value]\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m124\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_create\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mproject_data\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mreturn_entity\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mreturn_entity\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mexists_ok\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mexists_ok\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/datamint/api/entity_base_api.py:336\u001b[39m, in \u001b[36mCreatableEntityApi._create\u001b[39m\u001b[34m(self, entity_data, return_entity, exists_ok)\u001b[39m\n\u001b[32m 317\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Create a new entity.\u001b[39;00m\n\u001b[32m 318\u001b[39m \n\u001b[32m 319\u001b[39m \u001b[33;03mArgs:\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 333\u001b[39m \u001b[33;03m *exists_ok* is ``False``).\u001b[39;00m\n\u001b[32m 334\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 335\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m336\u001b[39m response = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_make_request\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m'\u001b[39;49m\u001b[30;43mPOST\u001b[39;49m\u001b[30;43m'\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mf\u001b[39;49m\u001b[30;43m'\u001b[39;49m\u001b[30;43m/\u001b[39;49m\u001b[30;43;01m{\u001b[39;49;00m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mendpoint_base\u001b[39;49m\u001b[30;43;01m}\u001b[39;49;00m\u001b[30;43m'\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mjson\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mentity_data\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 337\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m httpx.HTTPStatusError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 338\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m exists_ok \u001b[38;5;129;01mand\u001b[39;00m e.response.status_code == \u001b[32m409\u001b[39m:\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/datamint/api/base_api.py:326\u001b[39m, in \u001b[36mBaseApi._make_request\u001b[39m\u001b[34m(self, method, endpoint, **kwargs)\u001b[39m\n\u001b[32m 324\u001b[39m logger.debug(\u001b[33m'\u001b[39m\u001b[33mEquivalent curl command: \u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m\"\u001b[39m\u001b[33m'\u001b[39m, curl_command)\n\u001b[32m 325\u001b[39m response = \u001b[38;5;28mself\u001b[39m.client.request(method, url, **kwargs)\n\u001b[32m--> \u001b[39m\u001b[32m326\u001b[39m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_check_errors_response_httpx\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mresponse\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43murl\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43murl\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 327\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m response\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/datamint/api/base_api.py:416\u001b[39m, in \u001b[36mBaseApi._check_errors_response_httpx\u001b[39m\u001b[34m(self, response, url)\u001b[39m\n\u001b[32m 414\u001b[39m response_json = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 415\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m416\u001b[39m \u001b[30;43mresponse\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mraise_for_status\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 417\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m httpx.ConnectError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 418\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mCERTIFICATE_VERIFY_FAILED\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mstr\u001b[39m(e) \u001b[38;5;129;01mor\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mcertificate verify failed\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mstr\u001b[39m(e).lower():\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/httpx/_models.py:829\u001b[39m, in \u001b[36mResponse.raise_for_status\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 827\u001b[39m error_type = error_types.get(status_class, \u001b[33m\"\u001b[39m\u001b[33mInvalid status code\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 828\u001b[39m message = message.format(\u001b[38;5;28mself\u001b[39m, error_type=error_type)\n\u001b[32m--> \u001b[39m\u001b[32m829\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m HTTPStatusError(message, request=request, response=\u001b[38;5;28mself\u001b[39m)\n", + "\u001b[31mHTTPStatusError\u001b[39m: Client error '403 Forbidden' for url 'https://api.datamint.io/projects'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403" + ] + } + ], + "source": [ + "proj = api.projects.create(\n", + " name=PROJECT_NAME,\n", + " description=\"Tutorial project for DeepLabV3+ segmentation on BUSI dataset\",\n", + " exists_ok=True # Just return the existing project if it already exists\n", + ")\n", + "proj" + ] + }, + { + "cell_type": "markdown", + "id": "a242d5cd", + "metadata": {}, + "source": [ + "## 2. Dataset Preparation: Download and Upload BUSI\n", + "\n", + "This section downloads the BUSI dataset and uploads it to Datamint.\n", + "If you already have the data inside Datamint, you can **skip to Section 3**.\n", + "\n", + "In this section, we will:\n", + "- Download the BUSI dataset\n", + "- Upload ultrasound images to Datamint\n", + "- Upload corresponding segmentation masks\n", + "- Create train/val/test splits\n", + "\n", + "### 2.1 Download BUSI Dataset\n", + "\n", + "The BUSI dataset is available from Kaggle. For this tutorial, we'll use the breast ultrasound images dataset.\n", + "\n", + "> Al-Dhabyani W, Gomaa M, Khaled H, Fahmy A. Dataset of breast ultrasound images. Data in Brief. 2020 Feb;28:104863. DOI: 10.1016/j.dib.2019.104863." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97351339", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import requests\n", + "import zipfile\n", + "from pathlib import Path\n", + "\n", + "BUSI_URL = \"https://www.kaggle.com/api/v1/datasets/download/sabahesaraki/breast-ultrasound-images-dataset\"\n", + "DATA_DIR = Path(\"/tmp/BUSI_dataset\")\n", + "\n", + "if not DATA_DIR.exists():\n", + " print(\"Downloading BUSI dataset...\")\n", + " response = requests.get(BUSI_URL, stream=True)\n", + " response.raise_for_status()\n", + " zip_path = DATA_DIR / \"Dataset_BUSI.zip\"\n", + " DATA_DIR.mkdir(parents=True, exist_ok=True)\n", + " with open(zip_path, 'wb') as f:\n", + " for chunk in response.iter_content(chunk_size=8192):\n", + " f.write(chunk)\n", + " print(\"Extracting...\")\n", + " with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n", + " zip_ref.extractall(DATA_DIR)\n", + " os.remove(zip_path)\n", + " print(\"Download complete!\")\n", + "else:\n", + " print(f\"Dataset already exists at {DATA_DIR}\")" + ] + }, + { + "cell_type": "markdown", + "id": "8e6bf5f7", + "metadata": {}, + "source": [ + "### 2.2 Find image and mask paths" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c0b6f5e", + "metadata": {}, + "outputs": [], + "source": [ + "base_dir = DATA_DIR / \"Dataset_BUSI_with_GT\"\n", + "classes = [\"benign\", \"malignant\", \"normal\"]\n", + "\n", + "image_paths = []\n", + "label_paths = []\n", + "\n", + "for cls in classes:\n", + " cls_dir = base_dir / cls\n", + " cls_images = sorted([p for p in cls_dir.glob(\"*.png\") if \"_mask\" not in p.name])\n", + " for img_p in cls_images:\n", + " mask_p = cls_dir / f\"{img_p.stem}_mask.png\"\n", + " if mask_p.exists():\n", + " image_paths.append(img_p)\n", + " label_paths.append(mask_p)\n", + "\n", + "print(f\"Found {len(image_paths)} images and {len(label_paths)} masks\")" + ] + }, + { + "cell_type": "markdown", + "id": "cedb5070", + "metadata": {}, + "source": [ + "### 2.3 Upload images and masks to Datamint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0221a1fb", + "metadata": {}, + "outputs": [], + "source": [ + "# Upload images\n", + "uploaded_resources = api.resources.upload_resources(\n", + " [str(p) for p in image_paths],\n", + " tags=['busi', 'ultrasound', 'breast'],\n", + " publish_to=proj,\n", + " progress_bar=True,\n", + ")\n", + "print(f\"Uploaded {len(uploaded_resources)} images\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "396f0f70", + "metadata": {}, + "outputs": [], + "source": [ + "from tqdm.auto import tqdm\n", + "\n", + "# Get resources from project\n", + "all_resources = list(api.resources.get_list(project_name=PROJECT_NAME, tags=['busi']))\n", + "filename_to_resource = {r.filename: r for r in all_resources}\n", + "\n", + "# Upload segmentation masks\n", + "for img_path, label_path in tqdm(zip(image_paths, label_paths), total=len(image_paths)):\n", + " if 'normal' in img_path.parent.name:\n", + " continue # Normal images have no lesion masks\n", + " resource = filename_to_resource[img_path.name]\n", + " cls_name = img_path.parent.name # 'benign' or 'malignant'\n", + "\n", + " api.annotations.upload_segmentations(\n", + " resource=resource,\n", + " file_path=label_path,\n", + " name=cls_name,\n", + " imported_from=\"Original GT BUSI Dataset\",\n", + " )\n", + "\n", + "print(\"Segmentation masks uploaded successfully!\")" + ] + }, + { + "cell_type": "markdown", + "id": "bdc37407", + "metadata": {}, + "source": [ + "### 2.4 Tag train/val/test splits\n", + "\n", + "We split the dataset into three subsets using tags for reproducibility.\n", + "\n", + "| Split | Percentage | Purpose |\n", + "|-------|------------|---------|\n", + "| Train | 70% | Model training |\n", + "| Validation | 15% | Hyperparameter tuning, early stopping |\n", + "| Test | 15% | Final model evaluation |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba063034", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "\n", + "all_resources = list(api.resources.get_list(project_name=PROJECT_NAME, tags=['busi']))\n", + "all_resources.sort(key=lambda r: r.filename)\n", + "\n", + "random.seed(42)\n", + "random.shuffle(all_resources)\n", + "\n", + "n_total = len(all_resources)\n", + "n_train = int(0.7 * n_total)\n", + "n_val = int(0.15 * n_total)\n", + "\n", + "train_resources = all_resources[:n_train]\n", + "val_resources = all_resources[n_train:n_train + n_val]\n", + "test_resources = all_resources[n_train + n_val:]\n", + "\n", + "api.projects.assign_splits(proj, train_resources, split_name='train')\n", + "api.projects.assign_splits(proj, val_resources, split_name='val')\n", + "api.projects.assign_splits(proj, test_resources, split_name='test')\n", + "\n", + "print(f\"Train: {len(train_resources)}, Val: {len(val_resources)}, Test: {len(test_resources)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "b2e2f571", + "metadata": {}, + "source": [ + "## 3. Training with the Trainer API\n", + "\n", + "`DeepLabV3PlusTrainer` wraps the full training pipeline in a single object. It automatically:\n", + "\n", + "1. Builds an `ImageDataset` from the project (or a sliced 2D view if the project contains 3D volumes)\n", + "2. Applies standard augmentations — flips, brightness/contrast, ImageNet normalisation\n", + "3. Instantiates a **DeepLab v3+** model with an ImageNet-pretrained encoder\n", + "4. Sets up **BCE + Dice loss**, **Mean IoU** and **Generalised Dice** metrics\n", + "5. Configures MLflow logging, early stopping, and model checkpointing\n", + "6. Trains, validates, and evaluates on the test split\n", + "\n", + "The key architecture-specific parameter is `decoder_atrous_rates`, which controls the dilation rates of the **ASPP** (Atrous Spatial Pyramid Pooling) module — DeepLab v3+'s mechanism for capturing multi-scale context. The default `(12, 24, 36)` works well for most medical imaging tasks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "572fe5fd", + "metadata": {}, + "outputs": [], + "source": [ + "from datamint.lightning import DeepLabV3PlusTrainer\n", + "\n", + "\n", + "trainer = DeepLabV3PlusTrainer(\n", + " project=PROJECT_NAME,\n", + " image_size=256,\n", + " batch_size=16,\n", + " max_epochs=12,\n", + " accelerator='auto',\n", + " # register_model_name='MyModelName' # Default is project name\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4d11db7", + "metadata": {}, + "outputs": [], + "source": [ + "results = trainer.fit()" + ] + }, + { + "cell_type": "markdown", + "id": "496bf458", + "metadata": {}, + "source": [ + "That's it!\n", + "\n", + "Let's inspect the results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8313df73", + "metadata": {}, + "outputs": [], + "source": [ + "# Inspect the results dictionary\n", + "print(\"Test results:\")\n", + "for metric_dict in results['test_results']:\n", + " for k, v in metric_dict.items():\n", + " print(f\" {k}: {v:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "e1eab625", + "metadata": {}, + "source": [ + "## 4. Visualize predictions\n", + "\n", + "Let's visualize the model predictions against ground truth on test samples." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a20cc9f2", + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import numpy as np\n", + "from matplotlib import pyplot as plt\n", + "from datamint.utils.visualization import show, draw_masks\n", + "from torchmetrics.functional.segmentation import mean_iou\n", + "\n", + "model = trainer.model\n", + "model.eval()\n", + "\n", + "# Access the test dataset from the internal datamodule\n", + "test_dataset = trainer.datamodule.test_dataloader().dataset\n", + "class_names = trainer.datamodule.dataset.seglabel_list\n", + "\n", + "fig, axes = plt.subplots(3, 2, figsize=(12, 18))\n", + "\n", + "for row in range(3):\n", + " idx = np.random.choice(len(test_dataset))\n", + " sample = test_dataset[idx]\n", + " image = sample['image'] # (C, H, W)\n", + " mask_gt = sample['segmentations'] # (#classes+1, H, W) — includes background\n", + "\n", + " with torch.inference_mode():\n", + " logits = model(image.unsqueeze(0).to(model.device))\n", + " mask_pred = (logits[0] > 0).cpu()\n", + "\n", + " # Ground truth (skip background channel)\n", + " gt_overlay = draw_masks(image, mask_gt[1:], alpha=0.5)\n", + " axes[row, 0].set_title(f\"Ground Truth (sample {idx})\")\n", + " show(gt_overlay, ax=axes[row, 0])\n", + "\n", + " # Prediction\n", + " pred_overlay = draw_masks(image, mask_pred, alpha=0.5)\n", + " axes[row, 1].set_title(\"Prediction\")\n", + " show(pred_overlay, ax=axes[row, 1])\n", + "\n", + " iou = mean_iou(\n", + " mask_pred.unsqueeze(0).long(),\n", + " mask_gt[1:].unsqueeze(0).long(),\n", + " num_classes=len(class_names),\n", + " input_format='one-hot',\n", + " )\n", + " print(f\"Sample {idx} — IoU: {iou.max():.1%} — Classes: {class_names}\")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "aafb6276", + "metadata": {}, + "source": [ + "## 6. Deployment\n", + "\n", + "The trainer **automatically** created and registered a deployment adapter in MLflow.\n", + "You can deploy it directly to the Datamint server:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e4382ba", + "metadata": {}, + "outputs": [], + "source": [ + "job = api.deploy.start(\n", + " model_name=PROJECT_NAME,\n", + " model_alias=\"latest\",\n", + " with_gpu=False,\n", + ")\n", + "\n", + "print(f\"Deployment job started!\")\n", + "print(f\"Job ID: {job.id}\")\n", + "print(f\"Status: {job.status}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1b4933a", + "metadata": {}, + "outputs": [], + "source": [ + "# Check deployment status\n", + "job = api.deploy.get_by_id(job.id)\n", + "\n", + "print(f\"Job Status: {job.status}\")\n", + "print(f\"Progress: {job.progress_percentage}%\")\n", + "\n", + "if job.error_message:\n", + " print(f\"Error: {job.error_message}\")" + ] + }, + { + "cell_type": "markdown", + "id": "8704fd18", + "metadata": {}, + "source": [ + "### 6.1 Remote inference" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78a21a8b", + "metadata": {}, + "outputs": [], + "source": [ + "from datamint import Api\n", + "PROJECT_NAME = \"deeplabv3plus_Segmentation_Tutorial\"\n", + "api = Api()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec61ed35", + "metadata": {}, + "outputs": [], + "source": [ + "r = api.resources.get_list(project_name=PROJECT_NAME, limit=1)[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08615277", + "metadata": {}, + "outputs": [], + "source": [ + "inf_job = api.inference.submit(\n", + " model_name=PROJECT_NAME,\n", + " model_alias=\"latest\",\n", + " resource_id=r.id\n", + ")\n", + "inf_job.wait() # Wait for inference to complete" + ] + }, + { + "cell_type": "markdown", + "id": "09215ff7", + "metadata": {}, + "source": [ + "visualize predictions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "273350e8", + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n", + "\n", + "# plot all predictions using matplotlib\n", + "preds = inf_job.predictions[0]\n", + "plt.figure(figsize=(6, 6))\n", + "plt.imshow(preds[0].mask, cmap='gray')\n", + "plt.title(\"Predicted Mask\")\n", + "plt.axis('off')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd662b56", + "metadata": {}, + "outputs": [], + "source": [ + "# Open the Datamint dashboard for this project\n", + "proj.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "env", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/use_cases/segmentation_2d_trainer_BUSI_tutorial.ipynb b/notebooks/use_cases/segmentation_2d_trainer_BUSI_unetplusplus_tutorial.ipynb similarity index 100% rename from notebooks/use_cases/segmentation_2d_trainer_BUSI_tutorial.ipynb rename to notebooks/use_cases/segmentation_2d_trainer_BUSI_unetplusplus_tutorial.ipynb From fc2e006526d59f4d7f927050bc6ac236f62334e6 Mon Sep 17 00:00:00 2001 From: luandalmazo Date: Tue, 2 Jun 2026 07:27:48 -0300 Subject: [PATCH 2/2] Merge unet and deeplabv3 notebooks into one --- ..._trainer_BUSI_deeplabv3plus_tutorial.ipynb | 574 ------------------ ...gmentation_2d_trainer_BUSI_tutorial.ipynb} | 221 +++---- 2 files changed, 76 insertions(+), 719 deletions(-) delete mode 100644 notebooks/use_cases/segmentation_2d_trainer_BUSI_deeplabv3plus_tutorial.ipynb rename notebooks/use_cases/{segmentation_2d_trainer_BUSI_unetplusplus_tutorial.ipynb => segmentation_2d_trainer_BUSI_tutorial.ipynb} (99%) diff --git a/notebooks/use_cases/segmentation_2d_trainer_BUSI_deeplabv3plus_tutorial.ipynb b/notebooks/use_cases/segmentation_2d_trainer_BUSI_deeplabv3plus_tutorial.ipynb deleted file mode 100644 index 0b1bc16d..00000000 --- a/notebooks/use_cases/segmentation_2d_trainer_BUSI_deeplabv3plus_tutorial.ipynb +++ /dev/null @@ -1,574 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0263cedb", - "metadata": {}, - "source": [ - "# 2D Segmentation with the Datamint Trainer API\n", - "\n", - "This notebook shows how to train a **DeepLab V3+ semantic segmentation** model on the **BUSI** (Breast Ultrasound Images) dataset using Datamint's **Trainer API** — a high-level wrapper that handles dataset loading, model creation, training, experiment tracking, and model registration from a small amount of code.\n", - "\n", - "## Comparison with the manual Workflow\n", - "\n", - "The conventional way requires you to:\n", - "1. Define transforms, loss function, metrics, and a full LightningModule (~150 lines)\n", - "2. Configure MLflow logger, callbacks, and Trainer (~50 lines)\n", - "3. Build and wire together `DatamintDataModule`, `L.Trainer`, etc.\n", - "\n", - "With the Trainer API, **all of that is replaced by ~3 lines**:\n", - "\n", - "```python\n", - "from datamint.lightning.trainers import DeepLabV3PlusTrainer\n", - "\n", - "trainer = DeepLabV3PlusTrainer(project='MyProject')\n", - "results = trainer.fit()\n", - "```\n", - "\n", - "## What You'll Learn\n", - "\n", - "1. Upload data to Datamint\n", - "2. Train with `DeepLabV3PlusTrainer` using **zero configuration**\n", - "3. Train with `SemanticSegmentation2DTrainer` while swapping in a custom external model\n", - "4. Visualise predictions\n", - "5. Register and deploy the trained model\n", - "\n", - "## Required Dependencies\n", - "\n", - "```bash\n", - "pip install datamint\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "ca7b0298", - "metadata": {}, - "outputs": [], - "source": [ - "from datamint import Api\n", - "\n", - "PROJECT_NAME = \"deeplabv3plus_Segmentation_Tutorial\"\n", - "api = Api()" - ] - }, - { - "cell_type": "markdown", - "id": "19abf11f", - "metadata": {}, - "source": [ - "## 1. Setup: Create Project\n", - "\n", - "Create (or retrieve) a Datamint project for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "af13262e", - "metadata": {}, - "outputs": [ - { - "ename": "HTTPStatusError", - "evalue": "Client error '403 Forbidden' for url 'https://api.datamint.io/projects'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mHTTPStatusError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m proj = api.projects.create(\n\u001b[32m 2\u001b[39m name=PROJECT_NAME,\n\u001b[32m 3\u001b[39m description=\u001b[33m\"Tutorial project for DeepLabV3+ segmentation on BUSI dataset\"\u001b[39m,\n\u001b[32m 4\u001b[39m exists_ok=\u001b[38;5;28;01mTrue\u001b[39;00m \u001b[38;5;66;03m# Just return the existing project if it already exists\u001b[39;00m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/datamint/api/endpoints/projects_api.py:124\u001b[39m, in \u001b[36mProjectsApi.create\u001b[39m\u001b[34m(self, name, description, resources_ids, is_active_learning, two_up_display, segmentation_spec, return_entity, exists_ok)\u001b[39m\n\u001b[32m 111\u001b[39m project_data = {\u001b[33m'\u001b[39m\u001b[33mname\u001b[39m\u001b[33m'\u001b[39m: name,\n\u001b[32m 112\u001b[39m \u001b[33m'\u001b[39m\u001b[33mis_active_learning\u001b[39m\u001b[33m'\u001b[39m: is_active_learning,\n\u001b[32m 113\u001b[39m \u001b[33m'\u001b[39m\u001b[33mresource_ids\u001b[39m\u001b[33m'\u001b[39m: resources_ids,\n\u001b[32m (...)\u001b[39m\u001b[32m 120\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mrequire_review\u001b[39m\u001b[33m\"\u001b[39m: \u001b[38;5;28;01mFalse\u001b[39;00m,\n\u001b[32m 121\u001b[39m \u001b[33m'\u001b[39m\u001b[33mdescription\u001b[39m\u001b[33m'\u001b[39m: description}\n\u001b[32m 123\u001b[39m \u001b[38;5;66;03m# type: ignore[return-value]\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m124\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_create\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mproject_data\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mreturn_entity\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mreturn_entity\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mexists_ok\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mexists_ok\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/datamint/api/entity_base_api.py:336\u001b[39m, in \u001b[36mCreatableEntityApi._create\u001b[39m\u001b[34m(self, entity_data, return_entity, exists_ok)\u001b[39m\n\u001b[32m 317\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Create a new entity.\u001b[39;00m\n\u001b[32m 318\u001b[39m \n\u001b[32m 319\u001b[39m \u001b[33;03mArgs:\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 333\u001b[39m \u001b[33;03m *exists_ok* is ``False``).\u001b[39;00m\n\u001b[32m 334\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 335\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m336\u001b[39m response = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_make_request\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m'\u001b[39;49m\u001b[30;43mPOST\u001b[39;49m\u001b[30;43m'\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mf\u001b[39;49m\u001b[30;43m'\u001b[39;49m\u001b[30;43m/\u001b[39;49m\u001b[30;43;01m{\u001b[39;49;00m\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mendpoint_base\u001b[39;49m\u001b[30;43;01m}\u001b[39;49;00m\u001b[30;43m'\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mjson\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mentity_data\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 337\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m httpx.HTTPStatusError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 338\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m exists_ok \u001b[38;5;129;01mand\u001b[39;00m e.response.status_code == \u001b[32m409\u001b[39m:\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/datamint/api/base_api.py:326\u001b[39m, in \u001b[36mBaseApi._make_request\u001b[39m\u001b[34m(self, method, endpoint, **kwargs)\u001b[39m\n\u001b[32m 324\u001b[39m logger.debug(\u001b[33m'\u001b[39m\u001b[33mEquivalent curl command: \u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m\"\u001b[39m\u001b[33m'\u001b[39m, curl_command)\n\u001b[32m 325\u001b[39m response = \u001b[38;5;28mself\u001b[39m.client.request(method, url, **kwargs)\n\u001b[32m--> \u001b[39m\u001b[32m326\u001b[39m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_check_errors_response_httpx\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mresponse\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43murl\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43murl\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 327\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m response\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/datamint/api/base_api.py:416\u001b[39m, in \u001b[36mBaseApi._check_errors_response_httpx\u001b[39m\u001b[34m(self, response, url)\u001b[39m\n\u001b[32m 414\u001b[39m response_json = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 415\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m416\u001b[39m \u001b[30;43mresponse\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mraise_for_status\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 417\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m httpx.ConnectError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 418\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mCERTIFICATE_VERIFY_FAILED\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mstr\u001b[39m(e) \u001b[38;5;129;01mor\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mcertificate verify failed\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mstr\u001b[39m(e).lower():\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Datamint/Codes/datamint-python-api/datamint/env/lib/python3.12/site-packages/httpx/_models.py:829\u001b[39m, in \u001b[36mResponse.raise_for_status\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 827\u001b[39m error_type = error_types.get(status_class, \u001b[33m\"\u001b[39m\u001b[33mInvalid status code\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 828\u001b[39m message = message.format(\u001b[38;5;28mself\u001b[39m, error_type=error_type)\n\u001b[32m--> \u001b[39m\u001b[32m829\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m HTTPStatusError(message, request=request, response=\u001b[38;5;28mself\u001b[39m)\n", - "\u001b[31mHTTPStatusError\u001b[39m: Client error '403 Forbidden' for url 'https://api.datamint.io/projects'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403" - ] - } - ], - "source": [ - "proj = api.projects.create(\n", - " name=PROJECT_NAME,\n", - " description=\"Tutorial project for DeepLabV3+ segmentation on BUSI dataset\",\n", - " exists_ok=True # Just return the existing project if it already exists\n", - ")\n", - "proj" - ] - }, - { - "cell_type": "markdown", - "id": "a242d5cd", - "metadata": {}, - "source": [ - "## 2. Dataset Preparation: Download and Upload BUSI\n", - "\n", - "This section downloads the BUSI dataset and uploads it to Datamint.\n", - "If you already have the data inside Datamint, you can **skip to Section 3**.\n", - "\n", - "In this section, we will:\n", - "- Download the BUSI dataset\n", - "- Upload ultrasound images to Datamint\n", - "- Upload corresponding segmentation masks\n", - "- Create train/val/test splits\n", - "\n", - "### 2.1 Download BUSI Dataset\n", - "\n", - "The BUSI dataset is available from Kaggle. For this tutorial, we'll use the breast ultrasound images dataset.\n", - "\n", - "> Al-Dhabyani W, Gomaa M, Khaled H, Fahmy A. Dataset of breast ultrasound images. Data in Brief. 2020 Feb;28:104863. DOI: 10.1016/j.dib.2019.104863." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "97351339", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import requests\n", - "import zipfile\n", - "from pathlib import Path\n", - "\n", - "BUSI_URL = \"https://www.kaggle.com/api/v1/datasets/download/sabahesaraki/breast-ultrasound-images-dataset\"\n", - "DATA_DIR = Path(\"/tmp/BUSI_dataset\")\n", - "\n", - "if not DATA_DIR.exists():\n", - " print(\"Downloading BUSI dataset...\")\n", - " response = requests.get(BUSI_URL, stream=True)\n", - " response.raise_for_status()\n", - " zip_path = DATA_DIR / \"Dataset_BUSI.zip\"\n", - " DATA_DIR.mkdir(parents=True, exist_ok=True)\n", - " with open(zip_path, 'wb') as f:\n", - " for chunk in response.iter_content(chunk_size=8192):\n", - " f.write(chunk)\n", - " print(\"Extracting...\")\n", - " with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n", - " zip_ref.extractall(DATA_DIR)\n", - " os.remove(zip_path)\n", - " print(\"Download complete!\")\n", - "else:\n", - " print(f\"Dataset already exists at {DATA_DIR}\")" - ] - }, - { - "cell_type": "markdown", - "id": "8e6bf5f7", - "metadata": {}, - "source": [ - "### 2.2 Find image and mask paths" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9c0b6f5e", - "metadata": {}, - "outputs": [], - "source": [ - "base_dir = DATA_DIR / \"Dataset_BUSI_with_GT\"\n", - "classes = [\"benign\", \"malignant\", \"normal\"]\n", - "\n", - "image_paths = []\n", - "label_paths = []\n", - "\n", - "for cls in classes:\n", - " cls_dir = base_dir / cls\n", - " cls_images = sorted([p for p in cls_dir.glob(\"*.png\") if \"_mask\" not in p.name])\n", - " for img_p in cls_images:\n", - " mask_p = cls_dir / f\"{img_p.stem}_mask.png\"\n", - " if mask_p.exists():\n", - " image_paths.append(img_p)\n", - " label_paths.append(mask_p)\n", - "\n", - "print(f\"Found {len(image_paths)} images and {len(label_paths)} masks\")" - ] - }, - { - "cell_type": "markdown", - "id": "cedb5070", - "metadata": {}, - "source": [ - "### 2.3 Upload images and masks to Datamint" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0221a1fb", - "metadata": {}, - "outputs": [], - "source": [ - "# Upload images\n", - "uploaded_resources = api.resources.upload_resources(\n", - " [str(p) for p in image_paths],\n", - " tags=['busi', 'ultrasound', 'breast'],\n", - " publish_to=proj,\n", - " progress_bar=True,\n", - ")\n", - "print(f\"Uploaded {len(uploaded_resources)} images\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "396f0f70", - "metadata": {}, - "outputs": [], - "source": [ - "from tqdm.auto import tqdm\n", - "\n", - "# Get resources from project\n", - "all_resources = list(api.resources.get_list(project_name=PROJECT_NAME, tags=['busi']))\n", - "filename_to_resource = {r.filename: r for r in all_resources}\n", - "\n", - "# Upload segmentation masks\n", - "for img_path, label_path in tqdm(zip(image_paths, label_paths), total=len(image_paths)):\n", - " if 'normal' in img_path.parent.name:\n", - " continue # Normal images have no lesion masks\n", - " resource = filename_to_resource[img_path.name]\n", - " cls_name = img_path.parent.name # 'benign' or 'malignant'\n", - "\n", - " api.annotations.upload_segmentations(\n", - " resource=resource,\n", - " file_path=label_path,\n", - " name=cls_name,\n", - " imported_from=\"Original GT BUSI Dataset\",\n", - " )\n", - "\n", - "print(\"Segmentation masks uploaded successfully!\")" - ] - }, - { - "cell_type": "markdown", - "id": "bdc37407", - "metadata": {}, - "source": [ - "### 2.4 Tag train/val/test splits\n", - "\n", - "We split the dataset into three subsets using tags for reproducibility.\n", - "\n", - "| Split | Percentage | Purpose |\n", - "|-------|------------|---------|\n", - "| Train | 70% | Model training |\n", - "| Validation | 15% | Hyperparameter tuning, early stopping |\n", - "| Test | 15% | Final model evaluation |" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ba063034", - "metadata": {}, - "outputs": [], - "source": [ - "import random\n", - "\n", - "all_resources = list(api.resources.get_list(project_name=PROJECT_NAME, tags=['busi']))\n", - "all_resources.sort(key=lambda r: r.filename)\n", - "\n", - "random.seed(42)\n", - "random.shuffle(all_resources)\n", - "\n", - "n_total = len(all_resources)\n", - "n_train = int(0.7 * n_total)\n", - "n_val = int(0.15 * n_total)\n", - "\n", - "train_resources = all_resources[:n_train]\n", - "val_resources = all_resources[n_train:n_train + n_val]\n", - "test_resources = all_resources[n_train + n_val:]\n", - "\n", - "api.projects.assign_splits(proj, train_resources, split_name='train')\n", - "api.projects.assign_splits(proj, val_resources, split_name='val')\n", - "api.projects.assign_splits(proj, test_resources, split_name='test')\n", - "\n", - "print(f\"Train: {len(train_resources)}, Val: {len(val_resources)}, Test: {len(test_resources)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "b2e2f571", - "metadata": {}, - "source": [ - "## 3. Training with the Trainer API\n", - "\n", - "`DeepLabV3PlusTrainer` wraps the full training pipeline in a single object. It automatically:\n", - "\n", - "1. Builds an `ImageDataset` from the project (or a sliced 2D view if the project contains 3D volumes)\n", - "2. Applies standard augmentations — flips, brightness/contrast, ImageNet normalisation\n", - "3. Instantiates a **DeepLab v3+** model with an ImageNet-pretrained encoder\n", - "4. Sets up **BCE + Dice loss**, **Mean IoU** and **Generalised Dice** metrics\n", - "5. Configures MLflow logging, early stopping, and model checkpointing\n", - "6. Trains, validates, and evaluates on the test split\n", - "\n", - "The key architecture-specific parameter is `decoder_atrous_rates`, which controls the dilation rates of the **ASPP** (Atrous Spatial Pyramid Pooling) module — DeepLab v3+'s mechanism for capturing multi-scale context. The default `(12, 24, 36)` works well for most medical imaging tasks." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "572fe5fd", - "metadata": {}, - "outputs": [], - "source": [ - "from datamint.lightning import DeepLabV3PlusTrainer\n", - "\n", - "\n", - "trainer = DeepLabV3PlusTrainer(\n", - " project=PROJECT_NAME,\n", - " image_size=256,\n", - " batch_size=16,\n", - " max_epochs=12,\n", - " accelerator='auto',\n", - " # register_model_name='MyModelName' # Default is project name\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b4d11db7", - "metadata": {}, - "outputs": [], - "source": [ - "results = trainer.fit()" - ] - }, - { - "cell_type": "markdown", - "id": "496bf458", - "metadata": {}, - "source": [ - "That's it!\n", - "\n", - "Let's inspect the results:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8313df73", - "metadata": {}, - "outputs": [], - "source": [ - "# Inspect the results dictionary\n", - "print(\"Test results:\")\n", - "for metric_dict in results['test_results']:\n", - " for k, v in metric_dict.items():\n", - " print(f\" {k}: {v:.4f}\")" - ] - }, - { - "cell_type": "markdown", - "id": "e1eab625", - "metadata": {}, - "source": [ - "## 4. Visualize predictions\n", - "\n", - "Let's visualize the model predictions against ground truth on test samples." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a20cc9f2", - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "import numpy as np\n", - "from matplotlib import pyplot as plt\n", - "from datamint.utils.visualization import show, draw_masks\n", - "from torchmetrics.functional.segmentation import mean_iou\n", - "\n", - "model = trainer.model\n", - "model.eval()\n", - "\n", - "# Access the test dataset from the internal datamodule\n", - "test_dataset = trainer.datamodule.test_dataloader().dataset\n", - "class_names = trainer.datamodule.dataset.seglabel_list\n", - "\n", - "fig, axes = plt.subplots(3, 2, figsize=(12, 18))\n", - "\n", - "for row in range(3):\n", - " idx = np.random.choice(len(test_dataset))\n", - " sample = test_dataset[idx]\n", - " image = sample['image'] # (C, H, W)\n", - " mask_gt = sample['segmentations'] # (#classes+1, H, W) — includes background\n", - "\n", - " with torch.inference_mode():\n", - " logits = model(image.unsqueeze(0).to(model.device))\n", - " mask_pred = (logits[0] > 0).cpu()\n", - "\n", - " # Ground truth (skip background channel)\n", - " gt_overlay = draw_masks(image, mask_gt[1:], alpha=0.5)\n", - " axes[row, 0].set_title(f\"Ground Truth (sample {idx})\")\n", - " show(gt_overlay, ax=axes[row, 0])\n", - "\n", - " # Prediction\n", - " pred_overlay = draw_masks(image, mask_pred, alpha=0.5)\n", - " axes[row, 1].set_title(\"Prediction\")\n", - " show(pred_overlay, ax=axes[row, 1])\n", - "\n", - " iou = mean_iou(\n", - " mask_pred.unsqueeze(0).long(),\n", - " mask_gt[1:].unsqueeze(0).long(),\n", - " num_classes=len(class_names),\n", - " input_format='one-hot',\n", - " )\n", - " print(f\"Sample {idx} — IoU: {iou.max():.1%} — Classes: {class_names}\")\n", - "\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "aafb6276", - "metadata": {}, - "source": [ - "## 6. Deployment\n", - "\n", - "The trainer **automatically** created and registered a deployment adapter in MLflow.\n", - "You can deploy it directly to the Datamint server:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4e4382ba", - "metadata": {}, - "outputs": [], - "source": [ - "job = api.deploy.start(\n", - " model_name=PROJECT_NAME,\n", - " model_alias=\"latest\",\n", - " with_gpu=False,\n", - ")\n", - "\n", - "print(f\"Deployment job started!\")\n", - "print(f\"Job ID: {job.id}\")\n", - "print(f\"Status: {job.status}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d1b4933a", - "metadata": {}, - "outputs": [], - "source": [ - "# Check deployment status\n", - "job = api.deploy.get_by_id(job.id)\n", - "\n", - "print(f\"Job Status: {job.status}\")\n", - "print(f\"Progress: {job.progress_percentage}%\")\n", - "\n", - "if job.error_message:\n", - " print(f\"Error: {job.error_message}\")" - ] - }, - { - "cell_type": "markdown", - "id": "8704fd18", - "metadata": {}, - "source": [ - "### 6.1 Remote inference" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "78a21a8b", - "metadata": {}, - "outputs": [], - "source": [ - "from datamint import Api\n", - "PROJECT_NAME = \"deeplabv3plus_Segmentation_Tutorial\"\n", - "api = Api()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ec61ed35", - "metadata": {}, - "outputs": [], - "source": [ - "r = api.resources.get_list(project_name=PROJECT_NAME, limit=1)[0]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "08615277", - "metadata": {}, - "outputs": [], - "source": [ - "inf_job = api.inference.submit(\n", - " model_name=PROJECT_NAME,\n", - " model_alias=\"latest\",\n", - " resource_id=r.id\n", - ")\n", - "inf_job.wait() # Wait for inference to complete" - ] - }, - { - "cell_type": "markdown", - "id": "09215ff7", - "metadata": {}, - "source": [ - "visualize predictions:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "273350e8", - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib import pyplot as plt\n", - "\n", - "# plot all predictions using matplotlib\n", - "preds = inf_job.predictions[0]\n", - "plt.figure(figsize=(6, 6))\n", - "plt.imshow(preds[0].mask, cmap='gray')\n", - "plt.title(\"Predicted Mask\")\n", - "plt.axis('off')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd662b56", - "metadata": {}, - "outputs": [], - "source": [ - "# Open the Datamint dashboard for this project\n", - "proj.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "env", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/use_cases/segmentation_2d_trainer_BUSI_unetplusplus_tutorial.ipynb b/notebooks/use_cases/segmentation_2d_trainer_BUSI_tutorial.ipynb similarity index 99% rename from notebooks/use_cases/segmentation_2d_trainer_BUSI_unetplusplus_tutorial.ipynb rename to notebooks/use_cases/segmentation_2d_trainer_BUSI_tutorial.ipynb index c3fd8ae2..4596a38e 100644 --- a/notebooks/use_cases/segmentation_2d_trainer_BUSI_unetplusplus_tutorial.ipynb +++ b/notebooks/use_cases/segmentation_2d_trainer_BUSI_tutorial.ipynb @@ -7,7 +7,7 @@ "source": [ "# 2D Segmentation with the Datamint Trainer API\n", "\n", - "This notebook shows how to train a **UNet++ semantic segmentation** model on the **BUSI** (Breast Ultrasound Images) dataset using Datamint's **Trainer API** — a high-level wrapper that handles dataset loading, model creation, training, experiment tracking, and model registration from a small amount of code.\n", + "This notebook shows how to train **DeepLab V3+ and UNet++ semantic segmentation** model on the **BUSI** (Breast Ultrasound Images) dataset using Datamint's **Trainer API** — a high-level wrapper that handles dataset loading, model creation, training, experiment tracking, and model registration from a small amount of code.\n", "\n", "## Comparison with the manual Workflow\n", "\n", @@ -19,6 +19,15 @@ "With the Trainer API, **all of that is replaced by ~3 lines**:\n", "\n", "```python\n", + "from datamint.lightning.trainers import DeepLabV3PlusTrainer\n", + "\n", + "trainer = DeepLabV3PlusTrainer(project='MyProject')\n", + "results = trainer.fit()\n", + "```\n", + "\n", + "or\n", + "\n", + "```python\n", "from datamint.lightning.trainers import UNetPPTrainer\n", "\n", "trainer = UNetPPTrainer(project='MyProject')\n", @@ -28,7 +37,7 @@ "## What You'll Learn\n", "\n", "1. Upload data to Datamint\n", - "2. Train with `UNetPPTrainer` using **zero configuration**\n", + "2. Train with `DeepLabV3PlusTrainer` and `UNetPPTrainer` using **zero configuration**\n", "3. Train with `SemanticSegmentation2DTrainer` while swapping in a custom external model\n", "4. Visualise predictions\n", "5. Register and deploy the trained model\n", @@ -49,7 +58,7 @@ "source": [ "from datamint import Api\n", "\n", - "PROJECT_NAME = \"UNetPP_Segmentation_Tutorial\"\n", + "PROJECT_NAME = \"Segmentation_Tutorial\"\n", "api = Api()" ] }, @@ -65,143 +74,14 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "af13262e", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - " \n", - "
\n", - "
Entity
\n", - "
\n", - "

Project

\n", - "
\n", - "
\n", - "\n", - " \n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Name\n", - " UNetPP_Segmentation_Tutorial\n", - "
Created At\n", - " 2025-12-23T14:33:11.807Z\n", - "
Created By\n", - " datamint-dev@mail.com\n", - "
Archived\n", - " False\n", - "
Resource Count\n", - " 780\n", - "
Annotated Resource Count\n", - " 0\n", - "
Description\n", - " Tutorial project for UNet++ segmentation on BTCV dataset\n", - "
\n", - "
\n", - "\n", - "
" - ], - "text/plain": [ - "Project(id='6c770f6c-6483-4af3-924c-16b122bfc6d5', name='UNetPP_Segmentation_Tutorial', created_at='2025-12-23T14:33:11.807Z', created_by='datamint-dev@mail.com', dataset_id='d8c94f56-0897-428b-8d5a-7b846233522a', worklist_id='9ad15be0-4c6d-4537-ab8b-6464fab5eae4', archived=False, resource_count=780, annotated_resource_count=0, description='Tutorial project for UNet++ segmentation on BTCV dataset', viewable_ai_segs=None, editable_ai_segs=None, closed_resources_count=0, resources_to_annotate_count=0, most_recent_experiment=None, annotators=[{'email': 'datamint-dev@mail.com', 'roles': ['PROJECT_OWNER'], 'status': 'active'}])" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "proj = api.projects.create(\n", " name=PROJECT_NAME,\n", - " description=\"Tutorial project for UNet++ segmentation on BUSI dataset\",\n", + " description=\"Tutorial project for segmentation on BUSI dataset\",\n", " exists_ok=True # Just return the existing project if it already exists\n", ")\n", "proj" @@ -391,7 +271,7 @@ "test_resources = all_resources[n_train + n_val:]\n", "\n", "api.projects.assign_splits(proj, train_resources, split_name='train')\n", - "api.projects.assign_splits(proj, val_resources, split_name='val')\n", + "api.projects.assign_splits(proj, val_resources, split_name='val')### 3.1 UNet++\n", "api.projects.assign_splits(proj, test_resources, split_name='test')\n", "\n", "print(f\"Train: {len(train_resources)}, Val: {len(val_resources)}, Test: {len(test_resources)}\")" @@ -411,17 +291,60 @@ "- Callbacks, loggers, and a Lightning Trainer\n", "- A deployment adapter\n", "\n", - "...you simply create a `UNetPPTrainer` (or `SemanticSegmentation2DTrainer`) and call `fit()`.\n", + "...you simply create a `UNetPPTrainer`, `DeepLabV3PlusTrainer`, or even a `SemanticSegmentation2DTrainer` and call `fit()`.\n", + "\n", "\n", "If your project contains 3D volumes instead of 2D images, the 2D trainer automatically slices them into 2D samples before training.\n", "\n", "The trainer automatically:\n", "1. Builds an `ImageDataset` for 2D projects, or a sliced 2D view of a volume dataset when the project is volumetric\n", - "2. Creates augmentation pipelines (with medical-image-specific augmentations for `UNetPPTrainer`)\n", + "2. Creates augmentation pipelines (with medical-image-specific augmentations for `UNetPPTrainer` (`DeepLabV3PlusTrainer`))\n", "3. Instantiates a default segmentation model when `model=None`\n", "4. Sets up **BCE + Dice loss**, **IoU** and **Dice** metrics\n", "5. Configures MLflow logging, checkpointing, and early stopping\n", - "6. Trains and evaluates on the test split" + "6. Trains and evaluates on the test split.\n", + "\n", + "Here you can choose between two options, DeepLabV3 or UNet++:" + ] + }, + { + "cell_type": "markdown", + "id": "4c06d455", + "metadata": {}, + "source": [ + "### 3.1 DeepLabV3\n", + "\n", + "`DeepLabV3PlusTrainer`\n", + "\n", + "The key architecture-specific parameter is `decoder_atrous_rates`, which controls the dilation rates of the **ASPP** (Atrous Spatial Pyramid Pooling) module — DeepLab v3+'s mechanism for capturing multi-scale context. The default `(12, 24, 36)` works well for most medical imaging tasks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c596cd9", + "metadata": {}, + "outputs": [], + "source": [ + "from datamint.lightning import DeepLabV3PlusTrainer\n", + "\n", + "\n", + "trainer = DeepLabV3PlusTrainer(\n", + " project=PROJECT_NAME,\n", + " image_size=256,\n", + " batch_size=16,\n", + " max_epochs=12,\n", + " accelerator='auto',\n", + " # register_model_name='MyModelName' # Default is project name\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b9132010", + "metadata": {}, + "source": [ + "### 3.2 UNet++" ] }, { @@ -444,6 +367,14 @@ ")" ] }, + { + "cell_type": "markdown", + "id": "db8b1870", + "metadata": {}, + "source": [ + "Then, we just need to train our model." + ] + }, { "cell_type": "code", "execution_count": null, @@ -491,7 +422,7 @@ "1. (**RECOMMENDED**) Pass a `SegmentationModule` subclass **class** to `model=`. This is the preferred path when you want Datamint to manage computing loss and metrics, and when you want the resulting model to remain Datamint-compatible for inference and deployment. In this case, pass the class, not the instance object.\n", "2. Pass a fully constructed `lightning.LightningModule` **instance** to `model=` when you want complete control over `training_step`, `validation_step`, `test_step`, and `configure_optimizers`.\n", "\n", - "The example below uses `DeepLabV3Plus` from `segmentation_models_pytorch` as an external architecture while still keeping the Datamint trainer workflow." + "The example below uses `UNet++` from `segmentation_models_pytorch` as an external architecture while still keeping the Datamint trainer workflow." ] }, { @@ -591,7 +522,7 @@ "import mlflow\n", "from mlflow import MlflowClient\n", "\n", - "model_uri = \"models:/UNetPP_Segmentation_Tutorial/latest\"\n", + "model_uri = \"models:/Segmentation_Tutorial/latest\"\n", "\n", "model_info = mlflow.models.get_model_info(model_uri)\n", "client = MlflowClient()\n", @@ -635,11 +566,11 @@ "\n", "r = trainer.dataset[0]['resource']\n", "\n", - "model_loaded = datamint_flavor.load_model('models:/UNetPP_Segmentation_Tutorial/latest')\n", + "model_loaded = datamint_flavor.load_model('models:/Segmentation_Tutorial/latest')\n", "model_loaded.predict([r])\n", "\n", "# Alternatively:\n", - "# model_loaded = mlflow.pyfunc.load_model('models:/UNetPP_Segmentation_Tutorial/latest')\n", + "# model_loaded = mlflow.pyfunc.load_model('models:/Segmentation_Tutorial/latest')\n", "# model_loaded.predict([r])" ] }, @@ -736,7 +667,7 @@ "\n", "The Trainer API is customizable at several layers. Here are the most common overrides:\n", "\n", - "### 5.1 Change the encoder backbone\n", + "### 5.1 Change the encoder backbone (example using uNet++)\n", "\n", "Use any encoder supported by [segmentation_models_pytorch](https://github.com/qubvel-org/segmentation_models.pytorch):\n", "\n", @@ -930,7 +861,7 @@ "outputs": [], "source": [ "from datamint import Api\n", - "PROJECT_NAME = \"UNetPP_Segmentation_Tutorial\"\n", + "PROJECT_NAME = \"Segmentation_Tutorial\"\n", "api = Api()" ] },