datamint 2.44.0__tar.gz → 2.45.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {datamint-2.44.0 → datamint-2.45.0}/PKG-INFO +2 -2
- {datamint-2.44.0 → datamint-2.45.0}/README.md +1 -1
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/annotations_api.py +93 -105
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/annotationsets_api.py +8 -6
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/projects_api.py +8 -6
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/resources_api.py +3 -14
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/base_segmentation.py +58 -1
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/model.py +26 -19
- datamint-2.45.0/datamint/utils/date_utils.py +21 -0
- {datamint-2.44.0 → datamint-2.45.0}/pyproject.toml +1 -1
- {datamint-2.44.0 → datamint-2.45.0}/datamint/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/__main__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/_repr_utils.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/base_api.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/client.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/dto/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/dto/annotation_dto.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/dto/inference_prompts.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/dto/save_results_options.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/datasetsinfo_api.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/deploy_model_api.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/inference_api.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/model_types.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/models_api.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/pod_logs_api.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/users_api.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/endpoints/validation_studies_api.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/api/entity_base_api.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/client_cmd_tools/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/client_cmd_tools/datamint_config.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/client_cmd_tools/datamint_download.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/client_cmd_tools/datamint_example.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/client_cmd_tools/datamint_import.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/client_cmd_tools/datamint_inference.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/client_cmd_tools/datamint_init.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/client_cmd_tools/datamint_train.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/client_cmd_tools/datamint_upload.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/configs.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/annotation_processor.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/base.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/factory.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/image_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/multiframe_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/sliced_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/sliced_video_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/split_result.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/video_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/dataset/volume_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/default_project.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotation_worklist.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/annotation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/annotation_spec.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/base_geometry.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/box_annotation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/category_annotation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/geometry.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/image_classification.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/image_segmentation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/label_annotation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/line_annotation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/numeric_annotation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/point_annotation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/region_annotation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/text_annotation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/types.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/annotations/volume_segmentation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/base_entity.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/buildlog.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/cache_manager.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/channel.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/datasetinfo.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/deployjob.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/inferencejob.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/pod.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/project.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/project_resource_split.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/resource.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/resources/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/resources/dicom_resource.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/resources/image_resource.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/resources/nifti_resource.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/resources/video_resource.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/resources/volume_resource.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/sliced_resource.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/sliced_resource_base.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/sliced_video_resource.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/user.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/entities/validation_study.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/evaluate/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/evaluate/evaluator.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/evaluate/metrics/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/evaluate/metrics/classification.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/evaluate/metrics/detection.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/evaluate/metrics/segmentation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/examples/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/examples/_common.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/examples/_download.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/examples/bccd_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/examples/busi_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/examples/example_projects.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/examples/fracatlas_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/examples/synapse_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/exceptions.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/importers/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/importers/_common.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/importers/_detect.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/importers/coco.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/importers/pascal_voc.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/importers/yolo.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/benchmark.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/datamodule.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/base_trainer.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/classification_trainer.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/detection_trainer.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/base.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/classification_module.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/detection_modules/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/detection_modules/yolox_module.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/segmentation_module.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/segmentation_modules/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/segmentation_modules/deeplabv3plus.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/segmentation_modules/smp_module.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/segmentation_modules/transunet.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/segmentation_modules/unetpp.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/lightning_modules/segmentation_modules/unetrpp.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/seg2d_trainer.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/seg3d_trainer.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/segmentation_trainer.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/deeplabv3plus.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/efficientnetv2.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/nnunet/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/nnunet/_nnunet_trainer_bridge.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/nnunet/data_export.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/nnunet/data_import.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/nnunet/inference_model.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/nnunet/trainer.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/transunet.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/unetpp.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/unetrpp.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/specialized/yolox.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/lightning/trainers/vol_seg_trainer.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/logging.yaml +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/artifact/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/artifact/datamint_artifacts_repo.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/data/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/data/datamint_dataset.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/env_utils.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/env_vars.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/annotation_specs.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/datamint_flavor.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/model_loader.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/model_parser.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/prediction_modes.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/prediction_router.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/task_type.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/flavors/validation.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/lightning/callbacks/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/lightning/callbacks/modelcheckpoint.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/models/Dockerfile.template +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/models/__init__.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/models/datamint_model_store.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/models/docker_build.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/models/local_inference.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/models/serve.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/models/tags.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/store_utils.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/tracking/datamint_store.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/tracking/default_experiment.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/tracking/fluent.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/mlflow/tracking/offline_buffer.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/types.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/utils/annotation_agreement.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/utils/collection_utils.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/utils/env.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/utils/logging_utils.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/utils/nifti_utils.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/utils/torchmetrics.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/utils/uncertainty.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/utils/urls.py +0 -0
- {datamint-2.44.0 → datamint-2.45.0}/datamint/utils/visualization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datamint
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.45.0
|
|
4
4
|
Summary: A library for interacting with the Datamint API, designed for efficient data management, processing and Deep Learning workflows.
|
|
5
5
|
Keywords: medical-imaging,dicom,nifti,pytorch,mlflow,lightning,datasets,annotation
|
|
6
6
|
Author: SonanceAI
|
|
@@ -84,7 +84,7 @@ Description-Content-Type: text/markdown
|
|
|
84
84
|

