Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion datamint/dataset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- VideoDataset: Temporal sequences (videos, multi-frame DICOM)
- VolumetricDataset: 3D volumes (NIfTI, CT, MRI)

Use `create_dataset()` for automatic type detection, or instantiate directly.
Use `build_dataset()` for automatic type detection, or instantiate directly.
"""

# New modular architecture
Expand All @@ -18,6 +18,7 @@
from .sliced_dataset import SlicedVolumeDataset
from .sliced_video_dataset import SlicedVideoDataset
from .detection_dataset import DetectionDataset, detection_collate_fn
from .factory import build_dataset

__all__ = [
# Core
Expand All @@ -32,4 +33,6 @@
'SlicedVideoDataset',
'DetectionDataset',
'detection_collate_fn',
# Factory
'build_dataset',
]
99 changes: 99 additions & 0 deletions datamint/dataset/factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from datamint.entities.resource import Resource
from .base import DatamintBaseDataset

_LOGGER = logging.getLogger(__name__)


def _classify_resource(resource: 'Resource') -> str:
if resource.is_video():
return 'video'
if resource.is_volume():
try:
if resource.get_depth() == 1:
return 'image'
except Exception:
pass # frame_count not in list-response metadata; assume volume
return 'volume'
if resource.is_image():
return 'image'
return getattr(resource, 'kind', 'unknown')


def build_dataset(project_name: str, **kwargs: Any) -> 'DatamintBaseDataset':
"""Auto-detect and return the appropriate dataset class for a project.

Fetches a small sample of resources from the project to determine the
data type, then instantiates and returns the matching dataset class:

- 2D images (JPEG, PNG) → :class:`~datamint.dataset.ImageDataset`
- Volumes (NIfTI, DICOM, volumetric) → :class:`~datamint.dataset.VolumeDataset`
- Videos → :class:`~datamint.dataset.VideoDataset`

Args:
project_name: Name of the Datamint project.
**kwargs: Forwarded to the dataset constructor (transforms, filters, etc.).

Returns:
An instantiated dataset of the detected type.

Raises:
ValueError: If the project is empty, contains unknown resource types,
or contains a mix of data types.

Example::

from datamint.dataset import build_dataset

ds = build_dataset('MyProject', include_unannotated=False)
"""
from datamint import Api
from .image_dataset import ImageDataset
from .volume_dataset import VolumeDataset
from .video_dataset import VideoDataset

_KIND_TO_CLS = {
'image': ImageDataset,
'volume': VolumeDataset,
'nifti': VolumeDataset,
'dicom': VolumeDataset,
'video': VideoDataset,
}

api = Api()
sample = api.resources.get_list(project_name=project_name, limit=5)

if not sample:
raise ValueError(f"Project '{project_name}' has no resources.")

kinds = {_classify_resource(r) for r in sample}
unknown = kinds - set(_KIND_TO_CLS)
if unknown:
raise ValueError(
f"Project '{project_name}' contains unsupported resource types: {sorted(unknown)}. "
"Instantiate the dataset class directly."
)

if kinds == {'image', 'volume'}:
_LOGGER.warning(
f"Project '{project_name}' contains a mix of 2D and 3D DICOM resources. "
"Defaulting to VolumeDataset. Use ImageDataset or VolumeDataset directly "
"if you need a specific type."
)
kinds = {'volume'}

if len(kinds) > 1:
raise ValueError(
f"Project '{project_name}' contains mixed data types: {sorted(kinds)}. "
"Instantiate the dataset class directly."
)

kind = next(iter(kinds))
dataset_cls = _KIND_TO_CLS[kind]
_LOGGER.info(f"Detected resource type '{kind}'; using {dataset_cls.__name__}.")
return dataset_cls(project=project_name, **kwargs)
266 changes: 266 additions & 0 deletions notebooks/build_dataset_tutorial.ipynb

Large diffs are not rendered by default.

Loading