|
|
85
85
|
[](https://www.python.org/downloads/)
|
|
86
86
|
|
|
87
|
-
Datamint turns medical imaging ML work
|
|
87
|
+
Datamint turns medical imaging ML work (dataset management, annotation, training and deployment) into a few lines of Python, with built-in support for DICOM, NIfTI and PNG, PyTorch Lightning trainers and MLflow tracking.
|
|
88
88
|
|
|
89
89
|
**Common use cases:** 🩻 Segmentation · 🏷️ Classification · 📦 Detection
|
|
90
90
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|

|
|
8
8
|
[](https://www.python.org/downloads/)
|
|
9
9
|
|
|
10
|
-
Datamint turns medical imaging ML work
|
|
10
|
+
Datamint turns medical imaging ML work (dataset management, annotation, training and deployment) into a few lines of Python, with built-in support for DICOM, NIfTI and PNG, PyTorch Lightning trainers and MLflow tracking.
|
|
11
11
|
|
|
12
12
|
**Common use cases:** 🩻 Segmentation · 🏷️ Classification · 📦 Detection
|
|
13
13
|
|
|
@@ -6,7 +6,7 @@ from collections.abc import Generator, Sequence
|
|
|
6
6
|
from datetime import date
|
|
7
7
|
from io import BytesIO
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import IO, TYPE_CHECKING, Any,
|
|
9
|
+
from typing import IO, TYPE_CHECKING, Any, Literal, overload
|
|
10
10
|
|
|
11
11
|
import aiohttp
|
|
12
12
|
import httpx
|
|
@@ -36,6 +36,7 @@ from datamint.entities.annotations import (
|
|
|
36
36
|
annotation_from_dict,
|
|
37
37
|
)
|
|
38
38
|
from datamint.exceptions import ItemNotFoundError, ServerError
|
|
39
|
+
from datamint.utils.date_utils import to_iso_date_str
|
|
39
40
|
from datamint.utils.nifti_utils import metadata_to_nifti_obj
|
|
40
41
|
|
|
41
42
|
from ..entity_base_api import ApiConfig, CreatableEntityApi, DeletableEntityApi
|
|
@@ -81,8 +82,8 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
81
82
|
resource: str | Resource | Sequence[str | Resource] | None = None,
|
|
82
83
|
annotation_type: AnnotationType | str | None = None,
|
|
83
84
|
annotator_email: str | None = None,
|
|
84
|
-
from_date: date | None = None,
|
|
85
|
-
to_date: date | None = None,
|
|
85
|
+
from_date: date | str | None = None,
|
|
86
|
+
to_date: date | str | None = None,
|
|
86
87
|
dataset_id: str | None = None,
|
|
87
88
|
worklist_id: str | None = None,
|
|
88
89
|
status: Literal['new', 'published'] | None = None,
|
|
@@ -98,8 +99,8 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
98
99
|
resource: str | Resource | Sequence[str | Resource] | None = None,
|
|
99
100
|
annotation_type: AnnotationType | str | None = None,
|
|
100
101
|
annotator_email: str | None = None,
|
|
101
|
-
from_date: date | None = None,
|
|
102
|
-
to_date: date | None = None,
|
|
102
|
+
from_date: date | str | None = None,
|
|
103
|
+
to_date: date | str | None = None,
|
|
103
104
|
dataset_id: str | None = None,
|
|
104
105
|
worklist_id: str | None = None,
|
|
105
106
|
status: Literal['new', 'published'] | None = None,
|
|
@@ -116,8 +117,8 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
116
117
|
resource: str | Resource | Sequence[str | Resource] | None = None,
|
|
117
118
|
annotation_type: AnnotationType | str | None = None,
|
|
118
119
|
annotator_email: str | None = None,
|
|
119
|
-
from_date: date | None = None,
|
|
120
|
-
to_date: date | None = None,
|
|
120
|
+
from_date: date | str | None = None,
|
|
121
|
+
to_date: date | str | None = None,
|
|
121
122
|
dataset_id: str | None = None,
|
|
122
123
|
worklist_id: str | None = None,
|
|
123
124
|
status: Literal['new', 'published'] | None = None,
|
|
@@ -136,8 +137,8 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
136
137
|
a list of resources, or None to retrieve annotations from all resources.
|
|
137
138
|
annotation_type: Filter by annotation type (e.g., 'segmentation', 'category').
|
|
138
139
|
annotator_email: Filter by annotator email address.
|
|
139
|
-
from_date: Filter annotations created on or after this date.
|
|
140
|
-
to_date: Filter annotations created on or before this date.
|
|
140
|
+
from_date: Filter annotations created on or after this date (``date`` or ISO date string).
|
|
141
|
+
to_date: Filter annotations created on or before this date (``date`` or ISO date string).
|
|
141
142
|
dataset_id: Filter by dataset unique id.
|
|
142
143
|
worklist_id: Filter by annotation worklist unique id.
|
|
143
144
|
status: Filter by annotation status ('new' or 'published').
|
|
@@ -190,8 +191,8 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
190
191
|
payload = {
|
|
191
192
|
'annotation_type': annotation_type,
|
|
192
193
|
'annotatorEmail': annotator_email,
|
|
193
|
-
'from': from_date
|
|
194
|
-
'to': to_date
|
|
194
|
+
'from': to_iso_date_str(from_date),
|
|
195
|
+
'to': to_iso_date_str(to_date),
|
|
195
196
|
'dataset_id': dataset_id,
|
|
196
197
|
'annotation_worklist_id': worklist_id,
|
|
197
198
|
'status': status,
|
|
@@ -307,7 +308,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
307
308
|
)
|
|
308
309
|
|
|
309
310
|
# Handle frame-by-frame upload (existing logic)
|
|
310
|
-
nframes,
|
|
311
|
+
nframes, frames = AnnotationsApi._generate_segmentation_frames(
|
|
311
312
|
file_path, transpose_segmentation=transpose_segmentation
|
|
312
313
|
)
|
|
313
314
|
if frame_index is None:
|
|
@@ -322,11 +323,11 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
322
323
|
raise ValueError("frame_index must be a list of integers or None.")
|
|
323
324
|
|
|
324
325
|
annotids = []
|
|
325
|
-
for fidx,
|
|
326
|
+
for fidx, frame in zip(frames_indices, frames):
|
|
326
327
|
frame_annotids = await self._upload_single_frame_segmentation_async(
|
|
327
328
|
resource_id=resource_id,
|
|
328
329
|
frame_index=fidx,
|
|
329
|
-
|
|
330
|
+
frame=frame,
|
|
330
331
|
name=name,
|
|
331
332
|
imported_from=imported_from,
|
|
332
333
|
author_email=author_email,
|
|
@@ -342,7 +343,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
342
343
|
async def _upload_single_frame_segmentation_async(self,
|
|
343
344
|
resource_id: str,
|
|
344
345
|
frame_index: int | None,
|
|
345
|
-
|
|
346
|
+
frame: np.ndarray,
|
|
346
347
|
name: dict[int, str] | dict[tuple, str],
|
|
347
348
|
imported_from: str | None = None,
|
|
348
349
|
author_email: str | None = None,
|
|
@@ -358,7 +359,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
358
359
|
Args:
|
|
359
360
|
resource_id: The resource unique id.
|
|
360
361
|
frame_index: The frame index for the segmentation.
|
|
361
|
-
|
|
362
|
+
frame: Segmentation frame as a uint8 array of shape (height, width, 3).
|
|
362
363
|
name: The name of the segmentation, a dictionary mapping pixel values to names,
|
|
363
364
|
or a dictionary mapping RGB tuples to names.
|
|
364
365
|
imported_from: The imported from value.
|
|
@@ -372,81 +373,73 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
372
373
|
List of annotation IDs created.
|
|
373
374
|
"""
|
|
374
375
|
try:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
import_author=author_email,
|
|
421
|
-
model_id=model_id,
|
|
422
|
-
annotation_worklist_id=worklist_id,
|
|
423
|
-
source=source,
|
|
424
|
-
)
|
|
425
|
-
annotations.append(ann)
|
|
426
|
-
|
|
427
|
-
# Validate unique identifiers
|
|
428
|
-
if len(annotations) != len({a.identifier for a in annotations}):
|
|
429
|
-
raise ValueError(
|
|
430
|
-
"Multiple annotations with the same identifier, frame_index, scope and author is not supported yet."
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
annotids = await self._create_annotations_async(resource_id=resource_id,
|
|
434
|
-
annotations_dto=annotations,
|
|
435
|
-
session=session)
|
|
376
|
+
# Returns a list of (count, color) tuples
|
|
377
|
+
unique_vals = Image.fromarray(frame).getcolors(maxcolors=MAX_NUMBER_DISTINCT_COLORS)
|
|
378
|
+
# convert to list of RGB tuples
|
|
379
|
+
if unique_vals is None:
|
|
380
|
+
raise ValueError(f'Number of unique colors exceeds {MAX_NUMBER_DISTINCT_COLORS}.')
|
|
381
|
+
rgb_unique_vals = [
|
|
382
|
+
(int(color[0]), int(color[1]), int(color[2]))
|
|
383
|
+
for _, color in unique_vals
|
|
384
|
+
if isinstance(color, tuple) and len(color) >= 3
|
|
385
|
+
]
|
|
386
|
+
# Remove black pixels
|
|
387
|
+
black_pixel = (0, 0, 0)
|
|
388
|
+
rgb_unique_vals = [rgb for rgb in rgb_unique_vals if rgb != black_pixel]
|
|
389
|
+
|
|
390
|
+
rgb_name_map: dict[tuple[int, int, int] | str, str] = {}
|
|
391
|
+
for key, value in name.items():
|
|
392
|
+
if key == 'default':
|
|
393
|
+
rgb_name_map['default'] = value
|
|
394
|
+
elif isinstance(key, tuple) and len(key) >= 3:
|
|
395
|
+
rgb_name_map[(int(key[0]), int(key[1]), int(key[2]))] = value
|
|
396
|
+
|
|
397
|
+
if discard_empty_segmentations:
|
|
398
|
+
if len(rgb_unique_vals) == 0:
|
|
399
|
+
msg = f"Discarding empty RGB segmentation for frame {frame_index}"
|
|
400
|
+
_LOGGER.debug(msg)
|
|
401
|
+
_USER_LOGGER.debug(msg)
|
|
402
|
+
return []
|
|
403
|
+
segnames = AnnotationsApi._get_segmentation_names_rgb(rgb_unique_vals, names=rgb_name_map)
|
|
404
|
+
segs_generator = AnnotationsApi._split_rgb_segmentations(frame, rgb_unique_vals)
|
|
405
|
+
|
|
406
|
+
# Create annotations
|
|
407
|
+
annotations: list[CreateAnnotationDto] = []
|
|
408
|
+
for segname in segnames:
|
|
409
|
+
ann = CreateAnnotationDto(
|
|
410
|
+
type='segmentation',
|
|
411
|
+
identifier=segname,
|
|
412
|
+
scope='frame',
|
|
413
|
+
frame_index=frame_index,
|
|
414
|
+
imported_from=imported_from,
|
|
415
|
+
import_author=author_email,
|
|
416
|
+
model_id=model_id,
|
|
417
|
+
annotation_worklist_id=worklist_id,
|
|
418
|
+
source=source,
|
|
419
|
+
)
|
|
420
|
+
annotations.append(ann)
|
|
436
421
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
422
|
+
# Validate unique identifiers
|
|
423
|
+
if len(annotations) != len({a.identifier for a in annotations}):
|
|
424
|
+
raise ValueError(
|
|
425
|
+
"Multiple annotations with the same identifier, frame_index, scope and author is not supported yet."
|
|
426
|
+
)
|
|
441
427
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
content_type='image/png',
|
|
445
|
-
filename=segname,
|
|
428
|
+
annotids = await self._create_annotations_async(resource_id=resource_id,
|
|
429
|
+
annotations_dto=annotations,
|
|
446
430
|
session=session)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
431
|
+
|
|
432
|
+
# Upload segmentation files
|
|
433
|
+
if len(annotids) != len(segnames):
|
|
434
|
+
_LOGGER.warning(f"Number of uploaded annotations ({len(annotids)})" +
|
|
435
|
+
f" does not match the number of annotations ({len(segnames)})")
|
|
436
|
+
|
|
437
|
+
for annotid, segname, fio_seg in zip(annotids, segnames, segs_generator):
|
|
438
|
+
await self.upload_annotation_file_async(resource_id, annotid, fio_seg,
|
|
439
|
+
content_type='image/png',
|
|
440
|
+
filename=segname,
|
|
441
|
+
session=session)
|
|
442
|
+
return annotids
|
|
450
443
|
except ItemNotFoundError:
|
|
451
444
|
raise ItemNotFoundError('resource', {'resource_id': resource_id})
|
|
452
445
|
|
|
@@ -1013,9 +1006,9 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
1013
1006
|
_USER_LOGGER.info(f'Volume segmentation uploaded for resource {resource_id}')
|
|
1014
1007
|
|
|
1015
1008
|
@staticmethod
|
|
1016
|
-
def
|
|
1017
|
-
|
|
1018
|
-
|
|
1009
|
+
def _generate_segmentation_frames(file_path: str | np.ndarray,
|
|
1010
|
+
transpose_segmentation: bool = False
|
|
1011
|
+
) -> tuple[int, Generator[np.ndarray, None, None]]:
|
|
1019
1012
|
if not isinstance(file_path, (str, np.ndarray)):
|
|
1020
1013
|
raise ValueError(f"Unsupported file type: {type(file_path)}")
|
|
1021
1014
|
|
|
@@ -1029,7 +1022,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
1029
1022
|
normalized_imgs = normalized_imgs.transpose(0, 2, 1, 3)
|
|
1030
1023
|
|
|
1031
1024
|
nframes = normalized_imgs.shape[3]
|
|
1032
|
-
|
|
1025
|
+
frames = AnnotationsApi._numpy_to_rgb_frames(normalized_imgs)
|
|
1033
1026
|
|
|
1034
1027
|
elif file_path.endswith(('.nii', '.nii.gz')):
|
|
1035
1028
|
loaded_image: Any = nib_load(file_path)
|
|
@@ -1045,7 +1038,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
1045
1038
|
normalized_imgs = normalized_imgs.transpose(0, 2, 1, 3)
|
|
1046
1039
|
|
|
1047
1040
|
nframes = normalized_imgs.shape[3]
|
|
1048
|
-
|
|
1041
|
+
frames = AnnotationsApi._numpy_to_rgb_frames(normalized_imgs)
|
|
1049
1042
|
|
|
1050
1043
|
elif file_path.endswith('.png'):
|
|
1051
1044
|
with Image.open(file_path) as img:
|
|
@@ -1055,12 +1048,12 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
1055
1048
|
if transpose_segmentation:
|
|
1056
1049
|
normalized_imgs = normalized_imgs.transpose(0, 2, 1, 3)
|
|
1057
1050
|
|
|
1058
|
-
|
|
1051
|
+
frames = AnnotationsApi._numpy_to_rgb_frames(normalized_imgs)
|
|
1059
1052
|
nframes = 1
|
|
1060
1053
|
else:
|
|
1061
1054
|
raise ValueError(f"Unsupported file format of '{file_path}'")
|
|
1062
1055
|
|
|
1063
|
-
return nframes,
|
|
1056
|
+
return nframes, frames
|
|
1064
1057
|
|
|
1065
1058
|
@staticmethod
|
|
1066
1059
|
def _normalize_segmentation_array(seg_imgs: np.ndarray) -> np.ndarray:
|
|
@@ -1087,15 +1080,15 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
1087
1080
|
return seg_imgs
|
|
1088
1081
|
|
|
1089
1082
|
@staticmethod
|
|
1090
|
-
def
|
|
1083
|
+
def _numpy_to_rgb_frames(seg_imgs: np.ndarray) -> Generator[np.ndarray, None, None]:
|
|
1091
1084
|
"""
|
|
1092
|
-
Convert normalized segmentation images to
|
|
1085
|
+
Convert normalized segmentation images to RGB frames.
|
|
1093
1086
|
|
|
1094
1087
|
Args:
|
|
1095
1088
|
seg_imgs: Normalized segmentation array in shape (channels, height, width, frames).
|
|
1096
1089
|
|
|
1097
1090
|
Yields:
|
|
1098
|
-
|
|
1091
|
+
np.ndarray: uint8 array of shape (height, width, 3)
|
|
1099
1092
|
"""
|
|
1100
1093
|
# PIL RGB format is: (height, width, channels)
|
|
1101
1094
|
if seg_imgs.shape[0] not in [1, 3, 4]:
|
|
@@ -1104,13 +1097,8 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
1104
1097
|
for i in range(nframes):
|
|
1105
1098
|
img = seg_imgs[:, :, :, i].astype(np.uint8)
|
|
1106
1099
|
if img.shape[0] == 1:
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
pil_img = Image.fromarray(img.transpose(1, 2, 0))
|
|
1110
|
-
img_bytes = BytesIO()
|
|
1111
|
-
pil_img.save(img_bytes, format='PNG')
|
|
1112
|
-
img_bytes.seek(0)
|
|
1113
|
-
yield img_bytes
|
|
1100
|
+
img = np.repeat(img, 3, axis=0)
|
|
1101
|
+
yield np.ascontiguousarray(img[:3].transpose(1, 2, 0))
|
|
1114
1102
|
|
|
1115
1103
|
def create_image_classification(self,
|
|
1116
1104
|
resource: str | Resource,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from collections.abc import Sequence
|
|
3
|
+
from datetime import date
|
|
3
4
|
from typing import TYPE_CHECKING, Any, Literal, overload
|
|
4
5
|
|
|
5
6
|
from typing_extensions import override
|
|
6
7
|
|
|
7
8
|
from datamint.entities.annotation_worklist import AnnotationWorklist
|
|
9
|
+
from datamint.utils.date_utils import to_iso_date_str
|
|
8
10
|
|
|
9
11
|
from ..entity_base_api import CreatableEntityApi, UpdatableEntityApi
|
|
10
12
|
|
|
@@ -723,8 +725,8 @@ class AnnotationWorklistApi(CreatableEntityApi[AnnotationWorklist],
|
|
|
723
725
|
def download_annotations(
|
|
724
726
|
self,
|
|
725
727
|
worklist_id: str | None = None,
|
|
726
|
-
from_date: str | None = None,
|
|
727
|
-
to_date: str | None = None,
|
|
728
|
+
from_date: date | str | None = None,
|
|
729
|
+
to_date: date | str | None = None,
|
|
728
730
|
annotators: list[str] | None = None,
|
|
729
731
|
annotations: list[str] | None = None,
|
|
730
732
|
format: str = 'csv',
|
|
@@ -733,8 +735,8 @@ class AnnotationWorklistApi(CreatableEntityApi[AnnotationWorklist],
|
|
|
733
735
|
|
|
734
736
|
Args:
|
|
735
737
|
worklist_id: The annotation worklist ID.
|
|
736
|
-
from_date: Optional start date filter (ISO string).
|
|
737
|
-
to_date: Optional end date filter (ISO string).
|
|
738
|
+
from_date: Optional start date filter (``date`` or ISO date string).
|
|
739
|
+
to_date: Optional end date filter (``date`` or ISO date string).
|
|
738
740
|
annotators: Optional list of annotator emails. Accepts either a repeated/array
|
|
739
741
|
input or a comma-separated string.
|
|
740
742
|
annotations: Optional list of annotation identifiers. Accepts either a repeated/array
|
|
@@ -749,9 +751,9 @@ class AnnotationWorklistApi(CreatableEntityApi[AnnotationWorklist],
|
|
|
749
751
|
|
|
750
752
|
params: dict[str, str | list[str]] = {'format': format}
|
|
751
753
|
if from_date is not None:
|
|
752
|
-
params['from'] = from_date
|
|
754
|
+
params['from'] = to_iso_date_str(from_date)
|
|
753
755
|
if to_date is not None:
|
|
754
|
-
params['to'] = to_date
|
|
756
|
+
params['to'] = to_iso_date_str(to_date)
|
|
755
757
|
if annotators is not None:
|
|
756
758
|
params['annotators[]'] = annotators
|
|
757
759
|
if annotations is not None:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
from collections.abc import Sequence
|
|
3
|
+
from datetime import date
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import TYPE_CHECKING, Literal, overload
|
|
5
6
|
|
|
@@ -8,6 +9,7 @@ import httpx
|
|
|
8
9
|
from datamint.entities.project import Project
|
|
9
10
|
from datamint.entities.project_resource_split import ProjectResourceSplit
|
|
10
11
|
from datamint.entities.resource import Resource
|
|
12
|
+
from datamint.utils.date_utils import to_iso_date_str
|
|
11
13
|
from datamint.exceptions import (
|
|
12
14
|
DefaultProjectNotSetError,
|
|
13
15
|
EntityAlreadyExistsError,
|
|
@@ -544,8 +546,8 @@ class ProjectsApi(CRUDEntityApi[Project]):
|
|
|
544
546
|
format: str = 'csv',
|
|
545
547
|
annotators: list[str] | None = None,
|
|
546
548
|
annotations: list[str] | None = None,
|
|
547
|
-
from_date: str | None = None,
|
|
548
|
-
to_date: str | None = None,
|
|
549
|
+
from_date: date | str | None = None,
|
|
550
|
+
to_date: date | str | None = None,
|
|
549
551
|
progress_bar: bool = True,
|
|
550
552
|
project: str | Project | None = None) -> None:
|
|
551
553
|
"""Download annotation data as a CSV or Excel file.
|
|
@@ -555,8 +557,8 @@ class ProjectsApi(CRUDEntityApi[Project]):
|
|
|
555
557
|
format: Export format, ``'csv'`` (default) or ``'xlsx'``.
|
|
556
558
|
annotators: Optional list of annotator emails to include.
|
|
557
559
|
annotations: Optional list of annotation identifiers to include.
|
|
558
|
-
from_date: Optional start date filter (ISO string).
|
|
559
|
-
to_date: Optional end date filter (ISO string).
|
|
560
|
+
from_date: Optional start date filter (``date`` or ISO date string).
|
|
561
|
+
to_date: Optional end date filter (``date`` or ISO date string).
|
|
560
562
|
progress_bar: Whether to display a progress bar.
|
|
561
563
|
project: The project ID or Project instance. Falls back to the
|
|
562
564
|
session's default project (see `datamint.select_project()`) when omitted.
|
|
@@ -566,9 +568,9 @@ class ProjectsApi(CRUDEntityApi[Project]):
|
|
|
566
568
|
proj_id = self._entid(self._resolve_project_or_default(project))
|
|
567
569
|
params: dict = {'format': format}
|
|
568
570
|
if from_date is not None:
|
|
569
|
-
params['from'] = from_date
|
|
571
|
+
params['from'] = to_iso_date_str(from_date)
|
|
570
572
|
if to_date is not None:
|
|
571
|
-
params['to'] = to_date
|
|
573
|
+
params['to'] = to_iso_date_str(to_date)
|
|
572
574
|
if annotators is not None:
|
|
573
575
|
params['annotators[]'] = annotators
|
|
574
576
|
if annotations is not None:
|
|
@@ -30,6 +30,7 @@ from datamint.entities.annotations import AnnotationType
|
|
|
30
30
|
from datamint.entities.annotations.annotation import Annotation
|
|
31
31
|
from datamint.exceptions import ItemNotFoundError, ServerError, ValidationError
|
|
32
32
|
from datamint.types import ImagingData
|
|
33
|
+
from datamint.utils.date_utils import to_iso_date_str
|
|
33
34
|
from datamint.utils.collection_utils import ChainedSequence
|
|
34
35
|
|
|
35
36
|
from ..base_api import ApiConfig, BaseApi
|
|
@@ -191,22 +192,10 @@ class ResourcesApi(CreatableEntityApi[Resource], DeletableEntityApi[Resource]):
|
|
|
191
192
|
'include' returns both. Deleted resources are only visible to admins and librarians.
|
|
192
193
|
"""
|
|
193
194
|
|
|
194
|
-
# Convert datetime objects to ISO format
|
|
195
|
-
if from_date:
|
|
196
|
-
if isinstance(from_date, str):
|
|
197
|
-
date.fromisoformat(from_date)
|
|
198
|
-
else:
|
|
199
|
-
from_date = from_date.isoformat()
|
|
200
|
-
if to_date:
|
|
201
|
-
if isinstance(to_date, str):
|
|
202
|
-
date.fromisoformat(to_date)
|
|
203
|
-
else:
|
|
204
|
-
to_date = to_date.isoformat()
|
|
205
|
-
|
|
206
195
|
# Prepare the payload
|
|
207
196
|
payload = {
|
|
208
|
-
"from": from_date,
|
|
209
|
-
"to": to_date,
|
|
197
|
+
"from": to_iso_date_str(from_date),
|
|
198
|
+
"to": to_iso_date_str(to_date),
|
|
210
199
|
"status": status if status is not None else "",
|
|
211
200
|
"modality": modality,
|
|
212
201
|
"mimetype": mimetype,
|
|
@@ -20,7 +20,7 @@ from pydantic import BeforeValidator, Field, PlainSerializer
|
|
|
20
20
|
|
|
21
21
|
from datamint.types import CacheMode, ImagingData
|
|
22
22
|
|
|
23
|
-
from .annotation import Annotation
|
|
23
|
+
from .annotation import Annotation, _normalize_annotation_data
|
|
24
24
|
|
|
25
25
|
_LOGGER = logging.getLogger(__name__)
|
|
26
26
|
|
|
@@ -286,6 +286,63 @@ class BaseSegmentationAnnotation(Annotation):
|
|
|
286
286
|
'Use upload_segmentations or upload_volume_segmentation instead.'
|
|
287
287
|
)
|
|
288
288
|
|
|
289
|
+
@classmethod
|
|
290
|
+
def from_dict(cls, data: dict[str, Any]) -> 'BaseSegmentationAnnotation':
|
|
291
|
+
"""Create a segmentation annotation from a dictionary.
|
|
292
|
+
|
|
293
|
+
This overrides :meth:`Annotation.from_dict`, which rejects
|
|
294
|
+
segmentation annotations that have no associated ``file``. A
|
|
295
|
+
segmentation payload may instead carry the mask inline through the
|
|
296
|
+
``segmentation_data`` field (or its ``mask`` alias), as produced by
|
|
297
|
+
:meth:`BaseEntity.asdict` / ``model_dump``. Such payloads are
|
|
298
|
+
accepted here and the mask is decoded by the Pydantic
|
|
299
|
+
``BeforeValidator`` on :attr:`segmentation_data`.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
data: Dictionary containing annotation data, optionally including
|
|
303
|
+
an inline ``segmentation_data`` (or ``mask``) payload.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
A segmentation annotation instance with ``segmentation_data``
|
|
307
|
+
decoded to a native object (``np.ndarray``, ``PIL.Image.Image``
|
|
308
|
+
or ``Nifti1Image``).
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
ValueError: If neither an associated ``file`` nor an inline
|
|
312
|
+
``segmentation_data`` payload is present.
|
|
313
|
+
|
|
314
|
+
Example:
|
|
315
|
+
>>> import json
|
|
316
|
+
>>> from datamint.entities.annotations import ImageSegmentation
|
|
317
|
+
>>> with open('annotation.json') as f:
|
|
318
|
+
... raw = json.load(f) # list[list[dict]]
|
|
319
|
+
>>> seg = ImageSegmentation.from_dict(raw[0][0])
|
|
320
|
+
>>> seg.segmentation_data.shape
|
|
321
|
+
"""
|
|
322
|
+
converted_data = _normalize_annotation_data(data)
|
|
323
|
+
|
|
324
|
+
has_inline_mask = (
|
|
325
|
+
converted_data.get('segmentation_data') is not None
|
|
326
|
+
or converted_data.get('mask') is not None
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
if converted_data.get('file') is None and not has_inline_mask:
|
|
330
|
+
raise ValueError(
|
|
331
|
+
"Segmentation annotations must have an associated file or an "
|
|
332
|
+
f"inline segmentation_data payload. {data}"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Accept both field names and their aliases (e.g. ``mask`` for
|
|
336
|
+
# ``segmentation_data``) so inline payloads are not silently dropped.
|
|
337
|
+
valid_fields = set(cls.model_fields)
|
|
338
|
+
for field in cls.model_fields.values():
|
|
339
|
+
if field.alias:
|
|
340
|
+
valid_fields.add(field.alias)
|
|
341
|
+
|
|
342
|
+
filtered_data = {k: v for k, v in converted_data.items() if k in valid_fields}
|
|
343
|
+
|
|
344
|
+
return cls(**filtered_data)
|
|
345
|
+
|
|
289
346
|
@overload
|
|
290
347
|
def fetch_file_data(
|
|
291
348
|
self,
|
|
@@ -281,28 +281,35 @@ class BaseDatamintModel(PythonModel, ABC):
|
|
|
281
281
|
:class:`~datamint.mlflow.flavors.prediction_router.DeployedModelPrompts` instance before dispatch,
|
|
282
282
|
so ``predict_*`` handlers receive a typed object rather than a raw dict.
|
|
283
283
|
"""
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
284
|
+
logger.info(f'Predicting on {len(model_input)} resources')
|
|
285
|
+
try:
|
|
286
|
+
params = dict(params or {})
|
|
287
|
+
log_predictions = params.pop('log_predictions', False)
|
|
288
|
+
model_name = params.pop('model_name', None)
|
|
289
|
+
|
|
290
|
+
raw_prompts = params.pop('prompts', None)
|
|
291
|
+
if raw_prompts is not None:
|
|
292
|
+
|
|
293
|
+
# Sent JSON-encoded
|
|
294
|
+
if isinstance(raw_prompts, str):
|
|
295
|
+
raw_prompts = json.loads(raw_prompts)
|
|
296
|
+
params['prompts'] = (
|
|
297
|
+
raw_prompts
|
|
298
|
+
if isinstance(raw_prompts, DeployedModelPrompts)
|
|
299
|
+
else DeployedModelPrompts.from_dict(raw_prompts)
|
|
300
|
+
)
|
|
301
|
+
|
|
299
302
|
|
|
300
|
-
|
|
303
|
+
result = self._router.dispatch(model_input, params)
|
|
301
304
|
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
if log_predictions:
|
|
306
|
+
self._log_predictions(model_input, result, model_name=model_name)
|
|
304
307
|
|
|
305
|
-
|
|
308
|
+
return result
|
|
309
|
+
except Exception as e:
|
|
310
|
+
logger.error(f'Failed to predict on {len(model_input)} resources: {e}')
|
|
311
|
+
logger.exception(e)
|
|
312
|
+
raise
|
|
306
313
|
|
|
307
314
|
def _log_predictions(
|
|
308
315
|
self,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def to_iso_date_str(value: date | str | None) -> str | None:
|
|
5
|
+
"""Normalize a date filter to an ISO 8601 string.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
value: A ``date``/``datetime`` or an ISO date string (``YYYY-MM-DD``).
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
The ISO string, or ``None`` if ``value`` is ``None``.
|
|
12
|
+
|
|
13
|
+
Raises:
|
|
14
|
+
ValueError: If ``value`` is a string that is not a valid ISO date.
|
|
15
|
+
"""
|
|
16
|
+
if value is None:
|
|
17
|
+
return None
|
|
18
|
+
if isinstance(value, str):
|
|
19
|
+
date.fromisoformat(value)
|
|
20
|
+
return value
|
|
21
|
+
return value.isoformat()
|