├── docs ├── requirements.txt ├── api │ ├── train.rst │ ├── eval.rst │ ├── utils │ │ ├── time.rst │ │ ├── fold.rst │ │ ├── config.rst │ │ ├── test.rst │ │ ├── decorator.rst │ │ ├── os.rst │ │ ├── network.rst │ │ ├── encoding.rst │ │ ├── filtering.rst │ │ ├── image.rst │ │ ├── sampling.rst │ │ ├── utils.rst │ │ └── datahandler.rst │ ├── trainers.rst │ ├── predictions.rst │ ├── preprocess.rst │ ├── preprocess_train.rst │ ├── datasets │ │ ├── torchio.rst │ │ ├── patch_fast.rst │ │ ├── datasets.rst │ │ └── batchgen.rst │ ├── models │ │ ├── encoder_vgg.rst │ │ ├── eff3d.rst │ │ ├── decoder_vgg.rst │ │ ├── vgg_deep.rst │ │ ├── encoder_eff3d.rst │ │ └── models.rst │ ├── builder.rst │ ├── callbacks.rst │ ├── predictors.rst │ ├── auto_config.rst │ ├── api.rst │ ├── omero.rst │ ├── metrics.rst │ └── register.rst ├── _static │ ├── image │ │ ├── favicon.ico │ │ ├── old_logo.png │ │ ├── gui_splash.png │ │ ├── old_logo_2.png │ │ ├── gui_local_train.png │ │ ├── logo_biom3d_crop.png │ │ ├── omero_dataset_id.png │ │ ├── gui_local_predict.png │ │ ├── gui_remote_predict.png │ │ ├── logo_biom3d_green.png │ │ ├── gui_local_preprocess.png │ │ ├── gui_remote_preprocess.png │ │ ├── nucleus_segmentation.png │ │ ├── tuto_update_installer.png │ │ ├── gui_local_predict_omero.png │ │ ├── gui_local_send_to_omero.png │ │ ├── gui_remote_predict_omero.png │ │ ├── gui_remote_send_to_omero.png │ │ ├── gui_local_preprocess&train.png │ │ ├── gui_remote_preprocess&train.png │ │ └── gui_local_preprocess&train_loadconfig.png │ └── css │ │ └── readthedocs.css ├── dep │ ├── dep.rst │ ├── docker.md │ ├── installer.md │ └── cicd.md ├── Makefile ├── make.bat ├── index.rst ├── tuto │ ├── server.md │ ├── config.md │ └── dataset.md ├── how_it_works │ ├── pre_processing.md │ └── post_processing.md └── faq.md ├── src └── biom3d │ ├── datasets │ └── __init__.py │ ├── models │ ├── __init__.py │ └── unet3d_vgg_deep.py │ ├── logo_biom3d_minimal.png │ ├── utils │ ├── data_handler │ │ ├── __init__.py │ │ └── data_handler_factory.py │ ├── __init__.py │ ├── decorators.py │ ├── neural_network.py │ ├── time.py │ ├── os.py │ ├── eval_metrics.py │ └── fold.py │ ├── __init__.py │ ├── register.py │ ├── preprocess_train.py │ ├── eval.py │ └── omero_pred.py ├── bash ├── dist.sh ├── run_eval.sh ├── run_preprocess.sh ├── run_train.sh ├── run_preprocess_train.sh └── run_pred.sh ├── images ├── logo_UCA.jpg ├── logo_ip.png ├── logo_aura.PNG ├── GReD_color_EN.png ├── biom3d_train.png ├── logo_biom3d_crop.png ├── Flag_of_Europe.svg.png ├── brookes_logo_black.bmp ├── logo_biom3d_green.png ├── nucleus_segmentation.png └── nnunet_run_run_training.png ├── logo_biom3d_crop.ico ├── deployment ├── exe │ ├── macos │ │ ├── logo.icns │ │ ├── Biom3d.sh │ │ ├── Info.plist │ │ └── pack.sh │ ├── windows │ │ ├── logo.ico │ │ ├── Biom3d.bat │ │ └── pack.bat │ └── auto_update.py └── dockerfiles │ ├── entrypoint.sh │ ├── examples │ ├── biom3d_cpu_torch2_7.dockerfile │ ├── biom3d_cuda11_8cudnn8torch2_3.dockerfile │ ├── build.sh │ └── build.bat │ └── template.dockerfile ├── AUTHORS.md ├── .github └── workflows │ ├── config_docker.json │ └── config_docker_full.json ├── .readthedocs.yaml ├── pyproject.toml ├── .gitignore ├── CHANGELOG.md ├── tests ├── test_preprocess.py └── test_utils.py ├── README.md └── configs └── pancreas_example.py /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | .[docs] -------------------------------------------------------------------------------- /src/biom3d/datasets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/biom3d/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bash/dist.sh: -------------------------------------------------------------------------------- 1 | python -m build 2 | twine upload dist/* -------------------------------------------------------------------------------- /docs/api/train.rst: -------------------------------------------------------------------------------- 1 | Train 2 | ===== 3 | 4 | .. automodule:: biom3d.train 5 | :members: -------------------------------------------------------------------------------- /docs/api/eval.rst: -------------------------------------------------------------------------------- 1 | Evaluation 2 | ========== 3 | 4 | .. automodule:: biom3d.eval 5 | :members: -------------------------------------------------------------------------------- /docs/api/utils/time.rst: -------------------------------------------------------------------------------- 1 | Time 2 | ==== 3 | 4 | .. automodule:: biom3d.utils.time 5 | :members: -------------------------------------------------------------------------------- /images/logo_UCA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/logo_UCA.jpg -------------------------------------------------------------------------------- /images/logo_ip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/logo_ip.png -------------------------------------------------------------------------------- /docs/api/utils/fold.rst: -------------------------------------------------------------------------------- 1 | Folds 2 | ===== 3 | 4 | .. automodule:: biom3d.utils.fold 5 | :members: -------------------------------------------------------------------------------- /images/logo_aura.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/logo_aura.PNG -------------------------------------------------------------------------------- /logo_biom3d_crop.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/logo_biom3d_crop.ico -------------------------------------------------------------------------------- /docs/api/trainers.rst: -------------------------------------------------------------------------------- 1 | Trainers 2 | ======== 3 | 4 | .. automodule:: biom3d.trainers 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/api/utils/config.rst: -------------------------------------------------------------------------------- 1 | Config 2 | ====== 3 | 4 | .. automodule:: biom3d.utils.config 5 | :members: -------------------------------------------------------------------------------- /images/GReD_color_EN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/GReD_color_EN.png -------------------------------------------------------------------------------- /images/biom3d_train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/biom3d_train.png -------------------------------------------------------------------------------- /docs/api/predictions.rst: -------------------------------------------------------------------------------- 1 | Predictions 2 | =========== 3 | 4 | .. automodule:: biom3d.pred 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/api/utils/test.rst: -------------------------------------------------------------------------------- 1 | Testing 2 | ======= 3 | 4 | .. automodule:: biom3d.utils.eval_metrics 5 | :members: -------------------------------------------------------------------------------- /images/logo_biom3d_crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/logo_biom3d_crop.png -------------------------------------------------------------------------------- /docs/api/preprocess.rst: -------------------------------------------------------------------------------- 1 | Preprocessing 2 | ============= 3 | 4 | .. automodule:: biom3d.preprocess 5 | :members: -------------------------------------------------------------------------------- /docs/api/utils/decorator.rst: -------------------------------------------------------------------------------- 1 | Decorator 2 | ========= 3 | 4 | .. automodule:: biom3d.utils.decorators 5 | :members: -------------------------------------------------------------------------------- /images/Flag_of_Europe.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/Flag_of_Europe.svg.png -------------------------------------------------------------------------------- /images/brookes_logo_black.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/brookes_logo_black.bmp -------------------------------------------------------------------------------- /images/logo_biom3d_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/logo_biom3d_green.png -------------------------------------------------------------------------------- /deployment/exe/macos/logo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/deployment/exe/macos/logo.icns -------------------------------------------------------------------------------- /deployment/exe/windows/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/deployment/exe/windows/logo.ico -------------------------------------------------------------------------------- /docs/_static/image/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/favicon.ico -------------------------------------------------------------------------------- /docs/_static/image/old_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/old_logo.png -------------------------------------------------------------------------------- /docs/api/utils/os.rst: -------------------------------------------------------------------------------- 1 | Files & directories 2 | =================== 3 | 4 | .. automodule:: biom3d.utils.os 5 | :members: -------------------------------------------------------------------------------- /images/nucleus_segmentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/nucleus_segmentation.png -------------------------------------------------------------------------------- /docs/_static/image/gui_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_splash.png -------------------------------------------------------------------------------- /docs/_static/image/old_logo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/old_logo_2.png -------------------------------------------------------------------------------- /images/nnunet_run_run_training.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/images/nnunet_run_run_training.png -------------------------------------------------------------------------------- /src/biom3d/logo_biom3d_minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/src/biom3d/logo_biom3d_minimal.png -------------------------------------------------------------------------------- /docs/api/utils/network.rst: -------------------------------------------------------------------------------- 1 | Neural network 2 | ============== 3 | 4 | .. automodule:: biom3d.utils.neural_network 5 | :members: -------------------------------------------------------------------------------- /docs/_static/image/gui_local_train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_local_train.png -------------------------------------------------------------------------------- /docs/_static/image/logo_biom3d_crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/logo_biom3d_crop.png -------------------------------------------------------------------------------- /docs/_static/image/omero_dataset_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/omero_dataset_id.png -------------------------------------------------------------------------------- /docs/_static/image/gui_local_predict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_local_predict.png -------------------------------------------------------------------------------- /docs/_static/image/gui_remote_predict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_remote_predict.png -------------------------------------------------------------------------------- /docs/_static/image/logo_biom3d_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/logo_biom3d_green.png -------------------------------------------------------------------------------- /docs/api/preprocess_train.rst: -------------------------------------------------------------------------------- 1 | Preprocess & train 2 | ================== 3 | 4 | .. automodule:: biom3d.preprocess_train 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/_static/image/gui_local_preprocess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_local_preprocess.png -------------------------------------------------------------------------------- /docs/_static/image/gui_remote_preprocess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_remote_preprocess.png -------------------------------------------------------------------------------- /docs/_static/image/nucleus_segmentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/nucleus_segmentation.png -------------------------------------------------------------------------------- /docs/_static/image/tuto_update_installer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/tuto_update_installer.png -------------------------------------------------------------------------------- /docs/_static/image/gui_local_predict_omero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_local_predict_omero.png -------------------------------------------------------------------------------- /docs/_static/image/gui_local_send_to_omero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_local_send_to_omero.png -------------------------------------------------------------------------------- /docs/_static/image/gui_remote_predict_omero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_remote_predict_omero.png -------------------------------------------------------------------------------- /docs/_static/image/gui_remote_send_to_omero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_remote_send_to_omero.png -------------------------------------------------------------------------------- /src/biom3d/utils/data_handler/__init__.py: -------------------------------------------------------------------------------- 1 | from .data_handler_factory import DataHandlerFactory 2 | from .data_handler_abstract import DataHandler 3 | -------------------------------------------------------------------------------- /docs/_static/image/gui_local_preprocess&train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_local_preprocess&train.png -------------------------------------------------------------------------------- /docs/_static/image/gui_remote_preprocess&train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_remote_preprocess&train.png -------------------------------------------------------------------------------- /docs/_static/image/gui_local_preprocess&train_loadconfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuillaumeMougeot/biom3d/HEAD/docs/_static/image/gui_local_preprocess&train_loadconfig.png -------------------------------------------------------------------------------- /docs/api/datasets/torchio.rst: -------------------------------------------------------------------------------- 1 | Torchio 2 | ======= 3 | 4 | Torchio dataloader, aimed principally for data augmentation. 5 | 6 | 7 | .. automodule:: biom3d.datasets.semseg_torchio 8 | :members: -------------------------------------------------------------------------------- /src/biom3d/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib.metadata import version, PackageNotFoundError 2 | 3 | try: 4 | __version__ = version("biom3d") 5 | except PackageNotFoundError: 6 | __version__ = None -------------------------------------------------------------------------------- /docs/api/models/encoder_vgg.rst: -------------------------------------------------------------------------------- 1 | VGG3D Encoder 2 | ============= 3 | 4 | This encoder is currently used by :class:`UNet`. 5 | 6 | 7 | .. automodule:: biom3d.models.encoder_vgg 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/api/utils/encoding.rst: -------------------------------------------------------------------------------- 1 | Encoding 2 | ======== 3 | 4 | .. note:: 5 | 6 | For the moment, this submodule is only used in preprocessing. 7 | 8 | .. automodule:: biom3d.utils.encoding 9 | :members: -------------------------------------------------------------------------------- /docs/api/models/eff3d.rst: -------------------------------------------------------------------------------- 1 | EffUNet 2 | ======= 3 | 4 | This is the encoder-decoder that use :class:`EfficientNet3D` encoder and a :class:`VGGDecoder`. 5 | 6 | 7 | .. automodule:: biom3d.models.unet3d_eff 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/api/builder.rst: -------------------------------------------------------------------------------- 1 | Builder 2 | ======= 3 | 4 | The Builder is the core class of biom3d. It is used to train a model or to test a model in a couple of lines of code. 5 | 6 | .. automodule:: biom3d.builder 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/api/datasets/patch_fast.rst: -------------------------------------------------------------------------------- 1 | Semseg patch fast 2 | ================= 3 | 4 | This dataloader is the default dataloader, it is inspired from nnUnet but simplified. 5 | 6 | 7 | .. automodule:: biom3d.datasets.semseg_patch_fast 8 | :members: -------------------------------------------------------------------------------- /docs/api/models/decoder_vgg.rst: -------------------------------------------------------------------------------- 1 | VGG3D Decoder 2 | ============= 3 | 4 | This decoder is based on nnUnet decoder, and is currently used by :class:`EffUNet` and :class:`UNet` 5 | 6 | 7 | .. automodule:: biom3d.models.decoder_vgg_deep 8 | :members: -------------------------------------------------------------------------------- /docs/api/models/vgg_deep.rst: -------------------------------------------------------------------------------- 1 | VGG3dDeep 2 | ========= 3 | 4 | This is the encoder-decoder that use :class:`VGGEncoder` encoder and a :class:`VGGDecoder`. It is an adaptation nnUnet. 5 | 6 | 7 | .. automodule:: biom3d.models.unet3d_vgg_deep 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/dep/dep.rst: -------------------------------------------------------------------------------- 1 | Deployment 2 | ========== 3 | Here are describe the logic behind deployment and its implementation. 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: Deployment 8 | 9 | docker 10 | installer 11 | cicd -------------------------------------------------------------------------------- /docs/api/utils/filtering.rst: -------------------------------------------------------------------------------- 1 | Filtering & thresholding 2 | ======================== 3 | 4 | .. note:: 5 | 6 | For the moment, this submodule is only used in post processing. 7 | 8 | 9 | .. automodule:: biom3d.utils.filtering 10 | :members: 11 | -------------------------------------------------------------------------------- /docs/_static/css/readthedocs.css: -------------------------------------------------------------------------------- 1 | .header-logo { 2 | background-image: url("../image/logo.png"); 3 | background-size: 156px 40px; 4 | height: 40px; 5 | width: 156px; 6 | } 7 | 8 | img { 9 | display: block; 10 | margin-left: auto; 11 | margin-right: auto; 12 | } -------------------------------------------------------------------------------- /docs/api/utils/image.rst: -------------------------------------------------------------------------------- 1 | Image visualisation & resizing 2 | ============================== 3 | 4 | .. note:: 5 | 6 | Some function of this module need Napari, it is not a Biom3d dependency and need to be installed manually. 7 | 8 | .. automodule:: biom3d.utils.image 9 | :members: 10 | -------------------------------------------------------------------------------- /docs/api/datasets/datasets.rst: -------------------------------------------------------------------------------- 1 | Datasets 2 | ======== 3 | The datasets module define dataloader or batch loader used for both training and validation. 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: Datasets 8 | 9 | batchgen.rst 10 | patch_fast.rst 11 | torchio.rst 12 | -------------------------------------------------------------------------------- /deployment/dockerfiles/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | submodule=$1 3 | shift 4 | if [ "$TESTED" = 0 ]; then 5 | echo "[WARNING] This image hasn't been totally tested. If any problem is encountered, please open an issue at https://github.com/GuillaumeMougeot/biom3d" 6 | fi 7 | 8 | exec python3.11 -m biom3d."$submodule" "$@" 9 | -------------------------------------------------------------------------------- /docs/api/utils/sampling.rst: -------------------------------------------------------------------------------- 1 | Sampling & Data augmentation 2 | ============================ 3 | 4 | .. note:: 5 | 6 | This module is a work in progress and only implement sampling methods. We want to generalize some of the dataloaders code. 7 | 8 | .. automodule:: biom3d.utils.data_augmentation 9 | :members: 10 | -------------------------------------------------------------------------------- /docs/api/callbacks.rst: -------------------------------------------------------------------------------- 1 | Callbacks 2 | ========= 3 | 4 | The Callbacks are routine called periodically during the training. Simply inherit from the biom3d.callback.Callback class and override one on its methods to create a new callback. Then add it to the biom3d.builder.Builder.build_callback method. 5 | 6 | .. automodule:: biom3d.callbacks 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/api/models/encoder_eff3d.rst: -------------------------------------------------------------------------------- 1 | EfficentNet3D Encoder 2 | ===================== 3 | 4 | This encoder is currently used by :class:`EffUNet`. 5 | 6 | 7 | .. automodule:: biom3d.models.encoder_efficientnet3d 8 | :members: 9 | 10 | .. autofunction:: biom3d.models.encoder_efficientnet3d.GlobalParams 11 | 12 | .. autofunction:: biom3d.models.encoder_efficientnet3d.BlockArgs -------------------------------------------------------------------------------- /docs/api/predictors.rst: -------------------------------------------------------------------------------- 1 | Predictors 2 | ========== 3 | 4 | The Predictors are Python functions that take as input a image (a filename for V1 or a preprocessed image for V2) and a model and returns a post-processed image. 5 | 6 | The post-processing is optional for V2. Use `return_logit` option to return the raw model output. 7 | 8 | .. automodule:: biom3d.predictors 9 | :members: 10 | -------------------------------------------------------------------------------- /docs/api/datasets/batchgen.rst: -------------------------------------------------------------------------------- 1 | Semseg batchgen 2 | =============== 3 | 4 | This is a transcription of nnUnet's batch generator that has the same API as a dataloader. 5 | 6 | .. note:: 7 | 8 | This code use the library batchgenerators that is not a part of Biom3d's dependencies. You'll have to install it separatly. 9 | 10 | .. automodule:: biom3d.datasets.semseg_batchgen 11 | :members: -------------------------------------------------------------------------------- /docs/api/auto_config.rst: -------------------------------------------------------------------------------- 1 | Auto-configuration 2 | ================== 3 | 4 | The auto-configuration is used after the pre-processing to display in the terminal the training hyper-parameters, such as the batch size, the patch size, the augmentation patch size and the number of pooling in the U-Net model. The main function here is `biom3d.auto_config.auto_config` 5 | 6 | .. automodule:: biom3d.auto_config 7 | :members: -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # AUTHORS 2 | Developpers 3 | ------------ 4 | - Guillaume Mougeot¹²³ 5 | - Sami Safarbati¹² 6 | - Clément Colmerauer¹ 7 | 8 | Institutions / Organizations 9 | ---------------------------- 10 | ¹ Institut de Génétique, Reproduction et Développement (iGReD), Université Clermont Auvergne (UCA), CNRS, Clermont-Ferrand, France 11 | ² Institut Pascal, Université Clermont Auvergne (UCA), CNRS, Aubière, France 12 | ³ Oxford Brookes University, UK 13 | -------------------------------------------------------------------------------- /src/biom3d/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .decorators import * # Imported before deprecated 2 | from .time import * 3 | from .fold import * 4 | from .neural_network import * 5 | from .os import * 6 | # Those three must be imported in this order 7 | from .data_handler import * 8 | from .encoding import * 9 | # -------------- 10 | from .filtering import * 11 | from .image import * 12 | from .config import * 13 | from .data_augmentation import * 14 | from .eval_metrics import * 15 | from .deprecated import * -------------------------------------------------------------------------------- /docs/api/models/models.rst: -------------------------------------------------------------------------------- 1 | Models 2 | ====== 3 | 4 | The models module define the different models used by Biom3d, currently we have 2 encoders, 1 decoder and 2 encoders-decoders. 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Decoders 9 | 10 | decoder_vgg.rst 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | :caption: Encoders 15 | 16 | encoder_eff3d.rst 17 | encoder_vgg.rst 18 | 19 | .. toctree:: 20 | :maxdepth: 2 21 | :caption: Unet models 22 | 23 | eff3d.rst 24 | vgg_deep.rst -------------------------------------------------------------------------------- /docs/api/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | Here is Biom3d python API for most importants modules. 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: API 8 | 9 | auto_config.rst 10 | builder.rst 11 | callbacks.rst 12 | datasets/datasets.rst 13 | eval.rst 14 | metrics.rst 15 | models/models.rst 16 | omero.rst 17 | predictions.rst 18 | predictors.rst 19 | preprocess.rst 20 | preprocess_train.rst 21 | register.rst 22 | train.rst 23 | trainers.rst 24 | utils/utils.rst 25 | -------------------------------------------------------------------------------- /.github/workflows/config_docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "configs": [ 3 | { 4 | "architecture": "x86_64", 5 | "torch_version": "2.7.1", 6 | "base_image": "ubuntu:22.04", 7 | "python_version": "3.11", 8 | "omero_version": "5.21.0", 9 | "target": "cpu", 10 | "tested":1 11 | }, 12 | { 13 | "architecture": "x86_64", 14 | "torch_version": "2.7.1", 15 | "cuda_version": "12.8", 16 | "cudnn_version": "9", 17 | "base_image": "pytorch/pytorch:2.7.1-cuda12.8-cudnn9-runtime", 18 | "python_version": "3.11", 19 | "omero_version": "5.21.0", 20 | "tested":1 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Build documentation in the docs/ directory with Sphinx 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | build: 12 | os: "ubuntu-22.04" 13 | tools: 14 | python: "3.11" 15 | 16 | # Optionally build your docs in additional formats such as PDF 17 | # formats: 18 | # - pdf 19 | 20 | # specify dependencies 21 | python: 22 | install: 23 | # - requirements: docs/requirements.txt 24 | - method: pip 25 | path: . 26 | extra_requirements: 27 | - docs 28 | -------------------------------------------------------------------------------- /docs/api/omero.rst: -------------------------------------------------------------------------------- 1 | OMERO 2 | ===== 3 | 4 | There are several module for OMERO that will be documented here. 5 | 6 | OMERO Downloader 7 | ~~~~~~~~~~~~~~~~ 8 | 9 | .. automodule:: biom3d.omero_downloader 10 | :members: 11 | 12 | OMERO Uploader 13 | ~~~~~~~~~~~~~~~~ 14 | 15 | .. automodule:: biom3d.omero_uploader 16 | :members: 17 | 18 | OMERO Prediction 19 | ~~~~~~~~~~~~~~~~ 20 | 21 | .. note:: 22 | 23 | This module can be replaced by `preprocess_train` with action `pred`. 24 | 25 | .. automodule:: biom3d.omero_pred 26 | :members: 27 | 28 | OMERO Preprocess, Train & Pred 29 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 30 | 31 | .. automodule:: biom3d.omero_preprocess_train 32 | :members: -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = ../build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /deployment/exe/windows/Biom3d.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Quit at first error 3 | setlocal enabledelayedexpansion 4 | 5 | REM Charging env from env.bat 6 | call bin\env.bat 7 | 8 | REM Check if it is first launch 9 | if "%FIRST_LAUNCH%"=="1" ( 10 | echo First launch detected, initializng the virtual environment 11 | call "%~dp0bin\Scripts\conda-unpack.exe" 12 | echo Virtual environment initialized 13 | echo Checking for installed version of CUDA and installing appropriate PyTorch 14 | "%~dp0bin\python.exe" "%~dp0bin\auto_update.py" 15 | echo Done 16 | ( 17 | echo @echo off 18 | echo set FIRST_LAUNCH=0 19 | )>%~dp0bin\env.bat 20 | 21 | ) 22 | echo Starting Biom3d... 23 | REM Launch Biom3d GUI 24 | "%~dp0bin\python.exe" -m biom3d.gui 25 | -------------------------------------------------------------------------------- /deployment/exe/macos/Biom3d.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Quit immediately on error 4 | set -e 5 | 6 | # Load environment variables from env.sh 7 | source ./bin/env.sh 8 | 9 | # Check if it is the first launch 10 | if [ "$FIRST_LAUNCH" = "1" ]; then 11 | echo "First launch detected, initializing the virtual environment" 12 | 13 | # Run conda-unpack if it's available 14 | if [ -x "./bin/bin/conda-unpack" ]; then 15 | ./bin/bin/conda-unpack 16 | else 17 | echo "conda-unpack not found!" >&2 18 | exit 1 19 | fi 20 | 21 | echo "Virtual environment initialized" 22 | echo "export FIRST_LAUNCH=0" > ./bin/env.sh 23 | 24 | echo "Starting Biom3d..." 25 | # Launch Biom3d GUI 26 | ./bin/bin/python3.11 -c ./bin/bin/python3.11 -m biom3d.gui 27 | -------------------------------------------------------------------------------- /deployment/exe/macos/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | CFBundleName 7 | Biom3d 8 | 9 | CFBundleDisplayName 10 | Biom3d 11 | 12 | CFBundleExecutable 13 | Biom3d.sh 14 | 15 | CFBundleIdentifier 16 | com.github.GuillaumeMougeot.biom3d 17 | 18 | CFBundleVersion 19 | 0.0.30 20 | 21 | CFBundlePackageType 22 | APPL 23 | 24 | CFBundleIconFile 25 | Biom3d 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/api/metrics.rst: -------------------------------------------------------------------------------- 1 | Metrics 2 | ======= 3 | 4 | The metrics are used to both train the model, they will be called 'loss', and monitor the training process. All metrics must inherit from the `biom3d.metrics.Metric` class. Once defined a novel metric can be either used as a loss function and integrated in the config file as follows: 5 | 6 | >>> TRAIN_LOSS = Dict( 7 | >>> fct="DiceBCE", 8 | >>> kwargs = Dict(name="train_loss", use_softmax=True) 9 | >>> ) 10 | 11 | or as a metric with the following: 12 | 13 | >>> VAL_METRICS = Dict( 14 | >>> val_iou=Dict( 15 | >>> fct="IoU", 16 | >>> kwargs = Dict(name="val_iou", use_softmax=USE_SOFTMAX)), 17 | >>> val_dice=Dict( 18 | >>> fct="Dice", 19 | >>> kwargs=Dict(name="val_dice", use_softmax=USE_SOFTMAX)), 20 | >>> ) 21 | 22 | .. automodule:: biom3d.metrics 23 | :members: -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=../build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd -------------------------------------------------------------------------------- /docs/api/utils/utils.rst: -------------------------------------------------------------------------------- 1 | Utils 2 | ===== 3 | 4 | The utils module define functions (or class) that can be used in several other modules, needed by the core of biom3d or not numerous enough to constitute a new module. 5 | 6 | .. note:: 7 | 8 | Altough we present submoduls, you don't need to specify them when importing : 9 | 10 | .. code-block:: python 11 | 12 | from biom3d.utils.data_handler_abstract import DataHandler 13 | # Is the same thing as 14 | from biom3d.utils import DataHandler 15 | 16 | Only some functions/classes cannot be imported directly with utils. 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | :caption: Utils 21 | 22 | datahandler.rst 23 | config.rst 24 | sampling.rst 25 | image.rst 26 | filtering.rst 27 | encoding.rst 28 | test.rst 29 | decorator.rst 30 | fold.rst 31 | network.rst 32 | os.rst 33 | time.rst -------------------------------------------------------------------------------- /bash/run_eval.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #SBATCH -o ./slurm/%j-train.out # STDOUT 3 | 4 | # python -m biom3d.eval\ 5 | # --dir_pred data/msd/Task01_BrainTumour/preds/20230502-143157-unet_default\ 6 | # --dir_lab data/msd/Task01_BrainTumour/labelsTr_test\ 7 | # --num_classes 3 8 | 9 | # python -m biom3d.eval\ 10 | # --dir_pred data/msd/Task07_Pancreas/preds/20230523-105736-unet_default\ 11 | # --dir_lab data/msd/Task07_Pancreas/labelsTr_test\ 12 | # --num_classes 2 13 | 14 | python -m biom3d.eval\ 15 | --dir_pred data/msd/Task06_Lung/preds/swinunetr\ 16 | --dir_lab data/msd/Task06_Lung/labelsTr_test\ 17 | --num_classes 1 18 | 19 | # python -m biom3d.eval\ 20 | # --dir_pred data/msd/Task07_Pancreas/preds/20230523-105736-unet_default\ 21 | # --dir_lab data/msd/Task07_Pancreas/labelsTr_test\ 22 | # --num_classes 2 23 | 24 | # python -m biom3d.eval\ 25 | # --dir_pred data/nucleus/official/test/preds/20230908-202124-nucleus_official_fold4\ 26 | # --dir_lab data/nucleus/official/test/msk\ 27 | # --num_classes 1 28 | 29 | # python -m biom3d.eval\ 30 | # --dir_pred data/mito/test/pred/20230203-091249-unet_mito\ 31 | # --dir_lab data/mito/test/msk\ 32 | # --num_classes 1 -------------------------------------------------------------------------------- /deployment/dockerfiles/examples/biom3d_cpu_torch2_7.dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | #Install python and java dependencies 6 | RUN apt-get update && apt-get install -y \ 7 | software-properties-common \ 8 | curl \ 9 | openjdk-11-jre-headless \ 10 | && add-apt-repository ppa:deadsnakes/ppa \ 11 | && apt-get update && apt-get install -y \ 12 | python3.11 python3.11-distutils python3.11-venv python3.11-tk python3-pip \ 13 | && apt-get clean && rm -rf /var/lib/apt/lists/* \ 14 | && mkdir -p /workspace && chmod 777 /workspace 15 | 16 | #Install OMERO 17 | RUN python3.11 -m pip install --upgrade pip setuptools wheel\ 18 | && python3.11 -m pip install \ 19 | https://github.com/glencoesoftware/zeroc-ice-py-linux-x86_64/releases/download/20240202/zeroc_ice-3.6.5-cp311-cp311-manylinux_2_28_x86_64.whl \ 20 | omero-py==5.21.0\ 21 | && ${PYTHON_BIN} -m pip install --no-cache-dir --no-deps ezomero 22 | 23 | #Create entrypoint 24 | COPY entrypoint.sh /biom3d 25 | RUN chmod +x /biom3d\ 26 | # 27 | # Install torch cpu and biom3d 28 | && python3.11 -m pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu \ 29 | && python3.11 -m pip install --no-cache-dir biom3d 30 | 31 | # Volumes must be attached here 32 | WORKDIR /workspace 33 | ENTRYPOINT ["biom3d"] 34 | -------------------------------------------------------------------------------- /deployment/dockerfiles/examples/biom3d_cuda11_8cudnn8torch2_3.dockerfile: -------------------------------------------------------------------------------- 1 | FROM pytorch/pytorch:2.3.1-cuda11.8-cudnn8-runtime 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | #Install python and java dependencies 6 | RUN apt-get update && apt-get install -y \ 7 | software-properties-common \ 8 | curl \ 9 | openjdk-11-jre-headless \ 10 | && add-apt-repository ppa:deadsnakes/ppa \ 11 | && apt-get update && apt-get install -y \ 12 | python3.11 python3.11-distutils python3.11-venv python3-pip \ 13 | && apt-get clean && rm -rf /var/lib/apt/lists/* \ 14 | && mkdir -p /workspace && chmod 777 /workspace 15 | 16 | #Install OMERO 17 | RUN pip install numpy==1.26.4 --force-reinstall && \ 18 | python3.11 -m pip install --upgrade pip setuptools wheel\ 19 | && python3.11 -m pip install \ 20 | https://github.com/glencoesoftware/zeroc-ice-py-linux-x86_64/releases/download/20240202/zeroc_ice-3.6.5-cp311-cp311-manylinux_2_28_x86_64.whl \ 21 | omero-py==5.21.0\ 22 | && ${PYTHON_BIN} -m pip install --no-cache-dir --no-deps ezomero 23 | 24 | #Create entrypoint 25 | COPY entrypoint.sh /biom3d 26 | RUN chmod +x /biom3d\ 27 | # 28 | # Install torch cpu and biom3d 29 | && python3.11 -m pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu \ 30 | && python3.11 -m pip install --no-cache-dir biom3d\ 31 | && ln -s /opt/conda/lib/libnvrtc.so.11.2 /opt/conda/lib/libnvrtc.so 32 | 33 | # Volumes must be attached here 34 | WORKDIR /workspace 35 | ENTRYPOINT ["biom3d"] 36 | -------------------------------------------------------------------------------- /deployment/exe/auto_update.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import re 3 | import sys 4 | 5 | def get_cuda_version_from_nvcc(): 6 | output = subprocess.check_output(["nvcc", "--version"], stderr=subprocess.STDOUT, text=True) 7 | # Exemple de sortie : "Cuda compilation tools, release 11.8, V11.8.89" 8 | match = re.search(r"release (\d+)\.(\d+)", output) 9 | if match: 10 | major = match.group(1) 11 | return int(major) 12 | 13 | def get_cuda_version_from_nvidia_smi(): 14 | try: 15 | output = subprocess.check_output(["nvidia-smi"], stderr=subprocess.STDOUT, text=True) 16 | # Cherche une ligne comme : "CUDA Version: 12.2" 17 | match = re.search(r"CUDA Version: (\d+)\.(\d+)", output) 18 | if match: 19 | major = match.group(1) 20 | return int(major) 21 | except (subprocess.CalledProcessError, FileNotFoundError): 22 | return None 23 | 24 | def detect_cuda_major_version(): 25 | try : 26 | version = get_cuda_version_from_nvcc() 27 | except : 28 | try : 29 | version = get_cuda_version_from_nvidia_smi() 30 | except : 31 | version = None 32 | 33 | if version is not None : 34 | # Install 11.8 or 12.8 (we use the x.8 retrocompatibility) 35 | subprocess.check_call([sys.executable, "-m", "pip", "install", "torch","--index-url","https://download.pytorch.org/whl/cu"+str(version)+"8","--force-reinstall","--no-warn-script-location","--no-deps"]) #Remove no-deps once typing-extnsions doesn't bug 36 | 37 | if __name__ == "__main__": 38 | detect_cuda_major_version() -------------------------------------------------------------------------------- /.github/workflows/config_docker_full.json: -------------------------------------------------------------------------------- 1 | { 2 | "configs": [ 3 | { 4 | "architecture": "x86_64", 5 | "torch_version": "2.7.1", 6 | "base_image": "ubuntu:22.04", 7 | "python_version": "3.11", 8 | "omero_version": "5.21.0", 9 | "target": "cpu", 10 | "tested":1 11 | }, 12 | { 13 | "architecture": "x86_64", 14 | "torch_version": "2.3.1", 15 | "cuda_version": "11.8", 16 | "cudnn_version": "8", 17 | "base_image": "pytorch/pytorch:2.3.1-cuda11.8-cudnn8-runtime", 18 | "python_version": "3.11", 19 | "omero_version": "5.21.0", 20 | "tested":1 21 | }, 22 | { 23 | "architecture": "x86_64", 24 | "torch_version": "2.7.1", 25 | "cuda_version": "11.8", 26 | "cudnn_version": "9", 27 | "base_image": "pytorch/pytorch:2.7.1-cuda11.8-cudnn9-runtime", 28 | "python_version": "3.11", 29 | "omero_version": "5.21.0", 30 | "tested":1 31 | }, 32 | { 33 | "architecture": "x86_64", 34 | "torch_version": "2.7.1", 35 | "cuda_version": "12.8", 36 | "cudnn_version": "9", 37 | "base_image": "pytorch/pytorch:2.7.1-cuda12.8-cudnn9-runtime", 38 | "python_version": "3.11", 39 | "omero_version": "5.21.0", 40 | "tested":1 41 | }, 42 | { 43 | "architecture": "x86_64", 44 | "torch_version": "2.3.1", 45 | "cuda_version": "12.1", 46 | "cudnn_version": "8", 47 | "base_image": "pytorch/pytorch:2.3.1-cuda12.1-cudnn8-runtime", 48 | "python_version": "3.11", 49 | "omero_version": "5.21.0", 50 | "tested":1 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Biom3d 2 | =================================== 3 | 4 | .. image:: _static/image/nucleus_segmentation.png 5 | :align: right 6 | :height: 200 7 | 8 | Congrats! You've just found Biom3d, an easy-to-use tool for volumetric image semantic segmentation. 9 | 10 | This tool is addressed to three different profiles: 11 | 12 | - Non Programmers, who could be interested to use the Graphical User Interface (GUI) only. After going through the installation [link], start with [Quick Run/GUI]. 13 | - Python Programmers, who could be happy with some minimal customization, such as adapting Biom3d to a novel image format or choosing another training loss. After going through the installation [link], start with [CLI]. 14 | - Deep Learning Programmers, who are not scared of digging in some more advanced features of Biom3d, such as customizing the deep learning model or the metrics. After going through the installation [link], start with the basic here [CLI] and then go directly to the tutorials [link]. 15 | 16 | .. toctree:: 17 | :maxdepth: 2 18 | :caption: Installation 19 | 20 | installation.md 21 | 22 | .. toctree:: 23 | :maxdepth: 2 24 | :caption: Tutorials 25 | 26 | tuto/gui.md 27 | tuto/cli.md 28 | tuto/docker.md 29 | tuto/config.md 30 | tuto/server.md 31 | tuto/dataset.md 32 | 33 | .. toctree:: 34 | :maxdepth: 0 35 | :caption: How it works ? 36 | 37 | how_it_works/pre_processing 38 | how_it_works/post_processing 39 | 40 | .. toctree:: 41 | :maxdepth: 2 42 | :caption: FAQ 43 | 44 | faq.md 45 | 46 | .. toctree:: 47 | :maxdepth: 2 48 | :caption: Developper 49 | 50 | api/api.rst 51 | dep/dep.rst 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /deployment/exe/windows/pack.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set ENV_NAME=installer 3 | for /f "delims=" %%i in ('where conda.exe') do ( 4 | set "CONDA_PATH=%%i" 5 | ) 6 | set DIR=Biom3d 7 | set ARCH=x86_64 8 | set ARCHIVE_NAME=%DIR%_Windows_%ARCH% 9 | if not [%1]==[] set ARCH=%1 10 | 11 | :: Checking if venv exist 12 | call "%CONDA_PATH%" env list | findstr /i "%ENV_NAME%" >nul 13 | if %errorlevel%==0 ( 14 | echo Environment "%ENV_NAME%" already exists. 15 | ) else ( 16 | echo Create environment "%ENV_NAME%"... 17 | call "%CONDA_PATH%" create -y -n %ENV_NAME% python=3.11 tk -y 18 | ) 19 | 20 | call conda activate %ENV_NAME% 21 | call conda install conda-pack -y 22 | :: Avoid pip/conda conflict 23 | call conda install pip=23.1 -y 24 | :: Omero dependencies 25 | call conda install zeroc-ice=3.6.5 -y 26 | call pip install pillow future portalocker pywin32 requests "urllib3<2" 27 | :: Forced to do --no-deps because it would try to reinstall zeroc-ice by compiling it 28 | call pip install omero-py --no-deps 29 | call pip install ezomero --no-deps 30 | call pip install ../../../ 31 | call pip cache purge 32 | 33 | :: Pack 34 | if exist %DIR% ( 35 | echo Folder %DIR% already exist, deleting... 36 | rmdir /s /q %DIR% 37 | ) 38 | mkdir %DIR% 39 | call conda pack --format=no-archive -o %DIR%\bin 40 | call conda deactivate 41 | ( 42 | echo @echo off 43 | echo set FIRST_LAUNCH=1 44 | )>%DIR%\bin\env.bat 45 | copy Biom3d.bat %DIR%/Biom3d.bat 46 | copy "%~dp0..\auto_update.py" %DIR%\bin\auto_update.py 47 | copy logo.ico %DIR%\Biom3d.ico 48 | :: Doesn't work due to antivirus lock 49 | :: powershell -Command "Compress-Archive -Path '%DIR%' -DestinationPath '%DIR%.zip' -Force" 50 | :: Need 7z 51 | 7z a -tzip %ARCHIVE_NAME%.zip %DIR%\ -------------------------------------------------------------------------------- /src/biom3d/utils/decorators.py: -------------------------------------------------------------------------------- 1 | """This submodule provide decorators.""" 2 | 3 | from typing import Callable 4 | import warnings 5 | import functools 6 | 7 | def deprecated(reason:str="This function is deprecated.")->Callable: 8 | """ 9 | Mark functions as deprecated. 10 | 11 | When the decorated function is called, a `DeprecationWarning` is issued with the given reason. 12 | This helps inform developers and users that the function is outdated and may be removed in future versions. 13 | 14 | Parameters 15 | ---------- 16 | reason: str, default="This function is deprecated."nal 17 | Explanation or message indicating why the function is deprecated, or what to use instead. 18 | 19 | Returns 20 | ------- 21 | callable 22 | A decorator that wraps the given function and issues a deprecation warning when called. 23 | 24 | Examples 25 | -------- 26 | >>> @deprecated("Use 'new_function' instead.") 27 | ... def old_function(): 28 | ... pass 29 | 30 | >>> old_function() 31 | ... # DeprecationWarning: Call to deprecated function old_function(). Use 'new_function' instead. 32 | 33 | Notes 34 | ----- 35 | - The warning is raised using `warnings.warn` with category `DeprecationWarning`. 36 | - Stack level is set to 2 to show the warning at the caller's level. 37 | """ 38 | def decorator(func): 39 | @functools.wraps(func) 40 | def wrapped(*args, **kwargs): 41 | warnings.warn( 42 | f"Call to deprecated function {func.__name__}(). {reason}", 43 | DeprecationWarning, 44 | stacklevel=2 45 | ) 46 | return func(*args, **kwargs) 47 | return wrapped 48 | return decorator 49 | -------------------------------------------------------------------------------- /src/biom3d/utils/neural_network.py: -------------------------------------------------------------------------------- 1 | """This submodule provides function for neural network.""" 2 | import numpy as np 3 | 4 | def convert_num_pools(num_pools:list[int],roll_strides:bool=True)->list[list[int]]: 5 | """ 6 | Generate adaptive pooling stride configurations based on the number of pools per axis. 7 | 8 | This utility transforms a list indicating the number of pooling operations along each axis 9 | into a stride pattern usable for downsampling layers in convolutional architectures. 10 | 11 | Parameters 12 | ---------- 13 | num_pools: list of int 14 | List indicating how many pooling steps to apply per axis. 15 | For example, [3, 5, 5] means: 16 | - axis 0 will be pooled 3 times, 17 | - axis 1 and 2 will be pooled 5 times each. 18 | roll_strides: bool, default=True 19 | If True, zero-padding is symmetrically centered (rolled), otherwise zeros are left-aligned. 20 | 21 | Returns 22 | ------- 23 | strides : list of list of int 24 | A 2D list of shape (max(num_pools), len(num_pools)) representing stride values. 25 | Each inner list corresponds to the stride per axis at a given depth. 26 | Strides are either 1 (no pooling) or 2 (pooling). 27 | 28 | Examples 29 | -------- 30 | >>> convert_num_pools([3, 5, 5]) 31 | [[1, 2, 2], 32 | [2, 2, 2], 33 | [2, 2, 2], 34 | [2, 2, 2], 35 | [1, 2, 2]] 36 | """ 37 | max_pool = max(num_pools) 38 | strides = [] 39 | for i in range(len(num_pools)): 40 | st = np.ones(max_pool) 41 | num_zeros = max_pool-num_pools[i] 42 | for j in range(num_zeros): 43 | st[j]=0 44 | if roll_strides : st=np.roll(st,-num_zeros//2) 45 | strides += [st] 46 | strides = np.array(strides).astype(int).T+1 47 | strides = strides.tolist() 48 | return strides -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0", 3 | "setuptools-scm>=8.0"] 4 | build-backend = "setuptools.build_meta" 5 | 6 | [tool.setuptools_scm] 7 | 8 | [project] 9 | name = "biom3d" 10 | dynamic = ["version"] 11 | # version = "0.0.40" 12 | authors = [ 13 | {name="Guillaume Mougeot", email="guillaume.mougeot@laposte.net"}, 14 | ] 15 | description = "Biom3d. Framework for easy-to-use biomedical image segmentation." 16 | readme = "README.md" 17 | requires-python = ">=3.9" 18 | classifiers = [ 19 | "Programming Language :: Python :: 3", 20 | "Operating System :: OS Independent", 21 | ] 22 | dependencies = [ 23 | "torch>=2.3", 24 | "tqdm>=4.62.3", 25 | "scikit-image>=0.14", 26 | "scipy>=1.9.1", 27 | "numpy>=1.21.2", 28 | "SimpleITK>=2.1.1", 29 | "pandas>=1.4.0", 30 | "matplotlib>=3.5.3", 31 | "tensorboard>=2.8.0", 32 | "PyYAML>=5.4", 33 | "torchio>=0.20.6", 34 | "protobuf>=3.19.3", 35 | "appdirs>=1.4.4", 36 | "numba>=0.56.4", 37 | "paramiko", 38 | "netcat", 39 | "tifffile", 40 | "h5py", 41 | "monai", 42 | "einops", 43 | ] 44 | keywords=['deep learning', 'image segmentation', 'medical image analysis', 45 | 'medical image segmentation', 'biological image segmentation', 'bio-imaging'] 46 | 47 | [project.optional-dependencies] 48 | docs = [ 49 | 'sphinx>=4.1.2', 50 | 'sphinxcontrib-apidoc', 51 | 'sphinx_rtd_theme>=0.3.1', 52 | 'myst_nb', 53 | 'furo>=2022.06.21', 54 | 'docutils>=0.17.1', 55 | 'sphinx-copybutton', 56 | 'sphinx_design', 57 | 'sphinx_automodapi', 58 | 'pytorch_sphinx_theme', 59 | ] 60 | gui = [ 61 | "omero-py", 62 | "ezomero", 63 | ] 64 | 65 | [project.urls] 66 | "Homepage" = "https://github.com/GuillaumeMougeot/biom3d" 67 | "Bug Tracker" = "https://github.com/GuillaumeMougeot/biom3d/issues" 68 | 69 | [project.scripts] 70 | biom3d = "biom3d.gui:main" -------------------------------------------------------------------------------- /deployment/dockerfiles/examples/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PYTHON_VERSION="3.11" 4 | TORCH_VERSION="2.3.1" 5 | OMERO_VERSION="5.21.0" 6 | BIOM3D_VERSION="0.0.30" 7 | ARCHITECTURE=x86_64 8 | DOCKERFILE=../template.dockerfile 9 | 10 | 11 | # CPU 12 | docker build \ 13 | --build-arg BASE_IMAGE=ubuntu:22.04 \ 14 | --build-arg TARGET=cpu \ 15 | --build-arg PYTHON_VERSION=$PYTHON_VERSION \ 16 | --build-arg OMERO_VERSION=$OMERO_VERSION \ 17 | -t biom3d:${BIOM3D_VERSION}-$ARCHITECTURE-torch${TORCH_VERSION}-cpu \ 18 | -f $DOCKERFILE . 19 | 20 | # GPU 21 | docker build \ 22 | --build-arg BASE_IMAGE=pytorch/pytorch:2.3.1-cuda11.8-cudnn8-runtime \ 23 | --build-arg TARGET=gpu \ 24 | --build-arg PYTHON_VERSION=$PYTHON_VERSION \ 25 | --build-arg OMERO_VERSION=$OMERO_VERSION \ 26 | -t biom3d:${BIOM3D_VERSION}-$ARCHITECTURE-torch2.3.1-cuda11.8-cudnn8 \ 27 | -f $DOCKERFILE . 28 | 29 | docker build \ 30 | --build-arg BASE_IMAGE=pytorch/pytorch:2.7.1-cuda11.8-cudnn9-runtime \ 31 | --build-arg TARGET=gpu \ 32 | --build-arg PYTHON_VERSION=$PYTHON_VERSION \ 33 | --build-arg OMERO_VERSION=$OMERO_VERSION \ 34 | -t biom3d:${BIOM3D_VERSION}-$ARCHITECTURE-torch2.7.1-cuda11.8-cudnn9 \ 35 | -f $DOCKERFILE . 36 | 37 | docker build \ 38 | --build-arg BASE_IMAGE=pytorch/pytorch:2.7.1-cuda12.8-cudnn9-runtime \ 39 | --build-arg TARGET=gpu \ 40 | --build-arg PYTHON_VERSION=$PYTHON_VERSION \ 41 | --build-arg OMERO_VERSION=$OMERO_VERSION \ 42 | --build-arg TESTED=0 \ 43 | -t biom3d:${BIOM3D_VERSION}-$ARCHITECTURE-torch2.7.1-cuda12.8-cudnn9 \ 44 | -f $DOCKERFILE . 45 | 46 | docker build \ 47 | --build-arg BASE_IMAGE=pytorch/pytorch:2.3.1-cuda12.1-cudnn8-runtime \ 48 | --build-arg TARGET=gpu \ 49 | --build-arg PYTHON_VERSION=$PYTHON_VERSION \ 50 | --build-arg OMERO_VERSION=$OMERO_VERSION \ 51 | --build-arg TESTED=0 \ 52 | -t biom3d:${BIOM3D_VERSION}-$ARCHITECTURE-torch2.3.1-cuda12.1-cudnn8 \ 53 | -f $DOCKERFILE . 54 | 55 | -------------------------------------------------------------------------------- /deployment/exe/macos/pack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ENV_NAME=installer 6 | DIR="Biom3d" 7 | ARCH=$(uname -m) 8 | ARCHIVE_NAME="${DIR}_MacOS_${ARCH}.zip" 9 | DIR="${DIR}.app" 10 | # Activate environment 11 | source "$(conda info --base)/etc/profile.d/conda.sh" 12 | 13 | # Check if environment exists 14 | if conda env list | grep -i "^${ENV_NAME}[[:space:]]" > /dev/null; then 15 | echo "Environment '${ENV_NAME}' already exists." 16 | else 17 | echo "Create environment '${ENV_NAME}'..." 18 | conda create -y -n "$ENV_NAME" python=3.11 tk 19 | fi 20 | 21 | conda activate "$ENV_NAME" 22 | conda install -y conda-pack 23 | 24 | # Avoid pip/conda conflicts 25 | conda install -y pip=23.1 26 | 27 | # Omero dependencies 28 | conda install -y zeroc-ice=3.6.5 29 | 30 | pip install pillow future portalocker requests "urllib3<2" 31 | # pywin32 n'est pas disponible ni utile sous macOS 32 | 33 | # pip install omero-py sans dépendances car zeroc-ice est déjà installé 34 | pip install --no-deps omero-py 35 | pip install --no-deps ezomero 36 | pip install ../../../ 37 | pip cache purge 38 | 39 | # Pack 40 | if [ -d "$DIR" ]; then 41 | echo "Folder $DIR already exists, deleting..." 42 | rm -rf "$DIR" 43 | fi 44 | mkdir -p "$DIR" 45 | mkdir -p "$DIR/Contents" 46 | DIR="$DIR/Contents" 47 | mkdir -p "$DIR/MacOS" 48 | mkdir -p "$DIR/Resources" 49 | 50 | # conda pack (change output path) 51 | conda pack --format=no-archive -o "$DIR/MacOS/bin" 52 | echo "export FIRST_LAUNCH=1" > "$DIR/MacOS/bin/env.sh" 53 | chmod +x "$DIR/MacOS/bin/env.sh" 54 | 55 | conda deactivate 56 | 57 | # Copier fichiers 58 | cp Biom3d.sh "$DIR/MacOS/Biom3d.sh" 59 | cp logo.icns "$DIR/Resources/Biom3d.icns" 60 | cp Info.plist "$DIR/Info.plist" 61 | chmod +x "$DIR/MacOS/Biom3d.sh" 62 | 63 | # Zip (7z if installed, else default zip) 64 | if command -v 7z >/dev/null 2>&1; then 65 | 7z a -tzip "$ARCHIVE_NAME" "$DIR" 66 | else 67 | # zip natif macOS 68 | zip -r "$ARCHIVE_NAME" "$DIR" 69 | fi 70 | -------------------------------------------------------------------------------- /deployment/dockerfiles/examples/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set PYTHON_VERSION="3.11" 4 | set TORCH_VERSION="2.3.1" 5 | set OMERO_VERSION="5.21.0" 6 | set BIOM3D_VERSION="0.0.30" 7 | set ARCHITECTURE=x86_64 8 | set DOCKERFILE=..\template.dockerfile 9 | 10 | 11 | REM CPU 12 | docker build ^ 13 | --build-arg BASE_IMAGE=ubuntu:22.04 ^ 14 | --build-arg TARGET=cpu ^ 15 | --build-arg PYTHON_VERSION=%PYTHON_VERSION% ^ 16 | --build-arg OMERO_VERSION=%OMERO_VERSION% ^ 17 | -t biom3d:%BIOM3D_VERSION%-%ARCHITECTURE%-torch%TORCH_VERSION%-cpu ^ 18 | -f %DOCKERFILE% . 19 | 20 | REM GPU 21 | docker build ^ 22 | --build-arg BASE_IMAGE=pytorch/pytorch:2.3.1-cuda11.8-cudnn8-runtime ^ 23 | --build-arg TARGET=gpu ^ 24 | --build-arg PYTHON_VERSION=%PYTHON_VERSION% ^ 25 | --build-arg OMERO_VERSION=%OMERO_VERSION% ^ 26 | -t biom3d:%BIOM3D_VERSION%-%ARCHITECTURE%-torch2.3.1-cuda11.8-cudnn8 ^ 27 | -f %DOCKERFILE% . 28 | 29 | docker build ^ 30 | --build-arg BASE_IMAGE=pytorch/pytorch:2.7.1-cuda11.8-cudnn9-runtime ^ 31 | --build-arg TARGET=gpu ^ 32 | --build-arg PYTHON_VERSION=%PYTHON_VERSION% ^ 33 | --build-arg OMERO_VERSION=%OMERO_VERSION% ^ 34 | -t biom3d:%BIOM3D_VERSION%-%ARCHITECTURE%-torch%TORCH_VERSION%-cuda11.8-cudnn9 ^ 35 | -f %DOCKERFILE% . 36 | 37 | docker build ^ 38 | --build-arg BASE_IMAGE=pytorch/pytorch:2.7.1-cuda12.8-cudnn9-runtime ^ 39 | --build-arg TARGET=gpu ^ 40 | --build-arg PYTHON_VERSION=%PYTHON_VERSION% ^ 41 | --build-arg OMERO_VERSION=%OMERO_VERSION% ^ 42 | --build-arg TESTED=0 ^ 43 | -t biom3d:%BIOM3D_VERSION%-%ARCHITECTURE%-torch%TORCH_VERSION%-cuda12.8-cudnn9 ^ 44 | -f %DOCKERFILE% . 45 | 46 | docker build ^ 47 | --build-arg BASE_IMAGE=pytorch/pytorch:2.3.1-cuda12.1-cudnn8-runtime ^ 48 | --build-arg TARGET=gpu ^ 49 | --build-arg PYTHON_VERSION=%PYTHON_VERSION% ^ 50 | --build-arg OMERO_VERSION=%OMERO_VERSION% ^ 51 | --build-arg TESTED=0 ^ 52 | -t biom3d:%BIOM3D_VERSION%-%ARCHITECTURE%-torch2.3.1-cuda12.1-cudnn8 ^ 53 | -f %DOCKERFILE% . 54 | 55 | -------------------------------------------------------------------------------- /bash/run_preprocess.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #SBATCH -o ./slurm/%j-train.out # STDOUT 3 | 4 | # python -m biom3d.preprocess\ 5 | # --img_dir data/btcv/Training_official/img\ 6 | # --msk_dir data/btcv/Training_official/label\ 7 | # --num_classes 13\ 8 | # --max_dim 224\ 9 | # --desc unet_btcv\ 10 | # --ct_norm\ 11 | # --skip_preprocessing 12 | 13 | # python -m biom3d.preprocess\ 14 | # --img_dir data/msd/Task01_BrainTumour/imagesTr_train\ 15 | # --msk_dir data/msd/Task01_BrainTumour/labelsTr_train\ 16 | # --num_classes 3\ 17 | # --desc unet_brain\ 18 | # --skip_preprocessing 19 | 20 | # python -m biom3d.preprocess\ 21 | # --img_dir data/msd/Task09_Spleen/imagesTr\ 22 | # --msk_dir data/msd/Task09_Spleen/labelsTr\ 23 | # --num_classes 1\ 24 | # --max_dim 160\ 25 | # --desc unet_spleen\ 26 | # --ct_norm 27 | 28 | # python -m biom3d.preprocess\ 29 | # --img_dir data/nucleus/aline_nucleus_48h24hL/img\ 30 | # --msk_dir data/nucleus/aline_nucleus_48h24hL/msk_chromo\ 31 | # --num_classes 1\ 32 | # --desc chromo_48h24hL\ 33 | # --debug 34 | 35 | # python -m biom3d.preprocess\ 36 | # --img_dir data/msd/Task06_Lung/imagesTr_train\ 37 | # --msk_dir data/msd/Task06_Lung/labelsTr_train\ 38 | # --num_classes 1\ 39 | # --desc unet_lung\ 40 | # --ct_norm 41 | 42 | # python -m biom3d.preprocess\ 43 | # --img_dir data/msd/Task05_Prostate/imagesTr\ 44 | # --msk_dir data/msd/Task05_Prostate/labelsTr\ 45 | # --num_classes 2\ 46 | # --max_dim 128\ 47 | # --desc unet_prostate 48 | 49 | # python -m biom3d.preprocess\ 50 | # --img_dir data/nucleus/official/train/img\ 51 | # --msk_dir data/nucleus/official/train/msk\ 52 | # --num_classes 1\ 53 | # --desc nucleus_official\ 54 | # --use_tif 55 | 56 | # python -m biom3d.preprocess\ 57 | # --img_dir data/reims/large/img\ 58 | # --msk_dir data/reims/large/msk\ 59 | # --num_classes 1\ 60 | # --desc reims_large 61 | 62 | python -m biom3d.preprocess\ 63 | --img_dir data/nucleus/official/train_tmp/img\ 64 | --msk_dir data/nucleus/official/train_tmp/msk\ 65 | --num_classes 1\ 66 | --desc nucleus_official -------------------------------------------------------------------------------- /deployment/dockerfiles/template.dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | 3 | ARG BASE_IMAGE=ubuntu:22.04 4 | FROM ${BASE_IMAGE} 5 | 6 | ARG TARGET=cpu 7 | ARG PYTHON_VERSION=3.11 8 | ARG OMERO_VERSION=5.21.0 9 | ARG TESTED=1 10 | 11 | ENV TESTED=${TESTED} 12 | ENV DEBIAN_FRONTEND=noninteractive 13 | ENV PYTHON_BIN=python${PYTHON_VERSION} 14 | 15 | # Install system dependencies 16 | RUN apt-get update && apt-get install -y \ 17 | software-properties-common \ 18 | curl \ 19 | git \ 20 | && add-apt-repository ppa:deadsnakes/ppa \ 21 | && apt-get update && apt-get install -y \ 22 | python${PYTHON_VERSION} \ 23 | python${PYTHON_VERSION}-distutils \ 24 | python${PYTHON_VERSION}-venv \ 25 | python${PYTHON_VERSION}-tk \ 26 | python3-pip \ 27 | && apt-get clean && rm -rf /var/lib/apt/lists/* \ 28 | && mkdir -p /workspace && chmod 777 /workspace \ 29 | && mkdir -p /Biom3d && chmod 777 //Biom3d \ 30 | # 31 | # Upgrade pip & install OMERO 32 | && ${PYTHON_BIN} -m pip install --upgrade pip setuptools wheel && \ 33 | ${PYTHON_BIN} -m pip install \ 34 | https://github.com/glencoesoftware/zeroc-ice-py-linux-x86_64/releases/download/20240202/zeroc_ice-3.6.5-cp311-cp311-manylinux_2_28_x86_64.whl \ 35 | omero-py==${OMERO_VERSION}\ 36 | && ${PYTHON_BIN} -m pip install --no-cache-dir --no-deps ezomero 37 | 38 | # Clone project 39 | COPY . /Biom3d 40 | WORKDIR /Biom3d 41 | 42 | # Copy entrypoint 43 | RUN chmod +x /Biom3d/deployment/dockerfiles/entrypoint.sh \ 44 | # 45 | # Install biom3d 46 | && ${PYTHON_BIN} -m pip install . \ 47 | # 48 | # Conditional: Install torch or fix symlink depending on CPU or GPU 49 | && if [ "$TARGET" = "cpu" ]; then \ 50 | ${PYTHON_BIN} -m pip install torch --index-url https://download.pytorch.org/whl/cpu ; \ 51 | elif [ "$TARGET" = "gpu" ]; then \ 52 | ln -s /opt/conda/lib/libnvrtc.so.11.2 /opt/conda/lib/libnvrtc.so || true ; \ 53 | fi 54 | 55 | WORKDIR /workspace 56 | ENTRYPOINT ["/Biom3d/deployment/dockerfiles/entrypoint.sh"] -------------------------------------------------------------------------------- /bash/run_train.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #SBATCH -o ./slurm/%j-train.out # STDOUT 3 | 4 | # train 5 | # python -m biom3d.train --config configs/20230509-181824-segpatch_lung.py 6 | # python -m biom3d.train --log logs/20230501-153638-unet_default 7 | # python -m biom3d.train --config configs/20230524-181439-unet_brain.py 8 | # python -m biom3d.train --log logs/20230908-112859-nucleus_official_fold2 9 | # python -m biom3d.train --config configs/20230831-114941-nucleus_official_fold2.py 10 | # python -m biom3d.train --log logs/20231103-100735-nucleus_official_fold2 11 | # python -m biom3d.train --config configs/20230831-114941-nucleus_official_fold3.py 12 | # python -m biom3d.train --config configs/20230926-104900-triplet_pancreas_exp20.py 13 | # python -m biom3d.train --config configs/20230517-121730-unet_chromo.py 14 | # python -m biom3d.train --log logs/20230605-181034-unet_chromo_48h24-48hL 15 | # python -m biom3d.train --config configs/20240319-093546-reims_large_full.py 16 | # python -m biom3d.train --config configs/20240219-100035-reims_full.py 17 | # python -m biom3d.train --config configs/20240718-152732-exp21_supervised_baseline_pancreas_small.py 18 | # python -m biom3d.train --config configs/20240718-152732-exp22_supervised_baseline_pancreas_small.py 19 | 20 | # fine-tuning 21 | # python -m biom3d.train\ 22 | # --log logs/20230522-182916-unet_default\ 23 | # --config configs/20230522-182916-config_default.py 24 | 25 | # train and eval 26 | # python -m biom3d.train\ 27 | # --name seg_pred_eval\ 28 | # --config configs/20240217-191923-reims.py\ 29 | # --dir_in data/reims/test_match/img\ 30 | # --dir_out data/reims/test_match/preds\ 31 | # --dir_lab data/reims/test_match/msk 32 | 33 | # python -m biom3d.train\ 34 | # --name seg_pred_eval\ 35 | # --config configs/20240718-152732-exp21_supervised_baseline_pancreas_small.py\ 36 | # --dir_in data/pancreas/imagesTs_small\ 37 | # --dir_out data/pancreas/preds\ 38 | # --dir_lab data/pancreas/labelsTs_small 39 | 40 | python -m biom3d.train\ 41 | --name seg_pred_eval\ 42 | --config configs/20250715-140155-effunet_lung.py\ 43 | --dir_in data/msd/Task06_Lung/imagesTr_test\ 44 | --dir_out data/msd/Task06_Lung/preds\ 45 | --dir_lab data/msd/Task06_Lung/labelsTr_test -------------------------------------------------------------------------------- /src/biom3d/utils/time.py: -------------------------------------------------------------------------------- 1 | """This submodule provide a class to easily manipulate time.""" 2 | from time import time 3 | from typing import Optional 4 | 5 | class Time: 6 | """ 7 | Simple utility class to measure elapsed time across multiple events. 8 | 9 | Useful for timing loops, function calls, or sections of code with optional naming 10 | and usage tracking (via a counter). 11 | 12 | :ivar str name: Name of the timer instance. 13 | :ivar float start_time: Timestamp when the timer was last reset. 14 | :ivar int count: Number of times `.get()` or `__str__()` has been called. 15 | """ 16 | 17 | def __init__(self, name:Optional[str]=None): 18 | """ 19 | Initialize time. 20 | 21 | Parameters 22 | ---------- 23 | name : str, optional 24 | Optional name identifier for the timer (used in __str__ debug output). 25 | """ 26 | self.name=name 27 | self.reset() 28 | 29 | def reset(self)->None: 30 | """ 31 | Reset the timer and the internal call counter. 32 | 33 | This will set the start time to the current time and reset the count to 0. 34 | """ 35 | print("Count has been reset!") 36 | self.start_time = time() 37 | self.count = 0 38 | 39 | def get(self)->float: 40 | """ 41 | Get the elapsed time in seconds since the last reset. 42 | 43 | Increments the internal call counter. 44 | 45 | Returns 46 | ------- 47 | float 48 | Elapsed time in seconds. 49 | """ 50 | self.count += 1 51 | return time()-self.start_time 52 | 53 | def __str__(self)->str: 54 | """ 55 | Return a debug string showing elapsed time and usage count. 56 | 57 | Also resets the internal start time to now for next measurement. 58 | 59 | Returns 60 | ------- 61 | str 62 | Debug-formatted string containing name, count, and elapsed time. 63 | """ 64 | self.count += 1 65 | out = time() - self.start_time 66 | self.start_time=time() 67 | return "[DEBUG] name: {}, count: {}, time: {} seconds".format(self.name, self.count, out) 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # project specific 2 | data/* 3 | slurm/* 4 | slurm_zay/* 5 | # old/ 6 | tmp/ 7 | logs 8 | logs_meso/* 9 | logs_zay/* 10 | logs_tristan/ 11 | dev/ 12 | configs/20* 13 | bash/submit_* 14 | bash/zay_* 15 | bash/meso_* 16 | bash/nnunet_* 17 | bash/omero_* 18 | bash/seafile_* 19 | bash/exp_* 20 | 21 | # Byte-compiled / optimized / DLL files 22 | __pycache__/ 23 | *.py[cod] 24 | *$py.class 25 | 26 | # C extensions 27 | *.so 28 | 29 | # Distribution / packaging 30 | .Python 31 | build/ 32 | develop-eggs/ 33 | dist/ 34 | downloads/ 35 | eggs/ 36 | .eggs/ 37 | lib/ 38 | lib64/ 39 | parts/ 40 | sdist/ 41 | var/ 42 | wheels/ 43 | pip-wheel-metadata/ 44 | share/python-wheels/ 45 | *.egg-info/ 46 | .installed.cfg 47 | *.egg 48 | MANIFEST 49 | 50 | # PyInstaller 51 | # Usually these files are written by a python script from a template 52 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 53 | *.manifest 54 | *.spec 55 | 56 | # Installer logs 57 | pip-log.txt 58 | pip-delete-this-directory.txt 59 | 60 | # Unit test / coverage reports 61 | htmlcov/ 62 | .tox/ 63 | .nox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *.cover 70 | *.py,cover 71 | .hypothesis/ 72 | .pytest_cache/ 73 | 74 | # Translations 75 | *.mo 76 | *.pot 77 | 78 | # Django stuff: 79 | *.log 80 | local_settings.py 81 | db.sqlite3 82 | db.sqlite3-journal 83 | 84 | # Flask stuff: 85 | instance/ 86 | .webassets-cache 87 | 88 | # Scrapy stuff: 89 | .scrapy 90 | 91 | # Sphinx documentation 92 | docs/_build/ 93 | 94 | # PyBuilder 95 | target/ 96 | 97 | # Jupyter Notebook 98 | .ipynb_checkpoints 99 | 100 | # IPython 101 | profile_default/ 102 | ipython_config.py 103 | 104 | # pyenv 105 | .python-version 106 | 107 | # pipenv 108 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 109 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 110 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 111 | # install all needed dependencies. 112 | #Pipfile.lock 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | -------------------------------------------------------------------------------- /src/biom3d/utils/os.py: -------------------------------------------------------------------------------- 1 | """This submodule provides function for file and directory managment.""" 2 | from datetime import datetime 3 | import os 4 | 5 | def create_save_dirs(log_dir:str, 6 | desc:str, 7 | dir_names:list[str]=['model', 'logs', 'images'], 8 | return_base_dir:bool=False, 9 | )->list[str]: 10 | """ 11 | Create a directory structure for saving models, logs, images, etc. 12 | 13 | Parameters 14 | ---------- 15 | log_dir: str 16 | Root directory in which to create the new structure. 17 | desc: str 18 | Name of the model that will be appended to the timestamp. 19 | dir_names: list of str, default=['model', 'logs', 'images'] 20 | Names of the subdirectories to create inside the base directory. 21 | return_base_dir: bool, default=False 22 | If True, the base directory path is included in the returned list. 23 | 24 | Returns 25 | ------- 26 | list_dirs : list of str 27 | List of full paths to the created subdirectories. Contains root if return_base_dir is True 28 | """ 29 | list_dirs = [] 30 | current_time = datetime.now().strftime("%Y%m%d-%H%M%S") 31 | base_dir = current_time + '-' + desc 32 | base_dir = os.path.join(log_dir, base_dir) 33 | for name in dir_names: 34 | list_dirs += [os.path.join(base_dir, name)] 35 | if not os.path.exists(list_dirs[-1]): 36 | os.makedirs(list_dirs[-1]) 37 | if return_base_dir: 38 | return [base_dir] + list_dirs 39 | else: 40 | return list_dirs 41 | 42 | # ---------------------------------------------------------------------------- 43 | # os utils 44 | 45 | def abs_path(root:str, listdir_:list[str])->list[str]: 46 | """ 47 | Convert a list of filenames into absolute paths using the given root. 48 | 49 | Parameters 50 | ---------- 51 | root: str 52 | Root directory to prepend to each filename. 53 | listdir_: list of str 54 | List of filenames or relative paths. 55 | 56 | Returns 57 | ------- 58 | list_abs_paths: list of str 59 | List of absolute paths constructed by joining `root` and each element of `listdir_`. 60 | 61 | Notes 62 | ----- 63 | Is not recursive. 64 | """ 65 | listdir = listdir_.copy() 66 | for i in range(len(listdir)): 67 | listdir[i] = os.path.join(root, listdir[i]) 68 | return listdir 69 | 70 | def abs_listdir(path:str)->list[str]: 71 | """ 72 | List all files in a directory and return their absolute paths (sorted). 73 | 74 | Parameters 75 | ---------- 76 | path: str 77 | Path to the directory to list. 78 | 79 | Returns 80 | ------- 81 | list_abs_paths: list of str 82 | Sorted list of absolute paths for each file in the directory. 83 | """ 84 | return abs_path(path, sorted(os.listdir(path))) 85 | -------------------------------------------------------------------------------- /docs/dep/docker.md: -------------------------------------------------------------------------------- 1 | # Docker deployment 2 | Biom3d provides Docker images, and this section will describe their maintenance and usage in more detail. 3 | They are created by `template.dockerfile` here : 4 | ```{literalinclude} ../../deployment/dockerfiles/template.dockerfile 5 | :language: Dockerfile 6 | :linenos: 7 | ``` 8 | > Available images are Linux only, Windows ones aren't on our backlog. 9 | 10 | ## Build arguments 11 | The following build arguments are supported : 12 | - `BASE_IMAGE` : The base image used to build Biom3d images. By default, it's ubuntu:22.04. Our images aiming for GPU (Nvidia) uses official `PyTorch` images. 13 | - `TARGET` : Either cpu or gpu, it ils only used in our [CI/CD](cicd.md) that use it to automatically create the tag and create a CUDA symlink in the image for usage sake. 14 | - `PYTHON_VERSION` : Python version used in the image, we recommend the `3.11` as some Biom3d dependencies aren't all compatible with all version. 15 | - `OMERO_VERSION` : The `omero-py` package version. For now only the `5.21.0` has been tested. 16 | - `TESTED` : Indicate the image stability 17 | - `1` (default), tested and stable 18 | - `0`, the entry point will display a warning message. It is used for images that should work (theoretically) but couldn't be tested extensively. 19 | 20 | ## Installing dependencies 21 | Biom3d automatically install its dependencies with : 22 | ```bash 23 | pip install biom3d 24 | ``` 25 | 26 | But some optionnal dependencies require additional steps : 27 | - **`omero-py`** : It require `zeroc-ice<3.7` difficult to find for Linux, prebuild found [here](https://github.com/glencoesoftware/zeroc-ice-py-linux-x86_64/releases). Then we can install `omero-py`. Always install `zeroc-ice` before `omero-py` or it will try to compile it from C++. 28 | - **`ezomero`** must be installed with `--no-deps` or it will downgrade `numpy` to an incompatible version and break everything. Other dependencies use `numpy 2.x` that is marked as not compatible with `ezomero` but the `2.2.6` hasn't created a problem. Expect a incompatibility warning that you can ignore. As it is installed with `--no-deps`, always install it after `omero-py`. 29 | - **`tkinter`** comes with system dependencies but is easily installed with `apt install python${PYTHON_VERSION}-tk`. 30 | 31 | ## Entrypoint 32 | The default entrypoint is 33 | dockerfile` here : 34 | ```{literalinclude} ../../deployment/dockerfiles/entrypoint.sh 35 | :language: bash 36 | :linenos: 37 | ``` 38 | 39 | It displays a warning if needed and launches Biom3d, waiting for a submodule and its arguments. 40 | This script is intentionally simple — feel free to replace it with a custom entrypoint suited to your use case. 41 | 42 | ## Other specificities 43 | The `WORKDIR` is set on `/workspace` which mean that dataset volume should be attached there. If you want a more granular approach (for example that `preprocess_train` doesn't create preprocessed data folder there) you can do it by modifying the `ENTRYPOINT` script or `WORKDIR` (or both). 44 | 45 | The line `ln -s /opt/conda/lib/libnvrtc.so.11.2 /opt/conda/lib/libnvrtc.so || true` is here to do a symlink so that `torch` find the cuda drivers. -------------------------------------------------------------------------------- /bash/run_preprocess_train.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #SBATCH -o ./slurm/%j-train.out # STDOUT 3 | 4 | # python -m biom3d.preprocess_train\ 5 | # --img_dir /home/gumougeot/all/codes/python/biom3d/data/pancreas/imagesTs_tiny\ 6 | # --msk_dir /home/gumougeot/all/codes/python/biom3d/data/pancreas/labelsTs_tiny\ 7 | # --num_classes 2 8 | 9 | # python -m biom3d.preprocess_train\ 10 | # --img_dir data/msd/Task02_Heart/imagesTr\ 11 | # --msk_dir data/msd/Task02_Heart/labelsTr\ 12 | # --num_classes 1 13 | 14 | # python -m biom3d.preprocess_train\ 15 | # --img_dir data/msd/Task06_Lung/imagesTr_train\ 16 | # --msk_dir data/msd/Task06_Lung/labelsTr_train\ 17 | # --num_classes 1\ 18 | # --ct_norm 19 | 20 | # python -m biom3d.preprocess_train\ 21 | # --img_dir data/btcv/Training_official/img\ 22 | # --msk_dir data/btcv/Training_official/label\ 23 | # --num_classes 13\ 24 | # --ct_norm 25 | 26 | # python -m biom3d.preprocess_train\ 27 | # --img_dir data/btcv/Training_small/img\ 28 | # --msk_dir data/btcv/Training_small/label\ 29 | # --num_classes 13\ 30 | # --ct_norm 31 | 32 | # python -m biom3d.preprocess_train\ 33 | # --img_dir data/nucleus/official/train/img\ 34 | # --msk_dir data/nucleus/official/train/msk\ 35 | # --num_classes 1 36 | 37 | # python -m biom3d.preprocess_train\ 38 | # --img_dir data/mito/train/img\ 39 | # --msk_dir data/mito/train/msk\ 40 | # --num_classes 1\ 41 | # --desc unet_mito 42 | 43 | # python -m biom3d.preprocess_train\ 44 | # --img_dir data/msd/Task01_BrainTumour/imagesTr_train\ 45 | # --msk_dir data/msd/Task01_BrainTumour/labelsTr_train\ 46 | # --num_classes 3\ 47 | # --desc unet_brain 48 | 49 | # python -m biom3d.preprocess_train\ 50 | # --img_dir data/nucleus/aline_nucleus_48h24hL/img\ 51 | # --msk_dir data/nucleus/aline_nucleus_48h24hL/msk\ 52 | # --num_classes 1\ 53 | # --desc nucleus_48h24hL 54 | 55 | # python -m biom3d.preprocess_train\ 56 | # --img_dir data/nucleus/chromo/img\ 57 | # --msk_dir data/nucleus/chromo/msk\ 58 | # --num_classes 1\ 59 | # --desc nucleus_chromo 60 | 61 | # python -m biom3d.preprocess_train\ 62 | # --img_dir data/nucleus/official/train/img\ 63 | # --msk_dir data/nucleus/official/train/msk\ 64 | # --num_classes 1\ 65 | # --desc nucleus_official 66 | 67 | 68 | # python -m biom3d.preprocess_train\ 69 | # --img_dir data/nucleus/aline_48h72hL/img\ 70 | # --msk_dir data/nucleus/aline_48h72hL/msk_chromo\ 71 | # --num_classes 1\ 72 | # --desc nucleus_48h72hL\ 73 | # --num_epochs 300 74 | 75 | 76 | # python -m biom3d.preprocess_train\ 77 | # --img_dir data/nucleus/aline_all/img\ 78 | # --msk_dir data/nucleus/aline_all/msk\ 79 | # --num_classes 1\ 80 | # --desc nucleus_aline_all 81 | 82 | # python -m biom3d.preprocess_train\ 83 | # --img_dir data/reims/img_stack\ 84 | # --msk_dir data/reims/msk_stack\ 85 | # --num_classes 1\ 86 | # --desc reims 87 | 88 | # python -m biom3d.preprocess_train\ 89 | # --img_dir data/reims/large/img_split\ 90 | # --msk_dir data/reims/large/msk_split\ 91 | # --num_classes 1\ 92 | # --desc reims_large 93 | 94 | python -m biom3d.preprocess_train\ 95 | --img_dir data/reims/large/img\ 96 | --msk_dir data/reims/large/msk\ 97 | --num_classes 1\ 98 | --desc reims_large_full -------------------------------------------------------------------------------- /docs/api/utils/datahandler.rst: -------------------------------------------------------------------------------- 1 | DataHandler 2 | =========== 3 | 4 | DataHandler is the class to use to load, save and iterate on images, masks or foreground. 5 | 6 | .. currentmodule:: biom3d.utils 7 | 8 | Instanciating a DathaHandler with the :class:`DataHandlerFactory` 9 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | 11 | If you want to use a :class:`DataHandler`, we strongly recommend using our factory. 12 | 13 | .. autoclass:: DataHandlerFactory 14 | :members: 15 | 16 | .. currentmodule:: biom3d.utils.data_handler.data_handler_abstract 17 | 18 | The :class:`DataHandler` contract 19 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | 21 | Here we will describe the abstract class :class:`DataHandler`, so you can use it or implement a new one. 22 | 23 | Publics attributes 24 | ------------------ 25 | .. autoattribute:: DataHandler.images 26 | .. autoattribute:: DataHandler.masks 27 | .. autoattribute:: DataHandler.fg 28 | .. autoattribute:: DataHandler.msk_outpath 29 | 30 | Privates attributes 31 | ------------------- 32 | .. autoattribute:: DataHandler._images_path_root 33 | .. autoattribute:: DataHandler._masks_path_root 34 | .. autoattribute:: DataHandler._fg_path_root 35 | .. autoattribute:: DataHandler._image_index 36 | .. autoattribute:: DataHandler._iterator 37 | .. autoattribute:: DataHandler._size 38 | .. autoattribute:: DataHandler._saver 39 | 40 | Public methods 41 | -------------- 42 | .. automethod:: DataHandler.open 43 | .. automethod:: DataHandler.close 44 | .. automethod:: DataHandler.get_output 45 | .. automethod:: DataHandler.load 46 | .. automethod:: DataHandler.save 47 | .. automethod:: DataHandler.insert_prefix_to_name 48 | .. automethod:: DataHandler.reset_iterator 49 | 50 | 51 | Private methods 52 | --------------- 53 | .. automethod:: DataHandler._input_parse 54 | .. automethod:: DataHandler._output_parse 55 | .. automethod:: DataHandler._output_parse_preprocess 56 | .. automethod:: DataHandler._save 57 | 58 | Specials methods 59 | ---------------- 60 | .. automethod:: DataHandler.__init__ 61 | .. automethod:: DataHandler.__iter__ 62 | .. automethod:: DataHandler.__next__ 63 | .. automethod:: DataHandler.__len__ 64 | .. automethod:: DataHandler.__del__ 65 | 66 | :class:`OutputType` 67 | ~~~~~~~~~~~~~~~~~~~ 68 | 69 | .. autoclass:: OutputType 70 | .. autoattribute:: OutputType.IMG 71 | .. autoattribute:: OutputType.MSK 72 | .. autoattribute:: OutputType.FG 73 | .. autoattribute:: OutputType.PRED 74 | 75 | Adding a new dataset format 76 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 77 | 78 | To add a new format, only two thing are required : 79 | 80 | - Create a new implementation of :class:`DataHandler`. 81 | 82 | .. note:: 83 | 84 | Redefine all abstract methods, if you think one of the other methods need a redefinition, do it, just respect the contract. 85 | You can use existing implementations as base. 86 | 87 | - Add some code to the :class:`DataHandlerFactory` to allow it to recognize your new implementation. 88 | - Document your format in `docs/tuto/dataset.md`, specially if your implementation need a specific dataset structure. 89 | 90 | .. note:: 91 | 92 | When testing, be sure to also test with dataset of only 1 image to test if preprocessing._split_image work well. 93 | 94 | Adding a new image format 95 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 96 | .. currentmodule:: biom3d.utils.data_handler.file_handler 97 | 98 | In case you work on file using :class:`FileHandler` and you need to use another format than Numpy, TIFF or Nifty, you can easily implement it. 99 | 100 | In the module `biom3d.utils.data_handler.file_handler`, there is a static class named :class:`ImageManager`. This class implement the methods to read and save a single image as a file. 101 | 102 | Two functions will interest us : 103 | 104 | .. autoclass:: ImageManager 105 | :members: adaptive_imread, adaptive_imsave 106 | 107 | To implement a new file format for image (for example png because why not) you simply have to add the possibility for those two function to treat the new format, then it is all automatic. 108 | 109 | .. note:: 110 | 111 | We strongly advise to create two separate private function, one for reading and another one for saving, and call them in adaptive. -------------------------------------------------------------------------------- /src/biom3d/register.py: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------- 2 | # A register for all the existing methods 3 | # Aim of this module: 4 | # - to gather all required imports in a single file 5 | # - to use it in colaboration with a config file 6 | #--------------------------------------------------------------------------- 7 | 8 | from biom3d.utils import AttrDict 9 | 10 | #--------------------------------------------------------------------------- 11 | # dataset register 12 | 13 | from biom3d.datasets.semseg_patch_fast import SemSeg3DPatchFast 14 | from biom3d.datasets.semseg_torchio import TorchioDataset 15 | 16 | datasets = AttrDict( 17 | SegPatchFast =AttrDict(fct=SemSeg3DPatchFast, kwargs=AttrDict()), 18 | Torchio =AttrDict(fct=TorchioDataset, kwargs=AttrDict()), 19 | ) 20 | 21 | try: 22 | # Batchgen use nnUnet batchgenerator that may not be installed (it is not a dependency), do pip install batchgenerators 23 | from biom3d.datasets.semseg_batchgen import MTBatchGenDataLoader 24 | datasets.BatchGen = AttrDict(fct=MTBatchGenDataLoader, kwargs=AttrDict()) 25 | except: 26 | pass 27 | 28 | #--------------------------------------------------------------------------- 29 | # model register 30 | 31 | from biom3d.models.encoder_vgg import VGGEncoder, EncoderBlock 32 | from biom3d.models.unet3d_vgg_deep import UNet 33 | from biom3d.models.encoder_efficientnet3d import EfficientNet3D 34 | from biom3d.models.unet3d_eff import EffUNet 35 | from monai.networks import nets 36 | 37 | models = AttrDict( 38 | VGG3D =AttrDict(fct=VGGEncoder, kwargs=AttrDict(block=EncoderBlock, use_head=True)), 39 | UNet3DVGGDeep =AttrDict(fct=UNet, kwargs=AttrDict()), 40 | Eff3D =AttrDict(fct=EfficientNet3D.from_name, kwargs=AttrDict()), 41 | EffUNet =AttrDict(fct=EffUNet, kwargs=AttrDict()), 42 | SwinUNETR =AttrDict(fct=nets.SwinUNETR, kwargs=AttrDict()), 43 | ) 44 | 45 | #--------------------------------------------------------------------------- 46 | # metric register 47 | 48 | import biom3d.metrics as mt 49 | 50 | metrics = AttrDict( 51 | Dice =AttrDict(fct=mt.Dice, kwargs=AttrDict()), 52 | DiceBCE =AttrDict(fct=mt.DiceBCE, kwargs=AttrDict()), 53 | DiceCEnnUNet=AttrDict(fct=mt.DC_and_CE_loss, kwargs=AttrDict(soft_dice_kwargs={'batch_dice': True, 'smooth': 1e-5, 'do_bg': False}, ce_kwargs={})), 54 | IoU =AttrDict(fct=mt.IoU, kwargs=AttrDict()), 55 | MSE =AttrDict(fct=mt.MSE, kwargs=AttrDict()), 56 | CE =AttrDict(fct=mt.CrossEntropy, kwargs=AttrDict()), 57 | DeepMSE =AttrDict(fct=mt.DeepMetric, kwargs=AttrDict(metric=mt.MSE)), 58 | DeepDiceBCE =AttrDict(fct=mt.DeepMetric, kwargs=AttrDict(metric=mt.DiceBCE)), 59 | ) 60 | 61 | #--------------------------------------------------------------------------- 62 | # trainer register 63 | 64 | from biom3d.trainers import ( 65 | seg_train, 66 | seg_validate, 67 | seg_patch_validate, 68 | seg_patch_train, 69 | ) 70 | 71 | trainers = AttrDict( 72 | SegTrain =AttrDict(fct=seg_train, kwargs=AttrDict()), 73 | SegVal =AttrDict(fct=seg_validate, kwargs=AttrDict()), 74 | SegPatchTrain =AttrDict(fct=seg_patch_train, kwargs=AttrDict()), 75 | SegPatchVal =AttrDict(fct=seg_patch_validate, kwargs=AttrDict()), 76 | ) 77 | 78 | #--------------------------------------------------------------------------- 79 | # Preprocessor and predictor register 80 | # We register preprocessors here because they are needed to preprocess 81 | # data before prediction. 82 | # Preprocessor must correspond to the one used to preprocess data 83 | # before training. 84 | 85 | from biom3d.preprocess import seg_preprocessor 86 | 87 | preprocessors = AttrDict( 88 | Seg = AttrDict(fct=seg_preprocessor, kwargs=AttrDict()) 89 | ) 90 | 91 | from biom3d.predictors import ( 92 | seg_predict, 93 | seg_predict_patch_2, 94 | ) 95 | 96 | predictors = AttrDict( 97 | Seg = AttrDict(fct=seg_predict, kwargs=AttrDict()), 98 | SegPatch = AttrDict(fct=seg_predict_patch_2, kwargs=AttrDict()), 99 | ) 100 | 101 | from biom3d.predictors import seg_postprocessing 102 | 103 | postprocessors = AttrDict( 104 | Seg = AttrDict(fct=seg_postprocessing, kwargs=AttrDict()) 105 | ) 106 | 107 | -------------------------------------------------------------------------------- /src/biom3d/preprocess_train.py: -------------------------------------------------------------------------------- 1 | """ 2 | "All-in-one" command. 3 | 4 | Subsequently run the preprocessing and the training. 5 | """ 6 | 7 | import argparse 8 | from biom3d.preprocess import auto_config_preprocess 9 | from biom3d.builder import Builder 10 | 11 | def preprocess_train( 12 | img_path:str, 13 | msk_path:str, 14 | num_classes:str, 15 | config_dir:str="configs/", 16 | base_config:str|None=None, 17 | ct_norm:bool=False, 18 | desc:str="unet", 19 | max_dim:int=128, 20 | num_epochs:int=1000, 21 | is_2d:bool=False, 22 | )->Builder: 23 | """ 24 | Preprocess images and masks, then launch training with given configuration. 25 | 26 | This function automates preprocessing configuration creation and runs the training process. 27 | 28 | Parameters 29 | ---------- 30 | img_path : str 31 | Path to the collection conating images. 32 | msk_path : str 33 | Path to the collection conating masks. 34 | num_classes : int 35 | Number of classes for segmentation, background not included. 36 | config_dir : str,default="configs/" 37 | Directory where preprocessing configurations are saved. 38 | base_config : str or None, optional 39 | Path to a base configuration file to start from. 40 | ct_norm : bool, default=False 41 | Whether to apply CT normalization during preprocessing. 42 | desc : str, default="unet" 43 | Model name. 44 | max_dim : int, default=128 45 | Maximum dimension size used in preprocessing. 46 | num_epochs : int, default=1000 47 | Number of epochs for training. 48 | is_2d : bool, default=False 49 | Whether to treat the input data as 2D slices. 50 | 51 | Returns 52 | ------- 53 | biom3d.builder.Builder 54 | The Builder instance that was used to run training, which contains training details and results. 55 | """ 56 | # preprocessing 57 | config_path = auto_config_preprocess( 58 | img_path=img_path, 59 | msk_path=msk_path, 60 | num_classes=num_classes, 61 | config_dir=config_dir, 62 | base_config=base_config, 63 | ct_norm=ct_norm, 64 | desc=desc, 65 | max_dim=max_dim, 66 | num_epochs=num_epochs, 67 | is_2d=is_2d 68 | ) 69 | 70 | # training 71 | builder = Builder(config=config_path) 72 | builder.run_training() 73 | print("Training done!") 74 | return builder 75 | 76 | if __name__=='__main__': 77 | parser = argparse.ArgumentParser(description="Let's do it all-at-once! Subsequent preprocessing and training.") 78 | parser.add_argument("--img_path","--img_dir",dest="img_path", type=str,required=True, 79 | help="Path of the images collection") 80 | parser.add_argument("--msk_path","--msk_dir",dest="msk_path", type=str, default=None, 81 | help="(default=None) Path to the masks/labels collection") 82 | parser.add_argument("--num_classes", type=int, default=1, 83 | help="(default=1) Number of classes (types of objects) in the dataset. The background is not included.") 84 | parser.add_argument("--max_dim", type=int, default=128, 85 | help="(default=128) max_dim^3 determines the maximum size of patch for auto-config.") 86 | parser.add_argument("--num_epochs", type=int, default=1000, 87 | help="(default=1000) Number of epochs for the training.") 88 | parser.add_argument("--config_dir", type=str, default='configs/', 89 | help="(default=\'configs/\') Configuration folder to save the auto-configuration.") 90 | parser.add_argument("--base_config", type=str, default=None, 91 | help="(default=None) Optional. Path to an existing configuration file which will be updated with the preprocessed values.") 92 | parser.add_argument("--desc", type=str, default='unet_default', 93 | help="(default=unet_default) Optional. A name used to describe the model.") 94 | parser.add_argument("--ct_norm", default=False, action='store_true', dest='ct_norm', 95 | help="(default=False) Whether to use CT-Scan normalization routine (cf. nnUNet).") 96 | parser.add_argument("--is_2d", default=False, dest='is_2d', 97 | help="(default=False) Whether the image is 2d.") 98 | 99 | args = parser.parse_args() 100 | 101 | preprocess_train( 102 | img_path=args.img_path, 103 | msk_path=args.msk_path, 104 | num_classes=args.num_classes, 105 | config_dir=args.config_dir, 106 | base_config=args.base_config, 107 | ct_norm=args.ct_norm, 108 | desc=args.desc, 109 | max_dim=args.max_dim, 110 | num_epochs=args.num_epochs, 111 | is_2d=args.is_2d, 112 | ) -------------------------------------------------------------------------------- /docs/tuto/server.md: -------------------------------------------------------------------------------- 1 | # Server and client 2 | Biom3d can be used in a server/client mode, allowing usage from low spec computer by delegating computation to another computer. 3 | 4 | (remote)= 5 | ## Using the client (or remote) 6 | It is a light version of Biom3d specifically made to interact with a server. 7 | 8 | To use the client, simply download it from a [release](https://github.com/GuillaumeMougeot/biom3d/releases). It is the file ending with a "Remote". Once you have dowloaded it, start it (you may have a warning that require you to confirm the execution). 9 | 10 | Then you just have to enter the IP adress of the server, the username, password and folder to work in. Every of those parameters are to be given by the person that installed the server instance (most likely your IT department). 11 | 12 | Once you've enter those parameter and clicked on `start remotely` and then the same way as the [GUI](gui.md). Here we will describe the differences. In the [GUI](gui.md) documentation, there is a full walktrough. 13 | 14 | ### Training 15 | In the training tab, the sole difference is the dataset sending. Dataset are stored on the server and you need to send them to the server. 16 | For that you select the folder where are the raw et the one where are the label and name your dataset with a unique identifier. Then click on `Send Dataset` and it will be send to the server. 17 | You can then select that dataset to train in the training configuration (if you just sended it, you should click on `update`). 18 | ![Screenshot of training tab on client](../_static/image/gui_remote_preprocess&train.png) 19 | 20 | ### Prediction 21 | The predictions tab have the same functionnality of sending data. 22 | You can select existing data folder or create a new one. However you can only select models that are present on the server and not export one (this feature is on our backlog). Then, once the prediction is finished, you can select the folder on the server and download it to the given folder on your computer (or send it to OMERO). 23 | ![Screenshot of prediction tab on client](../_static/image/gui_remote_predict.png) 24 | 25 | (server)= 26 | ## Setting up a server 27 | ### File architecture on server 28 | For the moment, server only work on Linux. You need to create a dedicated ²user and define a work folder. Then you must have Biom3d installed (see [here](../installation.md)). 29 | - You can install it on the machine directly, however we advise using a virtual environment. 30 | - If you install it in a virtual environment, it should be a `pyvenv` (it will be activated with `source ./bin/activate`), that environment must be at the root of the work folder. 31 | 32 | Biom3d will be called with `python -m biom3d.submodule` so ensure that `python` refer to the version with Biom3d. 33 | 34 | Biom3d will store its datasets and trained models in the folder `data` and `logs`, that have to be created in the working folder. 35 | 36 | Structure of working folder. 37 | ``` 38 | ├── data 39 | ├── logs 40 | └── env (optionnal) 41 | ``` 42 | A good practice would be to use `/home/user` as working folder, and never a folder at root. 43 | 44 | ### Connection 45 | You have what Biom3d want on your server, now lets see the connection. 46 | Biom3d connect in `SSH` and transfert file with `SFTP` so your server must have a SSH server (like `open-ssh`) running and listening on port 22. There is also the possibility to use a proxy for connexion. 47 | 48 | ### Security 49 | As we use SSH, it is important to set some security. 50 | Biom3d only authentification method is that password of the user where Biom3d is installed must be used when initializing a connection (you can also not use one). 51 | 52 | We **strongly advise** to set up a classical key authentification and give the public key to your end user (it will require a setup step that they may struggle with so document it). 53 | 54 | Another security measure would be to filter the SSH commands. You can find the command used by Biom3d by searching for `REMOTE.exec_command` in the [gui code](../../src/biom3d/gui.py). 55 | 56 | ### Docker 57 | Official Biom3d images are made to be one use, however it is possible to use them as a base fo building a server by either : 58 | - Doing the basic setup and creating a SSH entrypoint that will modify and delegate call to Biom3d to the Docker image 59 | - Building a new Docker image that act as a standalone server by either creating a whole new [dockerfile](../../deployment/dockerfiles/template.dockerfile) or using existing image as base image. 60 | 61 | ### What your user need 62 | Your user will need to know : 63 | - The IP adress of the server 64 | - The user 65 | - The user's password 66 | - The working folder (absolute path) 67 | - The environment name (if you use one) 68 | 69 | And also if you use a proxy, the IP address, user and password. 70 | 71 | That's the minimum, if you use key authentification, you will have to transmit the public key and tell them how to make it accessible for the SSH client. 72 | -------------------------------------------------------------------------------- /src/biom3d/eval.py: -------------------------------------------------------------------------------- 1 | """ 2 | Evaluation module. 3 | 4 | Used to compare predictions and groundtruth. 5 | 6 | Examples 7 | -------- 8 | .. code-block:: bash 9 | 10 | python -m biom3d.eval -p MyPred -l MyMasks --num_classes 2 11 | python -m biom3d.eval -p MyPred -l MyMasks -f IoU --num_classes 2 12 | 13 | Or in python 14 | 15 | .. code-block:: python 16 | 17 | print(eval("./MyPred","./MyMasks",2)) 18 | print(eval("./MyPred","./MyMasks",2,iou)) 19 | """ 20 | 21 | import numpy as np 22 | import argparse 23 | 24 | from biom3d.utils import versus_one, dice, iou,DataHandlerFactory 25 | 26 | from typing import Callable 27 | 28 | def robust_sort(str_list: list[str]) -> list[str]: 29 | """ 30 | Perform a robust sorting of a list of strings, useful for sorting file paths. 31 | 32 | The sorting pads strings with zeros at the beginning so that all have the same length, 33 | then sorts lexicographically, and finally removes the padding. 34 | 35 | Parameters 36 | ---------- 37 | str_list : list of str 38 | List of strings to sort. 39 | 40 | Returns 41 | ------- 42 | list of str 43 | The sorted list of strings. 44 | """ 45 | # max string lenght 46 | max_len = max([len(s) for s in str_list]) 47 | 48 | # add zeros in the beginning so that all strings have the same length 49 | # associate with the original length to the elongated string 50 | same_len = {'0'*(max_len-len(s))+s:len(s) for s in str_list} 51 | 52 | # sort the dict by key 53 | sorted_same_len = {k:same_len[k] for k in sorted(same_len)} 54 | 55 | # remove zeros and return 56 | return [k[max_len-v:] for k,v in sorted_same_len.items()] 57 | 58 | def eval(path_lab: str, 59 | path_out: str, 60 | num_classes: int, 61 | fct: Callable = dice, 62 | ) -> tuple[list[float], float]: 63 | """ 64 | Evaluate segmentation results by comparing predictions to labels using a given metric. 65 | 66 | Parameters 67 | ---------- 68 | path_lab : str 69 | Path to the folder containing label images. 70 | path_out : str 71 | Path to the folder containing predicted images. 72 | num_classes : int 73 | Number of classes for evaluation. 74 | fct : Callable, optional 75 | Metric function to compute (default is `dice`). 76 | 77 | Returns 78 | ------- 79 | results: list of float 80 | List of metric results per image. 81 | mean: float 82 | Average of results 83 | """ 84 | print("Start evaluation") 85 | handler1 = DataHandlerFactory.get( 86 | path_lab, 87 | read_only=True, 88 | eval='label', 89 | ) 90 | 91 | handler2 = DataHandlerFactory.get( 92 | path_out, 93 | read_only=True, 94 | eval='pred', 95 | ) 96 | assert len(handler1) == len(handler2), f"[Error] Not the same number of labels and predictions! '{len(handler1)}' for '{len(handler2)}'" 97 | 98 | results = [] 99 | for (img1,_,_,),(img2,_,_) in zip(handler1,handler2): 100 | print("Metric computation for:", img1,img2) 101 | res = versus_one( 102 | fct=fct, 103 | input_img=handler1.load(img1)[0], 104 | target_img=handler2.load(img2)[0], 105 | num_classes=num_classes+1, 106 | single_class=None) 107 | 108 | print("Metric result:", res) 109 | results += [res] 110 | 111 | print("Evaluation done! Average {} result:".format(fct.__name__ ), np.mean(results)) 112 | return results, np.mean(results) 113 | 114 | 115 | if __name__=='__main__': 116 | supported_function = { 117 | "dice":dice, 118 | "equals":np.equal, 119 | "iou":iou, 120 | } 121 | parser = argparse.ArgumentParser(description="Prediction evaluation.") 122 | parser.add_argument("-p", "--path_pred","--dir_pred",dest="path_pred", type=str, default=None, 123 | help="Path to the prediction collection") 124 | parser.add_argument("-l", "--path_lab","--dir_lab",dest="path_lab", type=str, default=None, 125 | help="Path to the label collection") 126 | parser.add_argument("-f", "--function",dest="function", type=str, default='dice', 127 | help=f"(default=dice) Function used for evaluation. Supported : {', '.join(supported_function.keys())}") 128 | parser.add_argument("--num_classes", type=int, default=1, 129 | help="(default=1) Number of classes (types of objects) in the dataset. The background is not included.") 130 | args = parser.parse_args() 131 | if args.function not in supported_function: 132 | print("Function '{}' not supported. Supported functions :'{}'".format(args.function,supported_function.keys())) 133 | exit(1) 134 | eval(args.path_lab, args.path_pred, args.num_classes,supported_function[args.function]) 135 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.1.2] - Patching errors from previous version 4 | 5 | - median_spacing was set to np.array(0) which raised an error during preprocessing. 6 | - Dice score computation was incorrect for images with a C dimension. 7 | - the patch size first dimension could be 2 instead of 1 for 2D images. 8 | - the flip augmentation was causing an error for 2D images. 9 | 10 | ## [v0.1.1] - Patching errors from first version and added SwinUNETR 11 | 12 | This patch adds SwinUNETR to the register and fixes some issues caused by the new DataHandler class. 13 | 14 | ## [v0.1.0] - First BETA version of Biom3d 15 | This release merge several fork and add new functionality to Biom3d. 16 | 17 | **New funtionalities** 18 | - 2D images can be taken as input 19 | - OMERO training data (same way as the prediction does) 20 | - Easier to install with our brand new installers (Windows and macOS) 21 | - Remote mode on macOS 22 | - Official Docker images 23 | - Automated deployment pipeline 24 | - Metal (Applie Silicon) compatibility, due to us not having Macs with Apple Silicon, it may still be unstable, contact us if you encounter a problem 25 | - New DataHandler system that abstract image loading/saving and allow to easily implement new image format 26 | - HDF5 files are now supported 27 | 28 | **Changes** 29 | - Documentation : 30 | - Documentation structure has changed 31 | - Documentation now have a Developper part where the API and the whole deployment scripts are described 32 | - Installation documentation updated 33 | - New Docker documentation 34 | - New Server/Client documentation 35 | - New Config documentation 36 | - New Dataset documentation 37 | - New DataHandler documentation 38 | - Almost all classes and functions are now documented, exception are: 39 | - FileHandler and HDF5Handler, refer to DataHandler doc 40 | - gui.py, because it's a mess 41 | - Bug fix 42 | - Added a new safegard in `auto_config.py` that prevent `None` element in `spacings` list and empty `spacings`, preventing a `numpy.median(spacings)` error 43 | - Fixed `semseg_torchio` to ensure extracted foreground are casted to `numpy.ndarray` 44 | - Added `roll_stride` option to models and utils to make retrocompatibility with model trained before commit `f2ac9ee` (August 2023) 45 | - Fixed error in Google Collab 46 | - Added missing doc dependencies in pyproject.toml 47 | - Code 48 | - Simples refactorings and code cleaning 49 | - `versus_one()` doesn't load images by himself, you need to provide it with the images to compare, you can use `eval()` from `biom3d.eval` to do that automatically 50 | - Everywhere an image was loaded/saved, we now use a `Datahandler` that abstract the logic and allow to easily implement new formats. Sole exceptions are old predictions function. 51 | - Almost every variable refering to directory for image have been renamed to emphasize the new `DataHandler` system (except in GUI). 52 | - `Biom3d.utils` has been shattered to multiple files to ease reading and modification. The namespace hasn't changed. 53 | - Methods to read and save images has been moved to `utils.data_handler.file_handler` in the class `ImageManager`. The old path `biom3d.utils` still work but has been marked as deprecated. 54 | - Added condition `if not REMOTE` to display start locally button in GUI 55 | - Put constant initialization before imports in `gui.py` 56 | - Miscellaneous 57 | - Added `CHANGELOG.md` file 58 | - Now using semantic versionning 59 | - Biom3d version and date time (UTC) of training are stored in model config.yaml 60 | 61 | **Still in progress** 62 | - DockerHub link to include 63 | 64 | **Knwown issues** 65 | - On Windows, the `omero_preprocess_train` will crash trying to export model. You will need to manually export it to your Project 66 | 67 | 68 | ## [v0.0.30] - 2023-Sep-26 69 | All notable changes to this project will be documented in this file. 70 | New version of Biom3d! 71 | 72 | Most of the bugs have been solved: 73 | 74 | Preprocessing has been improved and takes multi channel images into account. 75 | Added Postprocessor module for ensemble prediction. 76 | Logs and metadata are now stored in the log folder. 77 | Finetuning works. 78 | Bug associated with the graphical user interface have been solved (tkinter, multiprocessing, matplotlib etc.) 79 | 80 | New simplified GUI ! (cf. documentation). 81 | 82 | ## [v0.0.6] - 2022-Dec-6 83 | This is the official and first version of the Windows Graphical User Interface of biom3d. 84 | 85 | Careful! The current version works only in remote mode. You must also install biom3d on a remote server to make it work. Please install biom3d, with Pypi (pip install biom3d), on a Linux remote server with a Nvidia GPU and then use “Start remotely” button in the local GUI to start. Refer to the documentation to get more information about how to install biom3d. 86 | 87 | To use the local version of the GUI you must install via Pypi and then run python -m biom3d.gui -L command. -------------------------------------------------------------------------------- /src/biom3d/utils/eval_metrics.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module define some simple metrics. 3 | 4 | The metrics defined here can't be used as loss function, contrarly to the module biom3d.metrics. 5 | """ 6 | 7 | from typing import Callable, Optional 8 | from biom3d.utils import one_hot_fast 9 | import numpy as np 10 | 11 | def iou(inputs:np.ndarray, targets:np.ndarray, smooth:float=1.0)->float: 12 | """ 13 | Calculate the Intersection over Union (IoU) score between two binary masks. 14 | 15 | Parameters 16 | ---------- 17 | inputs : numpy.ndarray 18 | Binary array representing the predicted mask. 19 | targets : numpy.ndarray 20 | Binary array representing the ground truth mask. 21 | smooth : float, default=1.0 22 | Smoothing factor to avoid division by zero. 23 | 24 | Returns 25 | ------- 26 | float 27 | IoU score between inputs and targets. 28 | """ 29 | inter = (inputs & targets).sum() 30 | union = (inputs | targets).sum() 31 | return (inter+smooth)/(union+smooth) 32 | 33 | def dice(inputs:np.ndarray, 34 | targets:np.ndarray, 35 | smooth:float=1.0, 36 | axis:tuple[int]=(-3,-2,-1), 37 | )->float: 38 | """ 39 | Compute the Dice coefficient between inputs and targets. 40 | 41 | Parameters 42 | ---------- 43 | inputs : numpy.ndarray 44 | Binary array or one-hot encoded mask of predictions. 45 | targets : numpy.ndarray 46 | Binary array or one-hot encoded mask of ground truth. 47 | smooth : float, default=1.0 48 | Smoothing factor to avoid division by zero. 49 | axis : tuple of int, default is last three axes (supposed spatial axis) 50 | Axes along which to compute the Dice score. 51 | 52 | Returns 53 | ------- 54 | float 55 | Mean Dice score over specified axes. 56 | """ 57 | inter = (inputs & targets).sum(axis=axis) 58 | dice = (2.*inter + smooth)/(inputs.sum(axis=axis) + targets.sum(axis=axis) + smooth) 59 | return dice.mean() 60 | 61 | def versus_one(fct:Callable, 62 | input_img:np.ndarray, 63 | target_img:np.ndarray, 64 | num_classes:int, 65 | single_class:Optional[int]=None, 66 | is_sigmoid_output:bool=False, 67 | )->float|None: 68 | """ 69 | Compare input and target images using a given metric function. 70 | 71 | This function: 72 | - Converts label images to one-hot encoding if needed. 73 | - Optionally selects a single class channel. 74 | - Binarizes masks. 75 | - Removes background class channel if present. 76 | - Checks shape compatibility. 77 | - Applies the provided comparison function `fct` on processed masks. 78 | 79 | Parameters 80 | ---------- 81 | fct : callable 82 | A function that takes two binary masks and returns a metric score (e.g., IoU or Dice). 83 | input_img : numpy.ndarray 84 | Input image as label indices or one-hot encoded mask. 85 | target_img : numpy.ndarray 86 | Target (ground truth) image as label indices or one-hot encoded mask. 87 | num_classes : int 88 | Number of classes expected in input and target images. 89 | single_class : int or None, optional 90 | Index of class to compare individually. If None, compares all classes. 91 | is_sigmoid_output : bool, optional 92 | Whether the first dimension is a label dimension (CZYX), typically output of sigmoid layer. 93 | For instance a 3D image could have the following dimensions: (2, 64, 111, 110) 94 | Otherwise, dimensions are supposed to be ZYX. 95 | 96 | Returns 97 | ------- 98 | float or None 99 | The score returned by `fct`. 100 | 101 | Notes 102 | ----- 103 | If shapes after processing don't match, prints an error message and returns None. 104 | Copy the images so no side effect. 105 | """ 106 | img1 = input_img.copy() 107 | img1 = img1.squeeze() 108 | if not is_sigmoid_output: 109 | img1 = one_hot_fast(img1.astype(np.uint8), num_classes)[1:,...] 110 | if single_class is not None: 111 | img1 = img1[single_class,...] 112 | img1 = (img1 > 0).astype(int) 113 | 114 | img2 = target_img.copy() 115 | img2 = img2.squeeze() 116 | if not is_sigmoid_output: 117 | img2 = one_hot_fast(img2.astype(np.uint8), num_classes)[1:,...] 118 | if single_class is not None: 119 | img2 = img2[single_class,...] 120 | img2 = (img2 > 0).astype(int) 121 | 122 | # remove background if needed 123 | if img1.shape[0]==(img2.shape[0]+1): 124 | img1 = img1[1:] 125 | if img2.shape[0]==(img1.shape[0]+1): 126 | img2 = img2[1:] 127 | 128 | if sum(img1.shape)!=sum(img2.shape): 129 | print("bug:sum(img1.shape)!=sum(img2.shape):") 130 | print("img1.shape", img1.shape) 131 | print("img2.shape", img2.shape) 132 | return # TODO should raise error 133 | out = fct(img1, img2) 134 | return out -------------------------------------------------------------------------------- /docs/how_it_works/pre_processing.md: -------------------------------------------------------------------------------- 1 | # Pre processing 2 | Here we will explain how the pre processing work and how to modify it. Some steps only apply on masks. 3 | 4 | ## Preprocessing function 5 | There are several steps, with some optionnals : 6 | 1. **(Image only) Dimension standardisation :** First it will change images to `(C,D,H,W)` for 3D images and `(C,1,H,W)` for 2D images. The missing dimensions are added, if your image already have a channel dimension be cautious that it is the smaller dimension as Biom3d consider the smallest dimension as the smallest. For example `(D=8,C=11,H=512,W=512)` would cause a problem. 7 | 2. **(Mask only) Corrections :** If the masks is in 2D, it will add new dimension. Then it will determine what kind of encoding your masks are in : 8 | 1. If the masks still has 3 dimension, it use `label` encoding. It collect the unique values of your voxels and assure there are in the given number of classes. If you have more uniques values that classes (eg `[0,1,2,3]` for 3 classes), it will crash. If you have the same number of values and classes but wrong values (eg `[1,2,8]`for 3 classes) it will rearrange them to `[0,1,2,...,number of classes]`. If there are only 2 classes, it will treat them the same way as `binary` encoding. We then add the channel dimension for standardisation. 9 | 2. If the masks has 4 dimension, it use `binary` encoding, so each voxel has a `0` or `1` for each channel. The strategy to fix potential error is to get all values and consider the most numerous value to be the background and the rest to be our channel. 10 | ``` 11 | 1,1,1 0,0,0 12 | 1,0,2 Will give 0,1,1 With here 0 as background and 1 as class. 13 | 1,1,0 0,0,1 14 | ``` 15 | 3. The `onehot` encoding isn't detected and must be forced (see below). This is a particular case of `binary` where each voxel has always only one channel at `1`. There is no correction with this encoding, it will throw an error if this is not respected. 16 | 17 | For the moment, the only way to force an encoding type is to call directly the function with specific parameter. 18 | 19 | Then in the case of not a `label` encoding and with a specific parameter, we go back to the initial number of dimension. This is not the default behaviour of this function. 20 | 3. **(Image only) Intensity transformation :** The value are clipped between the percentiles 0.5% and 99.5% (computed on the whole dataset), only if parameter `--ct_norm` is given to the module. Then we apply a zero mean unit variance normalization, if `--ct_norm` was used, we use mean and standard deviation of the whole dataset, else of the image. 21 | 4. **Spatial resampling :** In the case where spacings are given in the metadata of the image, the median spacing of the dataset and image spacing are used to resample the image so they all have the same spacing (being the median spacing). 22 | 5. **(Mask only) :** For each class, we get a maximum of 10,000 voxels of the class and store them in an array. Those array are used for training. 23 | 24 | That are the preprocessing done by Biom3d on each image, however, Biom3d does other things during preprocessing. 25 | 26 | ## Data fingerprint 27 | This is a `auto_config` function used before each preprocessing that collect and compute data from the whole dataset before doing the actual preprocessing. It will do : 28 | 1. **Spacing :** For each image, it will read the metadata to store the spacing (if spacing is in metadata), at the same time, it ensure that all images have the same number of dimension. 29 | 2. **(Mask only) Sampling :** If a path to the masks is given, it will also for each mask get a random sample of values on the image associated with the mask where the mask define this is not background. 30 | 3. **Fingerpring computation :** We use the samples to compute the `mean`, `standard deviation`, `percentile 0.5%` and `percentile 99.5%`. Those value are used by the preprocessing. 31 | 32 | You can retrieve those information in the `config.yaml` file of your model : 33 | ```yaml 34 | MEDIAN_SPACING: &id004 [] <- Empty because dataset didn't have metadata 35 | CLIPPING_BOUNDS: &id005 36 | - 0.0041880556382238865 <- Percentile 0.5% 37 | - 0.9984244108200073 <- Percentile 99.5% 38 | INTENSITY_MOMENTS: &id006 39 | - 0.4780538082122803 <- Mean 40 | - 0.2981347143650055 <- Standard deviation 41 | ``` 42 | 43 | ## Image splitting 44 | In case you are working with a dataset of only one image, this image will be split in two for training purpose (not in the predictions). We slice the image on the biggest dimension with a ratio of 25% validation and 75% training. 45 | 46 | ## Folds 47 | The preprocessing generate a file named `folds.csv`. For example (we added spaces for lisibility ): 48 | ``` 49 | filename, hold_out, fold 50 | img/img1.tif, 0, 0 51 | img/img2.tif, 0, 0 52 | img/img3.tif, 0, 1 53 | img/img4.tif, 0, 1 54 | img/img5.tif, 0, 2 55 | img/img6.tif, 0, 2 56 | img/img7.tif, 1, 0 57 | img/img8.tif, 1, 1 58 | ``` 59 | Let's break it down : 60 | - Filename is the image path (relative to where you starded the command from). Every images of your dataset are represented in this file. 61 | - Hold_out can have 2 values : `0` and `1`. The value `1` means that this image will only be used at the end of the training to evaluate model performance, `0` means that it can be use anywhere. 62 | - Fold : A fold is a subset of your dataset. During training, we use different groups of folds validation to mix the data and avoid overtraining. We can use folds 1 and 2 for a first validation and 0 and 1 for another. 63 | 64 | This file is not necessary for training (fold are computed if needed), but it better to use one for reproductibility sake. -------------------------------------------------------------------------------- /docs/dep/installer.md: -------------------------------------------------------------------------------- 1 | # Installer 2 | The Biom3d installer is a self-contained package intended to simplify deployment, it is aimed to Biologist so it is very simple to use. It includes a full Python environment and automatically updates itself on first run to match the host system’s CUDA configuration. 3 | 4 | There are currently two version of those : 5 | - **Windows 10/11** in `x86_64` architecture 6 | - **macOS** on `arm64` architecture (hasn't be fully tested yet) 7 | 8 | > Linux hasn't an installer as it is hard to make something universal and we consider Linux users able to do a [classic installation](../installation). 9 | 10 | Use : 11 | - Download it on a release 12 | - Unzip it 13 | - Execute the `.bat` (Windows) or `.app` (MacOS) 14 | 15 | ## Windows 16 | It follow this structure : 17 | ``` 18 | Biom3d/ 19 | ├── bin/ 20 | │ ├── Scripts/ 21 | │ │ ├── conda-unpack.exe 22 | │ │ └── ... 23 | │ ├── ... 24 | │ ├── python.exe 25 | │ ├── auto_update.py 26 | │ ├── env.bat 27 | │ └── ... 28 | ├── Biom3d.bat 29 | └── Biom3d.ico 30 | ``` 31 | 32 | ## MacOS 33 | It follow the MacOS application structure : 34 | ``` 35 | Biom3d.app/ 36 | └── Contents/ 37 | ├── Resources/ 38 | │ └── Biom3d.icns 39 | ├── MacOS/ 40 | │ ├── bin/ 41 | │ │ ├── bin/ 42 | │ │ │ ├── conda-unpack 43 | │ │ │ ├── python3.11 44 | │ │ │ └── ... 45 | │ │ └── ... 46 | │ └── Biom3d.sh 47 | └── Info.plist 48 | ``` 49 | 50 | ## Logic 51 | This section details the logic behind the executables and packaging scripts used to build and launch the Biom3d installer. 52 | 53 | ### Launcher scripts 54 | First are the "executables", `Biom3d.bat` (Windows) and `Biom3d.sh` (MacOS, and potentially Linux). 55 | 56 | They are very simple, here is their algorithm : 57 | 1. Get variables from `bin\env.bat` (Windows) or `bin/env.sh` (macOS/Linux) 58 | 2. If it is first launch (given by `FIRST_LAUNCH` variable): 59 | 1. Execute `conda-unpack`, it will make the virtual environment usable. 60 | 2. Execute `bin\auto_update.py` (described [here](#auto-updating)). 61 | > For the moment, macOS build doesn't use `auto_update.py` as Mac use Metal GPU drivers instead of CUDA and so the script is irrelevant there. 62 | 3. Execute `biom3d.gui` with the included `python` to launch Biom3d. 63 | 64 | ### Packaging 65 | Then are the packing script, `pack.bat` and `pack.sh`. 66 | 1. **Detect system architecure** : 67 | We retrive the building machine processor architecture, on Windows it was hard to obtain `x86_64` instead of `AMD64` that was unclear for non programmer so we decided that it should be passed as an argument (with a default value of `x86_64`). 68 | 2. **Create the conda environment** : 69 | We create a `conda` environment with `tkinter` and `python 3.11` (it has the most compatibilities with Biom3d dependencies). 70 | We are assuming two point : 71 | - `conda` is installed and in `PATH` 72 | - If there already is an environment with the same name, it has the same purpose and is reused. 73 | 3. **Install dependencies** : 74 | - We activate our environment. 75 | - Install `conda-pack`, it will allow us to export our environment. 76 | - We install `pip 23.1` with `conda` as it is a stable version of `pip` and that if not reinstalled we will have a `conda/pip` conflict at packing step and it will crash. 77 | - We then install all that is necessary for omero : 78 | - `zeroc-ica 3.6.5` with `conda`. 79 | - Then the others `omero-py` dependencies with `pip` to finally install `omero-py`. On Windows `omero-py` would try to recompile `zeroc-ice` so we use `--no-deps` 80 | - `ezomero` with `pip` and `--no-deps`. We use `--no-deps` as `ezomero` would reinstall `numpy` and break other packages (they're should be a incompatibility warning with `numpy 2.x` and `ezomero` but we tested with `numpy 2.2.6` and it worked fine). 81 | 4. **Install Biom3d** : 82 | With a simple `pip install .` with source code. The script are made to be used in the CI/CD so we assume we are in the repository. 83 | 5. **Creating the folder** 84 | We create the folder with the folder structure describe earlier. 85 | 6. **Packing** 86 | - Then we pack in the `bin/` subfolder with the command `conda pack --format=no-archive -o %DIR%\bin`. 87 | - Copy the `auto_update.py` and `env.bat` or `env.sh` in `bin/`. 88 | - We copy the `logo.ico` and `Biom3d.bat` or `logo.icns` and `Biom3d.sh`. 89 | 7. **Zipping** 90 | We zip it with the following name convention : `Biom3d_$OS_$ARCHITECURE.zip`. 91 | 92 | Note that the hardest part of this scipt is dependencies for two reasons : 93 | - You must find the correct versions so all is compatible (hence `python 3.11`) 94 | - `conda-pack` doesn't like when `pip` and `conda` touch the same files, so you must find an installation order that avoid those case. The current installation order has been empirically tested to respect that. 95 | 96 | ## Auto updating 97 | Here is the `auto_update.py` script : 98 | dockerfile` here : 99 | ```{literalinclude} ../../deployment/exe/auto_update.py 100 | :language: python 101 | :linenos: 102 | ``` 103 | 104 | It detect the major version of CUDA with either `nvcc` or `nvidia-smi` and install the `x.8` version that should be retrocompatible with lower `x.y` version. If no CUDA is found, it keep `torch cpu`. It can be easily augmented to other drivers if Biom3d implement them. 105 | 106 | ## Other 107 | The executable script actually just launch the GUI meaning that all limitation of the GUI are kept, however it is possible to use the environment by using the python executable in `bin/` (eg: `bin/bin/python3.11 -m biom3d.pred ...` on macOS). -------------------------------------------------------------------------------- /docs/how_it_works/post_processing.md: -------------------------------------------------------------------------------- 1 | # Post processing 2 | Here we will explain how the post processing works in Biom3d, from the steps to transform the models output to a masks and other filters. 3 | 4 | ## Post processing function : 5 | The post processing function works in several steps : 6 | 1. **Return logit :** If a specific parameter is set to true, it will directly return the result of the model without converting it to a mask. This is not the default behaviour. 7 | 2. **Translation to mask :** The output is translated into a usable mask. For that there are 3 options : 8 | 1. The default is to use softmax. It is a mathematical function that will transform the different values of a voxel across the channels in probability and keep the highest. It is the default behaviour and is great when doing multiclass. 9 | 2. Another possibility is sigmoid. It is another mathematical function that will polarize the values of the voxel across the channel and then a thresold is applied (thresold value being 0.5). This function should be used for images with only one class (+ background). 10 | 3. The last possibility is to force softmax, in case where the model has been trained with sigmoid. We compute both sigmoid and softmax as shown above and we keep the softmax part where the sigmoid is at 1. 11 | 3. We have a mask and now there are 2 possibles filters to remove noise : 12 | 1. Keep big only : This filter compute the volume of the different segmented object and do a otsu thresolding to keep only the largest of them. 13 | 2. Keep biggest only : This filter keep only the biggest **centered** object, it is used in case where you know that you have 1 segmented object per image and that it is centered. 14 | If both filter are selected, you will have a warning and it will use Keep biggest only. 15 | 16 | The image is always resample to it's original shape before pre-preprocessing, if you decide to use the option `--skip-preprocessing`, you must assure that the image has `original_shape` in its metadata and is in `(C,D,H,W)` for 3D or `(C,1,H,W)` for 2D. 17 | 18 | ## Modifying the parameters : 19 | There are two way to modify the post procesing behaviour : by calling the function with specific parameter, that require to develop your own python script, or to modify the `config.yaml` file, if you want to do that before the training, you will have to . 20 | 21 | The default look of the post processing part in the config file is : 22 | ```yaml 23 | POSTPROCESSOR: 24 | fct: Seg 25 | kwargs: 26 | use_softmax: true 27 | keep_biggest_only: false 28 | keep_big_only: false 29 | ``` 30 | or 31 | ```python 32 | POSTPROCESSOR = Dict( 33 | fct="Seg", 34 | kwargs=Dict( 35 | use_softmax=USE_SOFTMAX, 36 | keep_biggest_only=False, 37 | keep_big_only=False, 38 | ), 39 | ) 40 | ``` 41 | Now let's see how to change it. When you modify the yaml, ensure that you follow the indentation : 42 | ```yaml 43 | POSTPROCESSOR: 44 | fct: Seg 45 | kwargs: 46 | use_softmax: true 47 | variable: value <- will work 48 | variable: value <- will crash 49 | keep_biggest_only: false 50 | keep_big_only: false 51 | ``` 52 | Also, there is no space before the `:`, but always one after. 53 | When you modify the python config, you need to to follow python syntaxe. 54 | 55 | If you have any doubt about how to write a value or on syntaxe, check how we do in our [documentation](../tuto/config.md), the default configs or on Python or yaml documentation. 56 | 57 | - The `fct` is a postprocessing function defined by [`register.py`](../api/register.rst). For the moment there is only one prediction function so there is no use to modify it. 58 | - If you want to skip postprocessing and get the raw output, you will need to add a new parameter : 59 | ```yaml 60 | POSTPROCESSOR: 61 | fct: Seg 62 | kwargs: 63 | use_softmax: true 64 | return_logit: true 65 | keep_biggest_only: false 66 | keep_big_only: false 67 | ``` 68 | or 69 | ```python 70 | POSTPROCESSOR = Dict( 71 | fct="Seg", 72 | kwargs=Dict( 73 | use_softmax=USE_SOFTMAX, 74 | return_logit=True, # Don't forget the comma 75 | keep_biggest_only=False, 76 | keep_big_only=False, 77 | ), 78 | ) 79 | ``` 80 | - To use sigmoid instead of softmax, set `use_softmax: false` (in yaml) or `USE_SOFTMAX=False` (in python, it should be around line 114) 81 | - If you trained on sigmoid and want to force softmax, do the same thing as above and add the parameter `force_softmax:true` : 82 | ```yaml 83 | POSTPROCESSOR: 84 | fct: Seg 85 | kwargs: 86 | use_softmax: false 87 | force_softmax: true 88 | keep_biggest_only: false 89 | keep_big_only: false 90 | ``` 91 | or 92 | ```python 93 | USE_SOFTMAX=False 94 | POSTPROCESSOR = Dict( 95 | fct="Seg", 96 | kwargs=Dict( 97 | use_softmax=USE_SOFTMAX, 98 | force_softmax=True, # Don't forget the comma 99 | keep_biggest_only=False, 100 | keep_big_only=False, 101 | ), 102 | ) 103 | ``` 104 | - If you want to use `keep_big_only` or `keep_biggest_only`, you don't necessarly need to modify the config files as it is an option in our GUI, however this option hasn't been integrated in our CLI yet. If you want to modify the config here is how to : 105 | ```yaml 106 | POSTPROCESSOR: 107 | fct: Seg 108 | kwargs: 109 | use_softmax: true 110 | keep_biggest_only: false # Replace False by True 111 | keep_big_only: false # Replace False by True 112 | ``` 113 | or 114 | ```python 115 | POSTPROCESSOR = Dict( 116 | fct="Seg", 117 | kwargs=Dict( 118 | use_softmax=USE_SOFTMAX, 119 | keep_biggest_only=False, # Replace False by True 120 | keep_big_only=False, # Replace False by True 121 | ), 122 | ) 123 | ``` -------------------------------------------------------------------------------- /bash/run_pred.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #SBATCH -o ./slurm/%j-train.out # STDOUT 3 | 4 | # python -m biom3d.pred\ 5 | # --name seg_eval\ 6 | # --log logs/20230531-092023-unet_lung\ 7 | # --dir_in data/msd/Task06_Lung/imagesTr_test\ 8 | # --dir_out data/msd/Task06_Lung/preds\ 9 | # --dir_lab data/msd/Task06_Lung/labelsTr_test 10 | 11 | # python -m biom3d.pred\ 12 | # --name seg_eval\ 13 | # --log logs/20230524-182512-unet_brain\ 14 | # --dir_in data/msd/Task01_BrainTumour/imagesTr_test\ 15 | # --dir_out data/msd/Task01_BrainTumour/preds\ 16 | # --dir_lab data/msd/Task01_BrainTumour/labelsTr_test 17 | 18 | # python -m biom3d.pred\ 19 | # --name seg\ 20 | # --log logs/20230427-170753-unet_default\ 21 | # --dir_in data/btcv/Testing_official/img\ 22 | # --dir_out data/btcv/Testing_official/preds 23 | 24 | # python -m biom3d.pred\ 25 | # --name seg_eval\ 26 | # --log logs/20230522-182916-unet_default logs/20230425-162133-unet_btcv\ 27 | # --dir_in data/btcv/Testing_small/img\ 28 | # --dir_out data/btcv/Testing_small/preds\ 29 | # --dir_lab data/btcv/Testing_small/label 30 | 31 | # python -m biom3d.pred\ 32 | # --name seg_eval_single\ 33 | # --log logs/20230514-182230-unet_lung\ 34 | # --dir_in data/msd/Task06_Lung/imagesTr_test/lung_051.nii.gz\ 35 | # --dir_out data/msd/Task06_Lung/preds/20230514-182230-unet_lung/lung_051.nii.gz\ 36 | # --dir_lab data/msd/Task06_Lung/labelsTr_test/lung_051.nii.gz 37 | 38 | # python -m biom3d.pred\ 39 | # --name seg\ 40 | # --log logs/20230518-092836-unet_chromo_48h24-48hL\ 41 | # --dir_in data/nucleus/to_pred/tiny_data\ 42 | # --dir_out data/nucleus/preds/tiny_data 43 | 44 | # python -m biom3d.pred\ 45 | # --name seg\ 46 | # --log logs/20231003-herve_cynthia\ 47 | # --dir_in data/herve/img\ 48 | # --dir_out data/herve/preds 49 | 50 | # python -m biom3d.pred\ 51 | # --name seg_eval\ 52 | # --log logs/20230908-202124-nucleus_official_fold4 logs/20230908-060422-nucleus_official_fold3 logs/20230907-155249-nucleus_official_fold2 logs/20230907-002954-nucleus_official_fold1 logs/20230906-101825-nucleus_official_fold0\ 53 | # --dir_in data/nucleus/official/test/img\ 54 | # --dir_out data/nucleus/official/test/preds\ 55 | # --dir_lab data/nucleus/official/test/msk 56 | 57 | # python -m biom3d.pred\ 58 | # --name seg_eval\ 59 | # --log logs/20240719-232122-exp21_supervised_baseline_pancreas_small_fold0\ 60 | # --dir_in data/pancreas/imagesTs_small\ 61 | # --dir_out data/pancreas/preds\ 62 | # --dir_lab data/pancreas/labelsTs_small 63 | 64 | # nucleus 65 | # python -m biom3d.pred\ 66 | # --name seg\ 67 | # --log logs/20230914-015946-chromo_dry_fold0 logs/20230830-120829-chromo_48h24hL_fold0 logs/20230831-211753-chromo_48h72hL_fold0\ 68 | # --dir_in data/nucleus/to_pred/raw_selection_24h_cot\ 69 | # --dir_out data/nucleus/preds/raw_selection_24h_cot 70 | 71 | # python -m biom3d.pred\ 72 | # --name seg\ 73 | # --log logs/20230914-015946-chromo_dry_fold0 logs/20230830-120829-chromo_48h24hL_fold0 logs/20230831-211753-chromo_48h72hL_fold0\ 74 | # --dir_in "data/nucleus/to_pred/raw_selection_48h_cot A-C-D-E"\ 75 | # --dir_out "data/nucleus/preds/raw_selection_48h_cot A-C-D-E" 76 | 77 | # python -m biom3d.pred\ 78 | # --name seg\ 79 | # --log logs/20230914-015946-chromo_dry_fold0 logs/20230830-120829-chromo_48h24hL_fold0 logs/20230831-211753-chromo_48h72hL_fold0\ 80 | # --dir_in data/nucleus/to_pred/raw_selection_48h24hL_cot\ 81 | # --dir_out data/nucleus/preds/raw_selection_48h24hL_cot 82 | 83 | # python -m biom3d.pred\ 84 | # --name seg\ 85 | # --log logs/20230914-015946-chromo_dry_fold0 logs/20230830-120829-chromo_48h24hL_fold0 logs/20230831-211753-chromo_48h72hL_fold0\ 86 | # --dir_in data/nucleus/to_pred/raw_selection_48h48hL_cot\ 87 | # --dir_out data/nucleus/preds/raw_selection_48h48hL_cot 88 | 89 | # python -m biom3d.pred\ 90 | # --name seg\ 91 | # --log logs/20230914-015946-chromo_dry_fold0 logs/20230830-120829-chromo_48h24hL_fold0 logs/20230831-211753-chromo_48h72hL_fold0\ 92 | # --dir_in "data/nucleus/to_pred/raw_selection_48h72hL_cot A-B-C"\ 93 | # --dir_out "data/nucleus/preds/raw_selection_48h72hL_cot A-B-C" 94 | 95 | # python -m biom3d.pred\ 96 | # --name seg\ 97 | # --log logs/20230914-015946-chromo_dry_fold0 logs/20230830-120829-chromo_48h24hL_fold0 logs/20230831-211753-chromo_48h72hL_fold0\ 98 | # --dir_in "data/nucleus/to_pred/raw_selection_dry_cot A-C-D-E"\ 99 | # --dir_out "data/nucleus/preds/raw_selection_dry_cot A-C-D-E" 100 | 101 | # droso-herve 102 | # python -m biom3d.pred\ 103 | # --name seg\ 104 | # --log logs/20231128-104500-cynthia\ 105 | # --dir_in "data/herve/cynthia"\ 106 | # --dir_out "data/herve/preds" 107 | 108 | # nucleus aline bug fix 109 | # python -m biom3d.pred\ 110 | # --name seg\ 111 | # --log logs/20231127-165609-nucleus_official_fold0_fine-tuned_for_ubp5_fold0\ 112 | # --dir_in "data/nucleus/aline_bug/img"\ 113 | # --dir_out "data/nucleus/aline_bug/preds" 114 | 115 | # python -m biom3d.pred\ 116 | # --name seg\ 117 | # --log logs/20240219-100225-reims_full_fold0\ 118 | # --dir_in data/reims/big_stack/img\ 119 | # --dir_out data/reims/big_stack/preds\ 120 | 121 | # python -m biom3d.pred\ 122 | # --name seg_eval\ 123 | # --log logs/20240319-094430-reims_large_full_no47_fold0\ 124 | # --dir_in data/reims/large/test/img\ 125 | # --dir_out data/reims/large/test/preds\ 126 | # --dir_lab data/reims/large/test/msk 127 | 128 | # python -m biom3d.pred\ 129 | # --name seg_eval\ 130 | # --log logs/20230501-153638-unet_default\ 131 | # --dir_in data/mito/test/img\ 132 | # --dir_out data/mito/test/pred\ 133 | # --dir_lab data/mito/test/msk 134 | 135 | # python -m biom3d.pred\ 136 | # --name seg_single\ 137 | # --log logs/20240218-072550-reims_fold0\ 138 | # --dir_in data/reims/big_stack/img/1024.tif\ 139 | # --dir_out data/reims/big_stack/preds/1024.tif 140 | 141 | python -m biom3d.pred\ 142 | --name seg_eval\ 143 | --log logs/20250908-105724-unet_lung_fold0\ 144 | --dir_in data/msd/Task06_Lung/imagesTr_test\ 145 | --dir_out data/msd/Task06_Lung/preds\ 146 | --dir_lab data/msd/Task06_Lung/labelsTr_test 147 | -------------------------------------------------------------------------------- /tests/test_preprocess.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | from biom3d.preprocess import correct_mask 4 | 5 | class TestCorrectMask: 6 | """Groups all tests for the correct_mask function.""" 7 | 8 | # --- 1. Tests for Valid Masks (No Correction Needed) --- 9 | 10 | def test_valid_3d_label_mask(self): 11 | """Tests a perfect 3D label mask, which should not be changed.""" 12 | mask = np.array([[[0, 1], [2, 0]]], dtype=np.uint8) 13 | processed_mask = correct_mask(mask, num_classes=3) 14 | np.testing.assert_array_equal(processed_mask, mask) 15 | 16 | def test_valid_2d_label_mask(self): 17 | """Tests a perfect 2D label mask with is_2d=True.""" 18 | mask = np.array([[0, 1], [2, 0]], dtype=np.uint8) 19 | processed_mask = correct_mask(mask, num_classes=3, is_2d=True) 20 | assert processed_mask.ndim == 2 21 | np.testing.assert_array_equal(processed_mask, mask) 22 | 23 | def test_valid_4d_binary_mask(self): 24 | """Tests a perfect 4D binary mask, which should not be changed.""" 25 | mask = np.zeros((2, 1, 4, 4), dtype=np.uint8) 26 | mask[0, 0, :2, :2] = 1 27 | processed_mask = correct_mask(mask, num_classes=2, encoding_type='binary') 28 | np.testing.assert_array_equal(processed_mask, mask) 29 | 30 | def test_valid_3d_onehot_mask_2d_data(self): 31 | """Tests a perfect one-hot mask for 2D data (shape C,H,W).""" 32 | mask = np.array([ 33 | [[1, 0], [0, 0]], # Class 0 34 | [[0, 1], [0, 1]], # Class 1 35 | [[0, 0], [1, 0]], # Class 2 36 | ], dtype=np.uint8) 37 | processed_mask = correct_mask(mask, num_classes=3, is_2d=True, keep_original_dims=True, encoding_type='onehot') 38 | assert processed_mask.ndim == 3 39 | np.testing.assert_array_equal(processed_mask, mask) 40 | 41 | # --- 2. Tests for Correction Logic --- 42 | 43 | def test_correction_remap_3d_label(self): 44 | """Tests remapping of non-contiguous labels in a 3D mask.""" 45 | mask = np.array([[[10, 20], [30, 10]]], dtype=np.uint8) 46 | expected = np.array([[[0, 1], [2, 0]]], dtype=np.uint8) 47 | processed_mask = correct_mask(mask, num_classes=3) 48 | np.testing.assert_array_equal(processed_mask, expected) 49 | 50 | def test_correction_remap_2d_label(self): 51 | """Tests remapping of non-contiguous labels in a 2D mask.""" 52 | mask = np.array([[10, 20], [30, 10]], dtype=np.uint8) 53 | expected = np.array([[0, 1], [2, 0]], dtype=np.uint8) 54 | processed_mask = correct_mask(mask, num_classes=3, is_2d=True) 55 | np.testing.assert_array_equal(processed_mask, expected) 56 | 57 | def test_correction_binary_heuristic(self): 58 | """Tests binary correction where the majority value becomes background (0).""" 59 | # 50 is the most frequent value, so it should become 0. 100 becomes 1. 60 | mask = np.array([50, 50, 50, 100], dtype=np.uint8).reshape(1, 2, 2) 61 | expected = np.array([0, 0, 0, 1], dtype=np.uint8).reshape(1, 2, 2) 62 | processed_mask = correct_mask(mask, num_classes=2) 63 | np.testing.assert_array_equal(processed_mask, expected) 64 | 65 | def test_correction_4d_binary_per_channel(self): 66 | """Tests that correction is applied independently to each channel of a binary mask.""" 67 | mask = np.zeros((2, 1, 2, 2), dtype=np.uint8) 68 | mask[0, 0, 0, 0] = 1 # Channel 0 is already valid: [0, 1] 69 | mask[1, 0, :, :] = 50 # Channel 1 is invalid: [0, 50] 70 | mask[1, 0, 0, 0] = 0 # Make 50 the majority in channel 1 71 | 72 | expected = np.zeros_like(mask) 73 | expected[0, 0, 0, 0] = 1 # Channel 0 is unchanged 74 | expected[1, 0, :, :] = 0 # Channel 1: 50 becomes 0 75 | expected[1, 0, 0, 0] = 1 # Channel 1: 0 becomes 1 76 | 77 | processed_mask = correct_mask(mask, num_classes=2, encoding_type='binary') 78 | np.testing.assert_array_equal(processed_mask, expected) 79 | 80 | # --- 3. Tests for Error Handling and Overrides --- 81 | 82 | def test_auto_correct_false_raises_error(self): 83 | """Ensures an invalid mask raises an error when auto_correct is False.""" 84 | invalid_mask = np.array([10, 20, 30], dtype=np.uint8).reshape(1, 1, 3) 85 | with pytest.raises(RuntimeError, match="Mask is invalid and auto_correct is False."): 86 | correct_mask(invalid_mask, num_classes=3, auto_correct=False) 87 | 88 | def test_ambiguous_label_correction_raises_error(self): 89 | """Tests that an error is raised for uncorrectable label ambiguities.""" 90 | # 4 unique labels, but user expects 3 classes. This is ambiguous. 91 | ambiguous_mask = np.array([10, 20, 30, 40], dtype=np.uint8).reshape(1, 2, 2) 92 | with pytest.raises(RuntimeError): 93 | correct_mask(ambiguous_mask, num_classes=3) 94 | 95 | def test_invalid_onehot_raises_error(self): 96 | """Tests that a broken one-hot mask raises an error, as correction is unsupported.""" 97 | # This mask is not one-hot because the sum at [0,0] is 2. 98 | broken_onehot = np.array([ 99 | [[1, 0], [0, 0]], 100 | [[1, 1], [0, 1]], # Invalid value here 101 | [[0, 0], [1, 0]], 102 | ], dtype=np.uint8) 103 | with pytest.raises(RuntimeError): 104 | correct_mask(broken_onehot, num_classes=3, is_2d=True, keep_original_dims=True, encoding_type='onehot') 105 | 106 | def test_invalid_num_classes_raises_error(self): 107 | """Tests that num_classes < 2 raises an error.""" 108 | mask = np.zeros((2, 2), dtype=np.uint8) 109 | with pytest.raises(RuntimeError, match="num_classes must be an integer >= 2"): 110 | correct_mask(mask, num_classes=1, is_2d=True) 111 | 112 | def test_is_2d_with_wrong_ndim_raises_error(self): 113 | """Tests that using is_2d=True with an unsupported ndim (e.g., 4D) fails.""" 114 | mask = np.zeros((1, 1, 1, 1), dtype=np.uint8) 115 | with pytest.raises(RuntimeError, match="expected mask.ndim to be 2 or 3"): 116 | correct_mask(mask, num_classes=2, is_2d=True) -------------------------------------------------------------------------------- /src/biom3d/utils/fold.py: -------------------------------------------------------------------------------- 1 | """This submodule provides functions to split, save and load folds.""" 2 | 3 | import numpy as np 4 | from pandas import DataFrame 5 | 6 | #TODO use verbose 7 | def get_train_test_df(df:DataFrame, verbose:bool=True)->tuple[np.ndarray,np.ndarray]: 8 | """ 9 | Extract train and test sets from a DataFrame based on the 'hold_out' column. 10 | 11 | Parameters 12 | ---------- 13 | df: pandas.DataFrame 14 | The dataset containing a 'hold_out' column with 0 (train) and 1 (test) labels. 15 | verbose: bool, default=True 16 | If True, enables debug printing (currently unused). 17 | 18 | Returns 19 | ------- 20 | train_set : numpy.ndarray 21 | Array of training filenames (or sample IDs). 22 | test_set : numpy.ndarray 23 | Array of test filenames (or sample IDs). 24 | """ 25 | train_set = np.array(df[df['hold_out']==0].iloc[:,0]) 26 | test_set = np.array(df[df['hold_out']==1].iloc[:,0]) 27 | return train_set, test_set 28 | 29 | def get_folds_df(df:DataFrame, verbose:bool=True)->list[list[str]]: 30 | """ 31 | Extract folds from a DataFrame into a list of lists. 32 | 33 | Parameters 34 | ---------- 35 | df: pandas.DataFrame 36 | DataFrame with a 'fold' column indicating fold assignment. 37 | verbose: bool, default=True 38 | If True, prints the number and size of the folds. 39 | 40 | Returns 41 | ------- 42 | list of list 43 | List of folds, each being a list of filenames (or sample IDs). 44 | """ 45 | folds = [] 46 | if df.empty: 47 | print("[Warning] one of the data DataFrame is empty!") 48 | return [] 49 | nbof_folds = df['fold'].max()+1 50 | if verbose: 51 | print("Number of folds in df: {}".format(nbof_folds)) 52 | 53 | size_folds = [] 54 | for i in range(nbof_folds): 55 | folds += [list(df[df['fold']==i].iloc[:,0])] 56 | size_folds += [len(folds[-1])] 57 | if verbose: 58 | print("Size of folds: {}".format(size_folds)) 59 | return folds 60 | 61 | def get_folds_train_test_df(df:DataFrame, 62 | verbose:bool=True, 63 | merge_test:bool=True, 64 | )->tuple[list[list[str]],list[list[str]]|list[str]]: 65 | """ 66 | Extract fold groups from both train and test sets. 67 | 68 | Parameters 69 | ---------- 70 | df: pandas.DataFrame 71 | DataFrame with 'hold_out' and 'fold' columns. 72 | verbose: bool, default=True 73 | If True, prints debug info. 74 | merge_test: bool, default=True 75 | If True, test folds are merged into one list. 76 | 77 | Returns 78 | ------- 79 | train_folds: list of list 80 | List of training folds, each being a list of filenames. 81 | test_folds: list or list of list 82 | Test set either as a merged list or as a list of folds. 83 | """ 84 | if verbose: 85 | print("Training set:") 86 | train_folds = get_folds_df(df[df['hold_out']==0], verbose) 87 | 88 | if verbose: 89 | print("Testing set:") 90 | test_folds = get_folds_df(df[df['hold_out']==1], verbose) 91 | 92 | if merge_test: 93 | test_folds_merged = [] 94 | for i in range(len(test_folds)): 95 | test_folds_merged += test_folds[i] 96 | test_folds = test_folds_merged 97 | return train_folds, test_folds 98 | 99 | def get_splits_train_val_test(df:DataFrame)->tuple[list[list[str]],list[str],list[str]]: 100 | """ 101 | Create dataset splits of different sizes, along with validation and test sets. 102 | 103 | Assumes columns: 104 | - 'split': indicates split index (e.g., 0=50%, 1=25%, etc.) 105 | - 'fold': used to separate training and validation 106 | - 'hold_out': 0=train/val, 1=test 107 | - 'filename': sample identifier 108 | 109 | The splits contains [100%,50%,25%,10%,5%,2%,the rest] of the dataset 110 | 111 | Returns 112 | ------- 113 | train_splits: list of list 114 | List of training splits (first is the full training set, followed by reduced ones). 115 | valset: list 116 | List of filenames used for validation. 117 | testset: list 118 | List of filenames used for testing. 119 | """ 120 | nbof_splits = df['split'].max()+1 121 | valset = list(df[(df['split']==-1)*(df['fold']==0)*(df['hold_out']==0)]['filename']) 122 | testset = list(df[(df['hold_out']==1)]['filename']) 123 | train_splits = [] 124 | for i in range(nbof_splits): 125 | train_splits += [list(df[(df['split']==i)*(df['fold']!=0)*(df['hold_out']==0)].iloc[:,0])] 126 | # adds the whole dataset in the begging of the train_splits list 127 | train_splits = [list(df[(df['fold']!=0)*(df['hold_out']==0)].iloc[:,0])] + train_splits 128 | return train_splits, valset, testset 129 | 130 | def get_splits_train_val_test_overlapping(df:DataFrame)->tuple[list[list[str]],list[str],list[str]]: 131 | """ 132 | Create overlapping training splits plus validation and test sets. 133 | 134 | Each smaller training subset is fully included in all larger ones. 135 | Used for dataset scaling experiments (e.g., 100%, 50%, 25%, etc.). 136 | 137 | Parameters 138 | ---------- 139 | df: pandas.DataFrame 140 | DataFrame with 'split', 'fold', 'hold_out', and 'filename' columns. 141 | 142 | Returns 143 | ------- 144 | train_splits: list of list 145 | List of overlapping training subsets. 146 | valset: list 147 | List of filenames used for validation. 148 | testset: list 149 | List of filenames used for testing. 150 | 151 | Notes 152 | ----- 153 | Only works if the splits follow descending powers of two. 154 | """ 155 | nbof_splits = df['split'].max()+1 156 | valset = list(df[(df['split']==-1)*(df['fold']==0)*(df['hold_out']==0)]['filename']) 157 | testset = list(df[(df['hold_out']==1)]['filename']) 158 | train_splits = [] 159 | for i in range(nbof_splits): 160 | train_splits += [list(df[(df['split']>=i)*(df['fold']!=0)*(df['hold_out']==0)].iloc[:,0])] 161 | 162 | # adds the last set 163 | train_splits += [list(df[(df['split']==(nbof_splits-1))*(df['fold']!=0)*(df['hold_out']==0)].iloc[:,0])] 164 | return train_splits, valset, testset 165 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently asked questions 2 | 3 | ## "FileNotFoundError: [Errno 2] No such file or directory" or "FileNotFoundError: [Errno 2] No such file or directory" 4 | 5 | Usually appears when image files in image folder and mask files in mask folder do not have the **exact** same name and extension. Please make sur that both your images and masks have all the same names and extensions. 6 | 7 | ## OMERO 8 | When using OMERO prediction, you can ecounter two issues : 9 | ### recv() returned zero 10 | You may encounter this : 11 | ``` 12 | Traceback (most recent call last): 13 | File "/usr/lib/python3.11/tkinter/__init__.py", line 1948, in __call__ 14 | return self.func(*args) 15 | ^^^^^^^^^^^^^^^^ 16 | File "/usr/local/lib/python3.11/dist-packages/biom3d/gui.py", line 1747, in predict 17 | biom3d.omero_uploader.run(username=self.send_to_omero_connection.username.get(), 18 | File "/usr/local/lib/python3.11/dist-packages/biom3d/omero_uploader.py", line 179, in run 19 | if project and not conn.getObject('Project', project): 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 21 | File "/usr/local/lib/python3.11/dist-packages/omero/gateway/__init__.py", line 3300, in getObject 22 | result = self.getQueryService().findByQuery( 23 | ^^^^^^^^^^^^^^^^^^^^^^ 24 | File "/usr/local/lib/python3.11/dist-packages/omero/gateway/__init__.py", line 2596, in getQueryService 25 | return self._proxies['query'] 26 | ~~~~~~~~~~~~~^^^^^^^^^ 27 | File "/usr/local/lib/python3.11/dist-packages/omero/gateway/__init__.py", line 1489, in __getitem__ 28 | raise Ice.ConnectionLostException 29 | Ice.ConnectionLostException: Ice.ConnectionLostException: 30 | recv() returned zero 31 | ``` 32 | It means OMERO connection fail, it can be caused by : 33 | * Wrong username/password 34 | * Just a simple connection error 35 | 36 | The simpliest way to solve it is by reentering your credentials and try again. If it persist it may be a connection with the server error, if you can't acces your OMERO server with other means (Webclient, insight,...), contact your IT support. 37 | 38 | ### Uploading 39 | OMERO uploading can be quite long. For the moment, there isn't much feedbacks to indicate that upload is finished (we are working on it). On GUI, the `Start` button is white while it is running and become red when it is finished. Also, in the terminal, you see something like this : 40 | ``` 41 | Importing: Test_dataset_predictions/Unit/2.tif 42 | Uploading: Test_dataset_predictions/Unit/2.tif 43 | Hashes: 44 | 80d2a040ed8058338ea0c162033b57d8703ba4fd 45 | 46 | Imported Image ID: 301018 47 | ``` 48 | each time an image is uploaded, you can keep track of uploading with that. However there are cases where nothing is shown in terminal and it is still uploaded. If you are not using the GUI (and can't see the button feedback), we recommand checking in real time the destination folder in OMERO. 49 | 50 | ## Installer not detecting CUDA card 51 | We encountered some cases where the installer doesn't detect the CUDA drivers on first launch, hence not updating its torch version to use CUDA. We are currently investingating this. Here is how to manually update the torch verion of the installer: 52 | 1. You need to know which CUDA version your GPU is compatible for that use the command `nvidia-smi`: 53 | ```text 54 | > nvidia-smi 55 | Thu Jul 17 11:43:05 2025 56 | +-----------------------------------------------------------------------------+ 57 | | NVIDIA-SMI 457.51 Driver Version: 457.51 CUDA Version: 11.1 | 58 | |-------------------------------+----------------------+----------------------+ 59 | | GPU Name TCC/WDDM | Bus-Id Disp.A | Volatile Uncorr. ECC | 60 | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | 61 | | | | MIG M. | 62 | |===============================+======================+======================| 63 | | 0 GeForce GTX 106... WDDM | 00000000:01:00.0 On | N/A | 64 | | 43% 39C P2 22W / 120W | 1054MiB / 6144MiB | 3% Default | 65 | | | | N/A | 66 | +-------------------------------+----------------------+----------------------+ 67 | ``` 68 | 2. Then you need to know with which version of torch the installer was created: 69 | 1. Navigate to the installer folder. 70 | 2. In the address bar, write `cmd` and press enter, it will open a terminal. 71 | 3. Paste this command `bin\python.exe -c "import torch; print(torch.__version__)"` and enter, it will give something like `2.9.0+cpu` 72 | 4. Keep the terminal open 73 | 3. Navigate to the PyTorch website, the last version of pytorch can be found [here](https://pytorch.org/get-started/locally/) in PyTorch Build "Stable", if the installer use an older version go [here](https://pytorch.org/get-started/previous-versions/). 74 | 4. Select the version correspondig to your OS and CUDA version 75 | 5. In the previously open terminal write `bin\python.exe -m `, you can remove the torchvision and torchaudio part that are not used in Biom3d. The update will take some time, once it is finished you can close the terminal. 76 | 77 | ## NaN loss 78 | 79 | Can be caused by: 80 | * Not up to data CUDA/CuDNN version (see: https://discuss.pytorch.org/t/half-precision-convolution-cause-nan-in-forward-pass/117358/4). 81 | * Half-precision. Try to set the USE_FP16 parameter to False in the config file. 82 | 83 | ## ValueError : Images don't have the same number of dimensions : 84 | 85 | Happen when images you want to train or predict on don't all have the same dimensions. Can be cause by opening the image with Napari and transfering it to Fiji. You can either reimport the raw images or remove the problematics ones. 86 | 87 | ## [Error] Invalid image shape (x,_, _, _). Expected to have 1 numbers of channel at 0 channel axis. 88 | Can be caused by: 89 | * One of your image hasn't the number of channel the model has been trained on, you can fix it by removing problematic image. 90 | * The model didn't registered the number of channel it work with. If you are sure the problem isn't from the dataset (see above) you can add to the config.yaml (in log) the line "num_channels: x" to the kwarg part of the PREPROCESSOR part : 91 | ```yaml 92 | PREPROCESSOR: 93 | fct: Seg 94 | kwargs: 95 | median_spacing: *id004 96 | clipping_bounds: *id005 97 | intensity_moments: *id006 98 | channel_axis: 0 99 | num_channels: 1 <- (for example) 100 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | biom3d 3 | 4 | [📘Documentation](https://biom3d.readthedocs.io/) | 5 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/GuillaumeMougeot/biom3d/blob/master/docs/biom3d_colab.ipynb) 6 |
7 | nucleus 8 | 9 | 10 | 11 | 12 | 13 | ## Highlights 14 | 15 | Biom3d automatically configures the training of a 3D U-Net for 3D semantic segmentation. 16 | 17 | The default configuration matches the performance of [nnUNet](https://github.com/MIC-DKFZ/nnUNet) but is much easier to use both for community users and developers. Biom3d is flexible for developers: easy to understand and easy to edit. 18 | 19 | Code architecture of Biom3d versus code architecture of nnU-Net: 20 | 21 | Biom3d modules | nnUNet modules 22 | :-------------------------:|:-------------------------: 23 | ![](https://github.com/GuillaumeMougeot/biom3d/blob/main/images/biom3d_train.png) | ![](https://github.com/GuillaumeMougeot/biom3d/blob/main/images/nnunet_run_run_training.png) 24 | 25 | *Illustrations generated with `pydeps` module* 26 | 27 | > **Disclaimer**: Biom3d does not include the possibility to use 2D U-Net or 3D-Cascade U-Net or Pytorch distributed parallel computing (only Pytorch Data Parallel) yet. However, these options could easily be adapted if needed. 28 | 29 | We target two main types of users: 30 | 31 | * Community users, who are interested in using the basic functionalities of Biom3d: GUI or CLI, predictions with ready-to-use models or default training. 32 | * Deep-learning developers, who are interested in more advanced features: changing default configuration, writing of new Biom3d modules, Biom3d core editing etc. 33 | 34 | Biom3d tutorials are now available online: 35 | 36 | * [I2K Workshop tutorial (in english)](https://www.youtube.com/watch?v=cRUb9g66P18&ab_channel=I2KConference) 37 | * [RTMFM tutorial (in french)](https://www.youtube.com/live/fJopxW5vOhc?si=qdpJcaEy0Bd2GDec) 38 | 39 | And a DEMO dataset can be downloaded [here](https://github.com/GuillaumeMougeot/biom3d/releases/tag/v0.1.1). 40 | 41 | ## 🔨 Installation 42 | 43 | **For the installation details, please check our documentation here:** [**Installation**](https://biom3d.readthedocs.io/en/latest/installation.html) 44 | 45 | TL;DR: here is a single line of code to install biom3d: 46 | 47 | ``` 48 | pip install torch biom3d 49 | ``` 50 | 51 | ## ✋ Usage 52 | 53 | **For Graphical User Interface users, please check our documentation here:** [**GUI**](https://biom3d.readthedocs.io/en/latest/tuto/gui.html) 54 | 55 | **For Command Line Interface users, please check our documentation here:** [**CLI**](https://biom3d.readthedocs.io/en/latest/tuto/cli.html) 56 | 57 | **For Deep Learning developers, the tutorials are currently being cooked stayed tuned! You can check the partial API documentation already:** [**API**](https://biom3d.readthedocs.io/en/latest/api/api.html) 58 | 59 | TL;DR: here is a single line of code to run biom3d on the [BTCV challenge](https://www.synapse.org/#!Synapse:syn3193805/wiki/217785) and reach the same performance as nnU-Net (no cross-validation yet): 60 | 61 | ``` 62 | python -m biom3d.preprocess_train\ 63 | --img_dir data/btcv/Training/img\ 64 | --msk_dir data/btcv/Training/label\ 65 | --num_classes 13\ 66 | --ct_norm 67 | ``` 68 | 69 | ## DEMO Dataset 70 | 71 | A DEMO dataset of 3D can be downloaded [here](https://github.com/GuillaumeMougeot/biom3d/releases/tag/v0.1.1). This dataset can be helpful to interact with the Google Colab interface for the first time. 72 | 73 | ## ⚠ Disclaimer 74 | 75 | > **Warning**: This repository is still a work in progress and comes with no guarantees. 76 | 77 | ## Issues 78 | 79 | Please feel free to open an issue or send me an email if any problem with biom3d appears. But please make sure first that this problem is not referenced on the FAQ page: [Frequently Asked Question](https://biom3d.readthedocs.io/en/latest/faq.html) 80 | 81 | ## 📑 Citation 82 | 83 | If you find Biom3d useful in your research, please cite: 84 | 85 | ``` 86 | @misc{biom3d, 87 | title={{Biom3d} Easy-to-use Tool for 3D Semantic Segmentation of Volumetric Images using Deep Learning}, 88 | author={Guillaume Mougeot}, 89 | howpublished = {\url{https://github.com/GuillaumeMougeot/biom3d}}, 90 | year={2023} 91 | } 92 | 93 | @article {Mougeot2024.07.25.604800, 94 | author = {Mougeot, Guillaume and Safarbati, Sami and Al{\'e}got, Herv{\'e} and Pouchin, Pierre and Field, Nadine and Almagro, S{\'e}bastien and Pery, {\'E}milie and Probst, Aline and Tatout, Christophe and Evans, David E. and Graumann, Katja and Chausse, Fr{\'e}d{\'e}ric and Desset, Sophie}, 95 | title = {Biom3d, a modular framework to host and develop 3D segmentation methods}, 96 | elocation-id = {2024.07.25.604800}, 97 | year = {2024}, 98 | doi = {10.1101/2024.07.25.604800}, 99 | publisher = {Cold Spring Harbor Laboratory}, 100 | URL = {https://www.biorxiv.org/content/early/2024/10/28/2024.07.25.604800}, 101 | eprint = {https://www.biorxiv.org/content/early/2024/10/28/2024.07.25.604800.full.pdf}, 102 | journal = {bioRxiv} 103 | } 104 | ``` 105 | 106 | ## 💰 Fundings and Acknowledgements 107 | 108 | This project has been inspired by the following publication: "nnU-Net: a self-configuring method for deep learning-based biomedical image segmentation", Fabian Isensee et al, Nature Method, 2021. 109 | 110 | This project has been supported by Oxford Brookes University and the European Regional Development Fund (FEDER). It was carried out between the laboratories of iGReD (France), Institut Pascal (France) and Plant Nuclear Envelop (UK). 111 | 112 |

113 | Europe 114 | Brookes 115 | iGReD 116 | IP 117 | AURA 118 | UCA 119 |

120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /docs/dep/cicd.md: -------------------------------------------------------------------------------- 1 | # Continuous Integration / Deployment 2 | Biom3d now has a continuous deployment pipeline that activate when you push a tag. 3 | 4 | ## How to trigger it 5 | It trigger when a tag is pushed, typically : 6 | ```bash 7 | git tag vx.y.z 8 | git push origin tag vx.y.z 9 | ``` 10 | The tag name must be in the specific format above, starting with a 'v' and with [semantic versionning](https://semver.org/) (optional but better). 11 | You can also remove a tag by doing : 12 | ```bash 13 | git tag - vx.y.z # Remove local tag 14 | git push origin --delete vx.y.z # Remove remote tag 15 | ``` 16 | So if the CI/CD crash or you make a mistake, you can safely delete the tag and create a new one. 17 | 18 | ## Working 19 | Here is the script : 20 | ```{literalinclude} ../../.github/workflows/build.yml 21 | :language: yaml 22 | :linenos: 23 | ``` 24 | 25 | The pipeline is composed of 5 jobs : 26 | - Retrieve Docker information from a JSON file 27 | - Create and push Docker images 28 | - Building on macOS 29 | - Building on Windows 30 | - Create a GitHub release 31 | 32 | ### Retrieving Docker info 33 | Reading Json files is natively supported in GitHub Actions, so we can just read it and store it in a GitHub variable. A YAML format was considered to make the file more editable, but it would have introduced unnecessary complexity. 34 | 35 | ### Creating Docker Images 36 | It is run on `ubuntu-latest` as it has Docker pre-installed. We use the JSON parsed by the [previous step](#retrieving-docker-info) to determine build argument and other parameters. 37 | 38 | Here is the list of supported keys: 39 | - `architecture`: Processor architecture 40 | - `torch_version`: PyTorch version to install and use. If using a PyTorch base image, it should match this version. It is used in the name of the image. 41 | - `base_image` : Base Docker Image used to build from. 42 | - `python_version` : Python version used in the image (`3.11` is recommended) 43 | - `omero_version` : OMERO version used in the image (`5.21.0` is recommended) 44 | - `target` : Must be either `"cpu"` or `"gpu"`, it is mandatory only if `"cpu"` value to determine the name and prevent creating a symlink for non existing GPU library. 45 | - `cuda_version` : CUDA version to use. If using a PyTorch base image, it should match this version. It is used in the name of the image. Don't use if `target:"cpu"`. 46 | - `cudnn_version` : cuDNN version to use. If using a PyTorch base image, it should match this version. It is used in the name of the image. Don't use if `target:"cpu"`. 47 | - `tested` : Either `1` (image has been fully tested and validated) or `0` (not tested, or only partially). 48 | 49 | Here is an example Json : 50 | ```json 51 | { 52 | "configs": [ 53 | { 54 | "architecture": "x86_64", 55 | "torch_version": "2.7.1", 56 | "base_image": "ubuntu:22.04", 57 | "python_version": "3.11", 58 | "omero_version": "5.21.0", 59 | "target": "cpu", 60 | "tested":1 61 | }, 62 | { 63 | "architecture": "x86_64", 64 | "torch_version": "2.3.1", 65 | "cuda_version": "11.8", 66 | "cudnn_version": "8", 67 | "base_image": "pytorch/pytorch:2.3.1-cuda11.8-cudnn8-runtime", 68 | "python_version": "3.11", 69 | "omero_version": "5.21.0", 70 | "tested":1 71 | }] 72 | } 73 | ``` 74 | 75 | As shown above, the file consists of a "configs" array, where each object defines one image to build using the parameters described. 76 | 77 | Each image defined in the JSON file is built the pushed on **DockerHub** using `DOCKER_USERNAME` and `DOCKER_PASSWORD` (or more precisely access toker) stored in GitHub Secrets. 78 | 79 | For security reason, we recommend : 80 | - Restricting access to those variable to trusted collaborators 81 | - Change the access token on a regular basis. 82 | 83 | ### Building 84 | Here we will describe the differents steps of building. The overall logic the same accross plateforms, but syntaxe vary depending on the OS. We will go into syntax details only where necessary. 85 | 86 | Keep in mind that the build is done in the `GITHUB_WORKSPACE`, that is a the root of the GitHub repository. 87 | 88 | **Steps** 89 | - **Versioning on macOS :** 90 | macOS has a additional step, it is to retrieve the release version and modify the `CFBundleVersion` in `Info.plist`, so the application metadata reflects the correct version. 91 | - **Conda installation :** 92 | We install Conda and initialize it. 93 | On Windows we use `Invoke-WebRequest` instead of `curl` because : 94 | - In PowerShell, `curl` is an alias of `Invoke-WebRequest`. 95 | - Using a Bash terminal to run `curl` causes permission issues. 96 | We add Conda to the path, on MacOS, we also add it to the `GITHUB_ENV` variable so it is correctly transfered between steps. 97 | - **Architecture detection :** 98 | We retrive the runner architecture and store it in a `GITHUB_OUTPUT` variable. 99 | On Windows, we added a switch that replace `"AMD64"` with `"x86_64"` to avoid ambiguity. 100 | - **Packaging :** 101 | We move to `deployment/exe/os` directory and use the packing script described in the [installer documentation](installer.md). 102 | On macOS we have to reactivate Conda. 103 | - **Setting up remote :** 104 | Once the installer is created, we prepare the remote version by switching the `REMOTE=False` to `REMOTE=True` in `gui.py`. 105 | This is intended to let `pyinstaller` statically determine which imports are required (although it doesn't fully work), but more importantly, it hides the **"Start locally"** option in the GUI. 106 | - **Creating a minimal pyvenv :** 107 | Instead of using the existing Conda environment, we create a minimal `venv`. 108 | This significantly reduces the final build size — from around **200MB to 15MB**. 109 | We then use `pyinstaller` to build `gui.py`. 110 | Note: For unknown reasons, `pyinstaller` places the executable **outside** the `GITHUB_WORKSPACE`, so we manually copy it to the root for access in later steps. 111 | - **Creating the build artifact :** 112 | We create a folder containing both the installer and the remote executable, and place it at the repository root (it simplify the path for future use). This folder is then exported as a GitHub artifact. 113 | 114 | This concludes the build process. 115 | 116 | ### Release 117 | This step is straightforward and only runs after all other jobs have completed successfully. 118 | - It will download MacOS and Windows artifacts. 119 | - It extracts the corresponding changelog section from `CHANGELOG.md`, based on the pushed tag. 120 | > Important: `CHANGELOG.md` **must include a section** matching the pushed tag, for example: 121 | > `## [v1.0.0] - 2025-July-9` 122 | If this part fail, it will create a release with the following body `Version $VERSION not found in CHANGELOG.md`. 123 | - The CI/CD contain a step to zip the source code however it was commented as GitHub automatically does it. 124 | - It create a release associated to the tag, it includes : 125 | - The changelog as body. 126 | - The two installers. 127 | - The two remote executable. 128 | - Source code archived (in `.zip` and `.tar.gz`). -------------------------------------------------------------------------------- /docs/api/register.rst: -------------------------------------------------------------------------------- 1 | Register 2 | ======== 3 | 4 | The register is a core module of Biom3d as it define what function can be used for every module. 5 | 6 | .. note:: 7 | 8 | If you implement a new function/module, you may want to integrate it directly in Biom3d by adding it to the register. If so, you can add it's documentation here and submit a pull request. 9 | 10 | .. literalinclude:: ../../src/biom3d/register.py 11 | :language: python 12 | :linenos: 13 | 14 | Now let's delve on each modules. 15 | 16 | Dataloader and batchgenerator 17 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | 19 | There are currently 2 dataloader and 1 batchgenerator. 20 | 21 | .. currentmodule:: biom3d.datasets.semseg_patch_fast 22 | 23 | SegPatchFast 24 | ------------ 25 | 26 | This is the default dataloading module. 27 | 28 | .. autoclass:: SemSeg3DPatchFast 29 | :no-index: 30 | 31 | .. currentmodule:: biom3d.datasets.semseg_torchio 32 | 33 | Torchio 34 | ------- 35 | 36 | This dataloader is an implementation of torchio subjectloader, optimized to do operation with torchio. It was written to ease data augmentation but is not used. 37 | 38 | .. autoclass:: TorchioDataset 39 | :no-index: 40 | 41 | .. currentmodule:: biom3d.datasets.semseg_batchgen 42 | 43 | BatchGen 44 | -------- 45 | 46 | This is nnUnet batchgenerator. It has been slightly modified to act like a dataloader to be easier to interchange with the others. 47 | 48 | .. note:: 49 | 50 | If you want to use it, you have to modify the config file in a different way: 51 | 52 | .. code-block:: python 53 | 54 | TRAIN_DATALOADER = Dict( 55 | fct="BatchGen", 56 | kwargs=Dict( 57 | # Insert parameters here 58 | ) 59 | ) 60 | VAL_DATALOADER = Dict( 61 | fct="BatchGen", 62 | kwargs=Dict( 63 | # Insert parameters here 64 | ) 65 | ) 66 | 67 | # Instead of 68 | 69 | TRAIN_DATASET = Dict( 70 | fct="SegPatchFast", 71 | kwargs=Dict( 72 | # Insert parameters here 73 | ) 74 | ) 75 | VAL_DATASET = Dict( 76 | fct="SegPatchFast", 77 | kwargs=Dict( 78 | # Insert parameters here 79 | ) 80 | ) 81 | 82 | 83 | .. autoclass:: MTBatchGenDataLoader 84 | :no-index: 85 | 86 | .. note:: 87 | This class use a dependency that is not in Biom3d's dependency so you will need to install it manually : `pip install batchgenerators` 88 | 89 | Models 90 | ~~~~~~ 91 | 92 | .. currentmodule:: biom3d.models.unet3d_vgg_deep 93 | 94 | .. class:: UNet 95 | :no-index: 96 | 97 | --------------- 98 | 99 | This is a transcription of nnUnet neural network, and is the default model used by Biom3d 100 | 101 | .. autoclass:: UNet 102 | :no-index: 103 | 104 | .. currentmodule:: biom3d.models.encoder_vgg 105 | 106 | It use :class:`VGGEncoder` as encoder : 107 | 108 | .. autoclass:: VGGEncoder 109 | :no-index: 110 | 111 | And it use :class:`VGGDecoder` as decoder 112 | 113 | .. currentmodule:: biom3d.models.unet3d_eff 114 | 115 | .. class:: EfficientNet3D 116 | 117 | --------------- 118 | 119 | This is a transcription of nnUnet neural network, and is the default model used by Biom3d 120 | 121 | .. autoclass:: EfficientNet3D 122 | :no-index: 123 | 124 | .. currentmodule:: biom3d.models.encoder_efficientnet3d 125 | 126 | It use :class:`EfficientNet3D` as encoder : 127 | 128 | .. autoclass:: EfficientNet3D 129 | :no-index: 130 | 131 | .. currentmodule:: biom3d.models.decoder_vgg_deep 132 | 133 | And it use :class:`VGGDecoder` as decoder. 134 | 135 | .. note:: 136 | 137 | Both the encoder can also be used as models. 138 | 139 | Metrics 140 | ~~~~~~~ 141 | Here are the possibles metrics : 142 | 143 | .. currentmodule:: biom3d.metrics 144 | 145 | Dice 146 | ---- 147 | 148 | One of the most used metrics. It is a comparison between the two image returning a number between `0` and `1`, the closer it is to `1`, the closer are the images. 149 | 150 | .. autoclass:: Dice 151 | :no-index: 152 | 153 | CrossEntropy 154 | ------------ 155 | 156 | Metric that compare the softmax of the logit and the mask. 157 | 158 | .. autoclass:: CrossEntropy 159 | :no-index: 160 | 161 | DiceBCE 162 | ------- 163 | 164 | Metric that ally :class:`Dice` and :class:`CrossEntropy` 165 | 166 | .. autoclass:: DiceBCE 167 | :no-index: 168 | 169 | DC_and_CE_loss 170 | -------------- 171 | 172 | nnUnet's implementation of :class:`Dice` and :class:`CrossEntropy`, is more robust but doesn't treat binary cases. 173 | 174 | .. autoclass:: DC_and_CE_loss 175 | :no-index: 176 | 177 | IoU 178 | --- 179 | 180 | One of the most used metrics. It is a comparison between the two image returning a number between `0` and `1`, the closer it is to `1`, the closer are the images. 181 | It is close the :class:`Dice` but with less weight on the intersection (eg: Dice of 0.5 while IoU of 0.66). 182 | 183 | .. autoclass:: IoU 184 | :no-index: 185 | 186 | MSE 187 | --- 188 | 189 | Use mean square method to compute loss. 190 | 191 | .. autoclass:: MSE 192 | :no-index: 193 | 194 | DeepMetric 195 | ---------- 196 | 197 | A deep supervision metric. Can be used with :class:`DiceBCE` and :class:`MSE`. 198 | 199 | .. autoclass:: DeepMetric 200 | :no-index: 201 | 202 | Trainers 203 | ~~~~~~~~ 204 | 205 | Here are the possibles trainers : 206 | 207 | .. currentmodule:: biom3d.trainers 208 | 209 | SegTrain 210 | -------- 211 | 212 | Default trainer, do the whole training. 213 | 214 | .. autofunction:: seg_train 215 | :no-index: 216 | 217 | SegVal 218 | ------ 219 | 220 | Default validater. 221 | 222 | .. autofunction:: seg_validate 223 | :no-index: 224 | 225 | SegPatchTrain 226 | ------------- 227 | 228 | Torchio trainer, created to use Torchio datasets and patch approach. 229 | 230 | .. autofunction:: seg_patch_train 231 | :no-index: 232 | 233 | SegPatchVal 234 | ----------- 235 | 236 | Torchio validater, created to use Torchio datasets and patch approach. 237 | 238 | .. autofunction:: seg_patch_validate 239 | :no-index: 240 | 241 | Preprocessor 242 | ~~~~~~~~~~~~ 243 | 244 | There is currently only one preprocessor : 245 | 246 | .. currentmodule:: biom3d.preprocess 247 | 248 | .. autofunction:: seg_preprocessor 249 | :no-index: 250 | 251 | Predictors 252 | ~~~~~~~~~~ 253 | 254 | Here are the available predictors : 255 | 256 | There is currently only one preprocessor : 257 | 258 | .. currentmodule:: biom3d.predictors 259 | 260 | Seg 261 | --- 262 | 263 | .. note:: 264 | This predictor should not be used, it is only here for retrocompatibility sake. 265 | 266 | .. autofunction:: seg_predict 267 | :no-index: 268 | 269 | SegPatch 270 | -------- 271 | 272 | .. note:: 273 | This is the default predictor. 274 | 275 | .. autofunction:: seg_predict_patch_2 276 | :no-index: 277 | 278 | Postprocessor 279 | ~~~~~~~~~~~~~ 280 | 281 | There is currently only one postprocessor : 282 | 283 | .. currentmodule:: biom3d.predictors 284 | 285 | .. autofunction:: seg_postprocessing 286 | :no-index: -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | from biom3d.utils import one_hot_fast 4 | 5 | # Helper to create expected outputs for clarity 6 | def create_expected(channels): 7 | return np.array(channels, dtype=np.uint8) 8 | 9 | class TestOneHotFast: 10 | 11 | # --- Tests for `mapping_mode='strict'` (the default) --- 12 | 13 | def test_strict_valid_input(self): 14 | """Tests 'strict' mode with perfect, contiguous input.""" 15 | values = np.array([[0, 1], [2, 0]]) 16 | actual = one_hot_fast(values, num_classes=3, mapping_mode='strict') 17 | expected = create_expected([ 18 | [[1, 0], [0, 1]], # Class 0 19 | [[0, 1], [0, 0]], # Class 1 20 | [[0, 0], [1, 0]], # Class 2 21 | ]) 22 | np.testing.assert_array_equal(actual, expected) 23 | 24 | def test_strict_raises_error_on_high_value(self): 25 | """Tests 'strict' mode fails if a value is >= num_classes.""" 26 | values = np.array([0, 1, 3]) # 3 is out of bounds for num_classes=3 27 | with pytest.raises(ValueError, match=r"must be in \[0, 2\]"): 28 | one_hot_fast(values, num_classes=3, mapping_mode='strict') 29 | 30 | def test_strict_raises_error_on_negative_value(self): 31 | """Tests 'strict' mode fails if a value is negative.""" 32 | values = np.array([-1, 0, 1]) 33 | with pytest.raises(ValueError, match=r"must be in \[0, 2\]"): 34 | one_hot_fast(values, num_classes=3, mapping_mode='strict') 35 | 36 | def test_strict_allows_missing_labels(self): 37 | """Tests 'strict' mode works correctly even if some valid labels are missing.""" 38 | values = np.array([0, 2, 0]) # Label 1 is missing, but this is allowed 39 | actual = one_hot_fast(values, num_classes=3, mapping_mode='strict') 40 | expected = create_expected([ 41 | [1, 0, 1], # Class 0 42 | [0, 0, 0], # Class 1 (empty) 43 | [0, 1, 0], # Class 2 44 | ]) 45 | np.testing.assert_array_equal(actual, expected) 46 | 47 | # --- Tests for `mapping_mode='pad'` --- 48 | 49 | def test_pad_valid_input(self): 50 | """Tests 'pad' mode works like 'strict' for valid, sparse input.""" 51 | values = np.array([0, 3]) # Labels 1, 2 are missing 52 | actual = one_hot_fast(values, num_classes=4, mapping_mode='pad') 53 | expected = create_expected([ 54 | [1, 0], # Class 0 55 | [0, 0], # Class 1 (padded) 56 | [0, 0], # Class 2 (padded) 57 | [0, 1], # Class 3 58 | ]) 59 | np.testing.assert_array_equal(actual, expected) 60 | 61 | def test_pad_raises_error_on_high_value(self): 62 | """Tests 'pad' mode fails if a value is out of the [0, num_classes-1] range.""" 63 | values = np.array([0, 4]) # 4 is out of bounds for num_classes=4 64 | with pytest.raises(ValueError, match=r"must be in \[0, 3\]"): 65 | one_hot_fast(values, num_classes=4, mapping_mode='pad') 66 | 67 | def test_pad_raises_error_on_negative_value(self): 68 | """Tests 'pad' mode fails if a value is negative.""" 69 | values = np.array([-1, 0, 3]) 70 | with pytest.raises(ValueError, match=r"must be in \[0, 3\]"): 71 | one_hot_fast(values, num_classes=4, mapping_mode='pad') 72 | 73 | # --- Tests for `mapping_mode='remap'` --- 74 | 75 | def test_remap_valid_input(self): 76 | """Tests 'remap' mode with arbitrary, non-contiguous labels.""" 77 | values = np.array([10, 50, 100, 10]) # Remaps 10->0, 50->1, 100->2 78 | actual = one_hot_fast(values, num_classes=3, mapping_mode='remap') 79 | expected = create_expected([ 80 | [1, 0, 0, 1], # Class 0 (original 10) 81 | [0, 1, 0, 0], # Class 1 (original 50) 82 | [0, 0, 1, 0], # Class 2 (original 100) 83 | ]) 84 | np.testing.assert_array_equal(actual, expected) 85 | 86 | def test_remap_raises_error_on_mismatched_counts(self): 87 | """Tests 'remap' mode fails if len(unique) != num_classes.""" 88 | values = np.array([10, 50, 100]) # 3 unique values 89 | # We expect 4 classes, this is an ambiguity 'remap' should not solve. 90 | with pytest.raises(ValueError, match=r"number of unique values \(3\) must equal num_classes \(4\)"): 91 | one_hot_fast(values, num_classes=4, mapping_mode='remap') 92 | 93 | def test_remap_with_negative_numbers(self): 94 | """Tests 'remap' mode works correctly with negative and zero labels.""" 95 | values = np.array([-5, 0, 10, 0]) # Remaps -5->0, 0->1, 10->2 96 | actual = one_hot_fast(values, num_classes=3, mapping_mode='remap') 97 | expected = create_expected([ 98 | [1, 0, 0, 0], # Class 0 (original -5) 99 | [0, 1, 0, 1], # Class 1 (original 0) 100 | [0, 0, 1, 0], # Class 2 (original 10) 101 | ]) 102 | np.testing.assert_array_equal(actual, expected) 103 | 104 | # --- Tests for `num_classes=None` (Inference Mode) --- 105 | 106 | def test_infer_num_classes(self): 107 | """Tests that num_classes=None correctly infers classes and uses 'remap'.""" 108 | values = np.array([10, 50, 100, 10]) 109 | # Should behave exactly like the test_remap_valid_input test 110 | actual = one_hot_fast(values, num_classes=None) 111 | expected = create_expected([ 112 | [1, 0, 0, 1], # Class 0 (original 10) 113 | [0, 1, 0, 0], # Class 1 (original 50) 114 | [0, 0, 1, 0], # Class 2 (original 100) 115 | ]) 116 | assert actual.shape[0] == 3 117 | np.testing.assert_array_equal(actual, expected) 118 | 119 | def test_infer_num_classes_with_contiguous_labels(self): 120 | """Tests inference mode with simple, contiguous labels.""" 121 | values = np.array([0, 1, 2, 0]) 122 | actual = one_hot_fast(values, num_classes=None) 123 | expected = create_expected([ 124 | [1, 0, 0, 1], # Class 0 125 | [0, 1, 0, 0], # Class 1 126 | [0, 0, 1, 0], # Class 2 127 | ]) 128 | assert actual.shape[0] == 3 129 | np.testing.assert_array_equal(actual, expected) 130 | 131 | # --- Edge Case Tests --- 132 | 133 | def test_empty_input_array(self): 134 | """Tests behavior with an empty input array.""" 135 | values = np.array([], dtype=int) 136 | actual = one_hot_fast(values, num_classes=3) 137 | # Expected shape is (3, 0) 138 | assert actual.shape == (3, 0) 139 | np.testing.assert_array_equal(actual, np.empty((3, 0), dtype=np.uint8)) 140 | 141 | def test_empty_input_with_remap(self): 142 | """Tests remap mode with an empty input array.""" 143 | values = np.array([], dtype=int) 144 | # num_classes must be 0 for this to be valid 145 | actual = one_hot_fast(values, num_classes=0, mapping_mode='remap') 146 | assert actual.shape == (0, 0) 147 | 148 | # Test that it fails if num_classes is not 0 149 | with pytest.raises(ValueError, match="unique values (0) must equal num_classes (1)"): 150 | one_hot_fast(values, num_classes=1, mapping_mode='remap') -------------------------------------------------------------------------------- /docs/tuto/config.md: -------------------------------------------------------------------------------- 1 | # The Configuration file and the Register 2 | 3 | Let's delve a little bit more into Biom3d structure. 4 | 5 | Biom3d code is modular, which means that it is easy to plug in or out so-called *modules*. The complete list of existing modules can be found in [biom3d.register](https://github.com/GuillaumeMougeot/biom3d/blob/main/src/biom3d/register.py). 6 | 7 | 8 | 9 | ## Training configuration file definition 10 | 11 | All of the hyper-parameters are defined in the configuration file. The configuration files are stored in Python format in the `configs` folder. You can create a new config file by copy/paste one of the existing ones and by adapting the parameters defined below. For instance, copy/paste and rename `unet_pancreas.py` in the same folder and open this Python script with your favourite text editor. 12 | 13 | There are two types of hyper-parameters in the configuration file: builder parameters and modules parameters. 14 | 15 | ### Builder parameters 16 | 17 | Builder parameters are written as follows: `NAME=value`. The dataset builder parameters must be adapted to your own dataset and the Auto-config builder parameters value can be set with the pre-processing values. The rest of the builder parameters is optional. 18 | 19 | Here is the exhaustive list of builder parameters: 20 | 21 | ```python 22 | #--------------------------------------------------------------------------- 23 | # Dataset builder-parameters 24 | # EDIT THE FOLLOWING PARAMATERS WITH YOUR OWN DATASETS PARAMETERS 25 | 26 | # Folder where pre-processed images are stored 27 | IMG_DIR = 'data/pancreas/tif_imagesTr_small' 28 | 29 | # Folder where pre-processed masks are stored 30 | MSK_DIR = 'data/pancreas/tif_labelsTr_small' 31 | 32 | # (optional) path to the .csv file storing "filename,hold_out,fold", where: 33 | # "filename" is the image name, 34 | # "hold_out" is either 0 (training image) or 1 (testing image), 35 | # "fold" (non-negative integer) indicates the k-th fold, 36 | # by default fold 0 of the training image (hold_out=0) is the validation set. 37 | CSV_DIR = 'data/pancreas/folds_pancreas.csv' 38 | 39 | # CSV_DIR can be set to None, in which case the validation set will be 40 | # automatically chosen from the training set (20% of the training images/masks) 41 | # CSV_DIR = None 42 | 43 | # model name 44 | DESC = 'unet_mine-pancreas_21' 45 | 46 | # number of classes of objects 47 | # the background does not count, so the minimum is 1 (the max is 255) 48 | NUM_CLASSES=2 49 | 50 | #--------------------------------------------------------------------------- 51 | # Auto-config builder-parameters 52 | # PASTE AUTO-CONFIG RESULTS HERE 53 | 54 | # batch size 55 | BATCH_SIZE = 2 56 | 57 | # patch size passed to the model 58 | PATCH_SIZE = [40,224,224] 59 | 60 | # larger patch size used prior rotation augmentation to avoid "empty" corners. 61 | AUG_PATCH_SIZE = [48,263,263] 62 | 63 | # number of pooling done in the UNet 64 | NUM_POOLS = [3,5,5] 65 | 66 | # median spacing is used only during prediction to normalize the output images 67 | # it is commented here because we did not noticed any improvemet 68 | # MEDIAN_SPACING=[0.79492199, 0.79492199, 2.5] 69 | MEDIAN_SPACING=[] 70 | 71 | #--------------------------------------------------------------------------- 72 | # Advanced paramaters (can be left as such) 73 | # training configs 74 | 75 | # whether to store also the best model 76 | SAVE_BEST = True 77 | 78 | # number of epochs 79 | # the number of epochs can be reduced for small training set (e.g. a set of 10 images/masks of 128x128x64) 80 | NB_EPOCHS = 1000 81 | 82 | # optimizer paramaters 83 | LR_START = 1e-2 # comment if need to reload learning rate after training interruption 84 | WEIGHT_DECAY = 3e-5 85 | 86 | # whether to use deep-supervision loss: 87 | # a loss is placed at each stage of the UNet model 88 | USE_DEEP_SUPERVISION = False 89 | 90 | # whether to use softmax loss instead of sigmoid 91 | # should not be set to True if object classes are overlapping in the masks 92 | USE_SOFTMAX=False 93 | 94 | # training loop parameters 95 | USE_FP16 = True 96 | NUM_WORKERS = 4 97 | 98 | #--------------------------------------------------------------------------- 99 | # callback setup (can be left as such) 100 | # callbacks are routines that execute periodically during the training loop 101 | 102 | # folder where the training logs will be stored, including: 103 | # - model .pth files (state_dict) 104 | # - image snapshots of model training (only if USE_IMAGE_CLBK is True) 105 | # - logs with this configuration stored in .yaml format and tensorboard logs 106 | LOG_DIR = 'logs/' 107 | 108 | SAVE_MODEL_EVERY_EPOCH = 1 109 | USE_IMAGE_CLBK = True 110 | VAL_EVERY_EPOCH = SAVE_MODEL_EVERY_EPOCH 111 | SAVE_IMAGE_EVERY_EPOCH = SAVE_MODEL_EVERY_EPOCH 112 | USE_FG_CLBK = True 113 | #--------------------------------------------------------------------------- 114 | 115 | ``` 116 | 117 | ### Module parameters 118 | 119 | The modules parameters are written as follows in the configuration file: 120 | 121 | ```python 122 | NAME=Dict( 123 | fct="RegisterName" 124 | kwargs=Dict( 125 | key_word=arguments, 126 | ) 127 | ) 128 | ``` 129 | 130 | The `fct` argumentation correspond to one of the module name listed in the `register.py` file. The `register.py` file lists all existing modules in Biom3d. To have more details about one specific module, we recommended to read the documentation of the module. There are currently 5 main modules type: dataset, model, metric, trainer and predictor. Each modules are not compatible with all modules, read the documentation for more details. 131 | 132 | ## Training 133 | 134 | Please create a folder named `logs/` in the current directory. 135 | 136 | Once the configuration file is defined, the training can start with the following command: 137 | 138 | ``` 139 | python biom3d/train.py --config configs.your_config_file 140 | ``` 141 | 142 | Careful, do not put `.py` in the end of your config file name. 143 | 144 | A new sub-folder, that we dubbed base-folder in this documentation, will be created in the `logs/` folder. The base-folder contains 3 sub-folders: 145 | * `image`: with the snapshots of the current training results 146 | * `log`: with the configuration files stored in Yaml format and with Tensorboard event file 147 | * `model`: with the Pytorch model(s). 148 | 149 | You can plot the training curves during model training with the following command: 150 | 151 | ``` 152 | tensorboard --logdir=logs/ 153 | ``` 154 | 155 | ### Advanced training/evaluation/prediction 156 | 157 | Biom3d has originally been designed to fasten state-of-the-art tools development for 3d bio-medical imaging, that's why it possible to run in a single command: the training, the test prediction and the test metrics computations. Use `python biom3d/train.py --help` to get more details. 158 | 159 | ## Prediction 160 | 161 | Once your model is trained, it is ready to use for prediction with the following command: 162 | 163 | ``` 164 | python biom3d/pred.py --log path/to/base-folder --dir_in path/to/raw/data --dir_out path/of/the/future/predictions 165 | ``` 166 | 167 | For Omero user, you can use the following command to download a Omero Dataset or a Omero Project and to directly run the prediction over this dataset: 168 | 169 | ``` 170 | python biom3d/omero_pred.py --obj Dataset:ID 171 | ``` 172 | 173 | or with a Omero Project 174 | 175 | ``` 176 | python biom3d/omero_pred.py --obj Project:ID 177 | ``` 178 | 179 | The previous command will ask you to provide your omero server name, your omero identification and your omero password. 180 | 181 | ### Advanced prediction 182 | 183 | `pred.py` can also be used to compare the prediction results with existing test annotations. Use `python biom3d/pred.py --help` for more details. -------------------------------------------------------------------------------- /src/biom3d/utils/data_handler/data_handler_factory.py: -------------------------------------------------------------------------------- 1 | """Class to instantiate a DataHandler depending on the input and output type.""" 2 | 3 | from os.path import isdir,splitext,exists,dirname 4 | from os import lstat,getcwd,access,W_OK 5 | from typing import Optional 6 | from urllib.parse import urlparse 7 | 8 | from .data_handler_abstract import DataHandler 9 | from .file_handler import FileHandler 10 | from .hdf5_handler import HDF5Handler 11 | 12 | class DataHandlerFactory: 13 | """Class to instantiate a DataHandler depending on the input and output type.""" 14 | 15 | EXTENSION_MAP: dict[str, type['DataHandler']] = { 16 | ".h5": HDF5Handler, 17 | ".hdf5": HDF5Handler, 18 | "folder": FileHandler 19 | } 20 | 21 | @staticmethod 22 | def _is_url(path: str) -> bool: 23 | """ 24 | Check if given path is an URL. 25 | 26 | Parameters 27 | ---------- 28 | path: str 29 | The path to test. 30 | 31 | Returns 32 | ------- 33 | boolean: 34 | Whether path is an URL or not. 35 | """ 36 | return urlparse(path).scheme in ("http", "https", "ftp", "s3") 37 | 38 | @staticmethod 39 | def _is_nonexistent_folder(path: str) -> bool: 40 | """ 41 | Check if given path refer to a folder that doesn't exist yet. 42 | 43 | Parameters 44 | ---------- 45 | path: str 46 | The path to test. 47 | 48 | Returns 49 | ------- 50 | boolean: 51 | Wether the path refer to a non existing folder or not. 52 | """ 53 | def is_path_valid(pathname: str) -> bool: 54 | # Assume path is valid unless proven otherwise 55 | try: 56 | if not pathname or pathname.isspace(): 57 | return False 58 | lstat(pathname) 59 | return True 60 | except (OSError, ValueError): 61 | return True # We allow non-existing paths 62 | except Exception: 63 | return False 64 | 65 | def is_path_creatable(pathname: str) -> bool: 66 | # Check for writing authorisation 67 | dir = dirname(pathname) or getcwd() 68 | return access(dir, W_OK) 69 | 70 | if DataHandlerFactory._is_url(path): 71 | return False 72 | _, ext = splitext(path) 73 | if ext: # Probably a file 74 | return False 75 | 76 | try: 77 | return is_path_valid(path) and ( 78 | exists(path) or is_path_creatable(path) 79 | ) 80 | except OSError: 81 | return False 82 | 83 | @staticmethod 84 | def _detect_handler_type(path: str) -> type['DataHandler']: 85 | """ 86 | Extract the data format from path and return `DataHandler` subclass fit to treat it if existing, raise `NotImplementedError` else. 87 | 88 | Parameters 89 | ---------- 90 | path: str 91 | The path to test 92 | 93 | Raises 94 | ------ 95 | NotImplementedError: 96 | If handler or input data not found. 97 | 98 | Returns 99 | ------- 100 | Datahandler: 101 | A DataHandler that can treat the data format given by path. 102 | """ 103 | if isdir(path) or DataHandlerFactory._is_nonexistent_folder(path): 104 | return DataHandlerFactory.EXTENSION_MAP["folder"] 105 | _, ext = splitext(path) 106 | ext = ext.lower() 107 | if ext in DataHandlerFactory.EXTENSION_MAP: 108 | return DataHandlerFactory.EXTENSION_MAP[ext] 109 | raise NotImplementedError(f"No handler found for extension: '{ext}'") 110 | 111 | @staticmethod 112 | def get(input:str,read_only:bool=False,preprocess:bool=False,output:Optional[str]=None,**kwargs)->DataHandler: 113 | """ 114 | Create a handler which type depend on the input extension. 115 | 116 | Parameters 117 | ---------- 118 | input: str 119 | Path to input (Folder path, archive path, url,...). This path will be used as the image path. 120 | 121 | read_only: bool, default = False 122 | (Optional) Whether handler is in read only. 123 | 124 | output: str, default = None 125 | (Optional) Path to output, is used if the output type is different from input. 126 | 127 | preprocess: bool, default = False 128 | (Optional) If it is a preprocessing handler (will create more output). 129 | 130 | **kwargs: 131 | 132 | All existing parameters to existing handlers, currently 133 | msk_path:str, default=None, 134 | Generic : mask output path 135 | fg_path:str, default = None 136 | Generic : foreground output path 137 | eval: "label" | "pred" | None, default=None 138 | HDF5Hanlder (and all others that use keys) : Tell your handler that it is to eval and that it should search for the label or prediction key in your dataset key. 139 | img_inner_paths_list, default=None 140 | Generic : A list of path comming from a specific root (eg: The paths inside a .h5 file), used in data/batch loaders. 141 | msk_inner_paths_list, default=None 142 | Generic : A list of path comming from a specific root (eg: The paths inside a .h5 file), used in data/batch loaders 143 | fg_inner_paths_list, default=None 144 | Generic : A list of path comming from a specific root (eg: The paths inside a .h5 file), used in data/batch loaders 145 | img_outpath:str, default = None, 146 | Generic : images output path 147 | msk_outpath:str, default = None 148 | Generic : mask output path 149 | fg_outpath:str, default = None 150 | Generic : foreground output path 151 | model_name:str, default = None 152 | Generic : Used for prediction, if different than `None`, it will be added at the end of path (eg: predictions/MyModelName, predictions.h5["MyModelName"]) 153 | use_tif:bool, default = False 154 | FileHandler : If should be saved as tif instead of npy. 155 | 156 | Raises 157 | ------ 158 | ValueError: 159 | If parameters `read_only` and `preprocess` are both `True`. 160 | 161 | Returns 162 | ------- 163 | DataHandler 164 | A DataHandler specific to input and output type 165 | """ 166 | if read_only and preprocess : raise ValueError("A preprocess handler need to write and can't be in read_only") 167 | INPUT = DataHandlerFactory._detect_handler_type(input) 168 | kwargs["img_path"]=input 169 | handler = INPUT() 170 | handler._input_parse(**kwargs) 171 | handler._preprocess=preprocess 172 | if read_only: 173 | saver=None 174 | elif output == None: 175 | saver = handler 176 | else : 177 | OUTPUT = DataHandlerFactory._detect_handler_type(output) 178 | saver = OUTPUT() if OUTPUT != INPUT else handler 179 | 180 | if not read_only and preprocess : 181 | saver._preprocess=preprocess 182 | saver._output_parse_preprocess(**kwargs) 183 | elif not read_only: 184 | saver._preprocess=preprocess 185 | saver._output_parse(**kwargs) 186 | 187 | 188 | handler._saver = saver 189 | return handler 190 | -------------------------------------------------------------------------------- /src/biom3d/omero_pred.py: -------------------------------------------------------------------------------- 1 | """ 2 | Predictions with Omero. 3 | 4 | This script can download data from Omero, compute predictions, and upload back into Omero. 5 | """ 6 | 7 | import argparse 8 | import os 9 | import shutil 10 | from typing import Optional 11 | from omero.cli import cli_login 12 | 13 | 14 | from biom3d import omero_downloader 15 | try: 16 | from biom3d import omero_uploader 17 | except: 18 | print("[WARNING] Couldn't import omero uploader.") 19 | pass 20 | from biom3d import pred 21 | 22 | def run( 23 | obj: str, 24 | target: str, 25 | log:str, 26 | dir_out: str, 27 | host: Optional[str] = None, 28 | user: Optional[str] = None, 29 | pwd: Optional[str] = None, 30 | upload_id: Optional[str] = None, 31 | ext: str = "_predictions", 32 | attachment: Optional[str] = None, 33 | session_id: Optional[str] = None, 34 | skip_preprocessing: bool = False 35 | ) -> Optional[str]: 36 | """ 37 | Download a dataset or project from Omero, perform predictions, and optionally upload the results back. 38 | 39 | Depending on whether the object is a "Dataset" or "Project", the function handles: 40 | - downloading the data (either via Omero API or CLI), 41 | - running inference, 42 | - optionally uploading the predicted results back to Omero, 43 | - cleaning up temporary files if upload is done. 44 | 45 | Parameters 46 | ---------- 47 | obj : str 48 | Type and ID of the object to process (e.g., "Dataset:123" or "Project:456"). 49 | target : str 50 | Target location for downloading. 51 | log : str 52 | Path to the model folder. 53 | dir_out : str 54 | Path to the directory where predictions should be saved. 55 | host : str, optional 56 | Hostname of the Omero server, if using API authentication. 57 | user : str, optional 58 | Username for Omero authentication. 59 | pwd : str, optional 60 | Password for Omero authentication. 61 | upload_id : str, optional 62 | ID of the project to upload predictions back to. If None, uploading is skipped. 63 | ext : str, default="_predictions" 64 | Suffix to append to prediction output directories. 65 | attachment : str, optional 66 | Path to an optional attachment file to include in the upload (e.g., logs or configs). 67 | session_id : str, optional 68 | Session ID for Omero (used for authenticated operations). 69 | skip_preprocessing : bool, default=False 70 | Whether to skip preprocessing steps before prediction. 71 | 72 | Returns 73 | ------- 74 | str or None 75 | Path to the output directory containing predictions, or None if an error occurred. 76 | 77 | Notes 78 | ----- 79 | - The function prints messages that can be parsed remotely with the format "REMOTE:key:value". 80 | - Uploading back to Omero is deprecated but still supported. 81 | """ 82 | print("Start dataset/project downloading...") 83 | if host is not None: 84 | datasets, dir_in = omero_downloader.download_object(user, pwd, host, obj, target, session_id) 85 | else: 86 | with cli_login() as cli: 87 | datasets, dir_in = omero_downloader.download_object_cli(cli, obj, target) 88 | 89 | print("Done downloading dataset/project!") 90 | 91 | print("Start prediction...") 92 | if 'Dataset' in obj: 93 | dir_in = os.path.join(dir_in, datasets[0].name) 94 | dir_out = os.path.join(dir_out, datasets[0].name + ext) 95 | if not os.path.isdir(dir_out): 96 | os.makedirs(dir_out, exist_ok=True) 97 | dir_out = pred.pred(log, dir_in, dir_out,skip_preprocessing=skip_preprocessing) 98 | 99 | 100 | # eventually upload the dataset back into Omero [DEPRECATED] 101 | if upload_id is not None and host is not None: 102 | 103 | # create a new Omero Dataset 104 | dataset_name = os.path.basename(dir_in) 105 | if len(dataset_name)==0: # this might happen if pred_dir=='path/to/folder/' 106 | dataset_name = os.path.basename(os.path.dirname(dir_in)) 107 | dataset_name += ext 108 | 109 | omero_uploader.run( 110 | username=user, 111 | password=pwd, 112 | host=host, 113 | project=upload_id, 114 | attachment=attachment, 115 | is_pred=True, 116 | dataset_name=dataset_name, 117 | path=dir_out, 118 | session_id=session_id) 119 | 120 | # Remove all folders (pred, to_pred, attachment File) 121 | try : 122 | shutil.rmtree(dir_in) 123 | shutil.rmtree(dir_out) 124 | os.remove(attachment+".zip") 125 | except: 126 | pass 127 | 128 | print("Done prediction!") 129 | 130 | # print for remote. Format TAG:key:value 131 | print("REMOTE:dir_out:{}".format(dir_out)) 132 | return dir_out 133 | 134 | elif 'Project' in obj: 135 | dir_out = os.path.join(dir_out, os.path.split(dir_in)[-1]) 136 | if not os.path.isdir(dir_out): 137 | os.makedirs(dir_out, exist_ok=True) 138 | pred.pred_multiple(log, dir_in, dir_out) 139 | print("Done prediction!") 140 | 141 | # print for remote. Format TAG:key:value 142 | print("REMOTE:dir_out:{}".format(dir_out)) 143 | return dir_out 144 | else: 145 | print("[Error] Type of object unknown {}. It should be 'Dataset' or 'Project'".format(obj)) #TODO raise error, or exit with error code 146 | 147 | if __name__=='__main__': 148 | 149 | # parser 150 | parser = argparse.ArgumentParser(description="Prediction with Omero.") 151 | parser.add_argument('--obj', type=str, 152 | help="Download object: 'Project:ID' or 'Dataset:ID'") 153 | parser.add_argument('--target', type=str, default="data/to_pred/", 154 | help="Directory name to download into") 155 | parser.add_argument("--log", type=str, default="logs/unet_nucleus", 156 | help="Path of the builder directory") 157 | parser.add_argument("--dir_out", type=str, default="data/pred/", 158 | help="Path to the output prediction directory") 159 | parser.add_argument('--hostname', type=str, default=None, 160 | help="(optional) Host name for Omero server. If not mentioned use the CLI.") 161 | parser.add_argument('--username', type=str, default=None, 162 | help="(optional) User name for Omero server") 163 | parser.add_argument('--password', type=str, default=None, 164 | help="(optional) Password for Omero server") 165 | parser.add_argument('--upload_id', type=int, default=None, 166 | help="(optional) Id of Omero Project in which to upload the dataset. Only works with Omero Project Id and folder of images.") 167 | parser.add_argument('--attachment', type=str, default=None, 168 | help="(optional) Attachment file") 169 | parser.add_argument('--session_id', default=None, 170 | help="(optional) Session ID for Omero client.") 171 | parser.add_argument('--ext', type=str, default='_predictions', 172 | help='(optional) Name of the extension added to the future uploaded Omero dataset.') 173 | parser.add_argument("--skip_preprocessing", default=False, action='store_true',dest="skip_prepprocessing", 174 | help="(default=False) Skip preprocessing") 175 | args = parser.parse_args() 176 | 177 | run( 178 | obj=args.obj, 179 | target=args.target, 180 | log=args.log, 181 | dir_out=args.dir_out, 182 | host=args.hostname, 183 | user=args.username, 184 | pwd=args.password, 185 | upload_id=args.upload_id, 186 | ext=args.ext, 187 | attachment=args.attachment, 188 | session_id=args.session_id, 189 | skip_preprocessing=args.skip_preprocessing, 190 | ) -------------------------------------------------------------------------------- /src/biom3d/models/unet3d_vgg_deep.py: -------------------------------------------------------------------------------- 1 | """Biom3d adaptation of nnUnet base model.""" 2 | 3 | from typing import Optional 4 | import torch 5 | from torch import nn 6 | 7 | from biom3d.models.encoder_vgg import EncoderBlock, VGGEncoder 8 | from biom3d.models.decoder_vgg_deep import VGGDecoder 9 | 10 | #--------------------------------------------------------------------------- 11 | # 3D UNet with the previous encoder and decoder 12 | 13 | class UNet(nn.Module): 14 | """ 15 | A 3D UNet architecture utilizing VGG-style encoder and decoder blocks for volumetric (3D) image segmentation. 16 | 17 | The UNet model is a convolutional neural network for fast and precise segmentation of images. 18 | This implementation incorporates VGG blocks for encoding and decoding, allowing for deep feature extraction 19 | and reconstruction, respectively. The model supports dynamic adjustment of pooling layers and class numbers, 20 | along with optional deep decoder usage and weight initialization from pre-trained checkpoints. 21 | 22 | :ivar VGGEncoder encoder: The encoder part of the UNet, responsible for downscaling and feature extraction. 23 | :ivar VGGDecoder decoder: The decoder part of the UNet, responsible for upscaling and constructing the segmentation map. 24 | """ 25 | 26 | def __init__( 27 | self, 28 | num_pools:list[int]=[5,5,5], 29 | num_classes:int=1, 30 | factor:int=32, 31 | encoder_ckpt:Optional[str] = None, 32 | model_ckpt:Optional[str] = None, 33 | use_deep:bool=True, 34 | in_planes:int = 1, 35 | flip_strides:bool = False, 36 | roll_strides:bool = True, #used for models trained before commit f2ac9ee (August 2023) 37 | ): 38 | """ 39 | Unet initialization. 40 | 41 | Parameters 42 | ---------- 43 | num_pools : list of int, default=[5,5,5] 44 | A list of integers defining the number of pooling layers for each dimension of the input. 45 | num_classes : int, default=1 46 | The number of classes for segmentation. 47 | factor : int, default=32 48 | The scaling factor for the number of channels in VGG blocks. 49 | encoder_ckpt : str, optional 50 | Path to a checkpoint file from which to load encoder weights. 51 | model_ckpt : str, optional 52 | Path to a checkpoint file from which to load the entire model's weights. 53 | use_deep : bool, default=True 54 | Flag to indicate whether to use a deep decoder. 55 | in_planes : int, default=1 56 | The number of input channels. 57 | flip_strides : bool, default=False 58 | Flag to flip strides to match encoder and decoder dimensions. Useful for ensuring dimensionality alignment. 59 | roll_strides : bool, default=True 60 | Whether to roll strides when computing pooling (used for backward compatibility for models trained before commit f2ac9ee (August 2023)). 61 | """ 62 | super(UNet, self).__init__() 63 | self.encoder = VGGEncoder( 64 | EncoderBlock, 65 | num_pools=num_pools, 66 | factor=factor, 67 | in_planes=in_planes, 68 | flip_strides=flip_strides, 69 | roll_strides=roll_strides, 70 | ) 71 | self.decoder = VGGDecoder( 72 | EncoderBlock, 73 | num_pools=num_pools, 74 | num_classes=num_classes, 75 | factor_e=factor, 76 | factor_d=factor, 77 | use_deep=use_deep, 78 | flip_strides=flip_strides, 79 | roll_strides=roll_strides, 80 | ) 81 | 82 | # load encoder if needed 83 | if encoder_ckpt is not None: 84 | print("Load encoder weights from", encoder_ckpt) 85 | if torch.cuda.is_available(): 86 | self.encoder.cuda() 87 | elif torch.backends.mps.is_available(): 88 | self.encoder.to('mps') 89 | ckpt = torch.load(encoder_ckpt) 90 | if 'model' in ckpt.keys(): 91 | # remove `module.` prefix 92 | state_dict = {k.replace("module.", ""): v for k, v in ckpt['model'].items()} 93 | # remove `0.` prefix induced by the sequential wrapper 94 | state_dict = {k.replace("0.layers", "layers"): v for k, v in state_dict.items()} 95 | # remove `backbone.` prefix induced by pretraining 96 | state_dict = {k.replace("backbone.", ""): v for k, v in state_dict.items()} 97 | print(self.encoder.load_state_dict(state_dict, strict=False)) 98 | elif 'teacher' in ckpt.keys(): 99 | # remove `module.` prefix 100 | state_dict = {k.replace("module.", ""): v for k, v in ckpt['teacher'].items()} 101 | # remove `backbone.` prefix induced by multicrop wrapper 102 | state_dict = {k.replace("backbone.", ""): v for k, v in state_dict.items()} 103 | print(self.encoder.load_state_dict(state_dict, strict=False)) 104 | else: 105 | print("[Warning] the following encoder couldn't be loaded, wrong key:", encoder_ckpt) 106 | 107 | if model_ckpt is not None: 108 | self.load(model_ckpt) 109 | 110 | def freeze_encoder(self, freeze:bool=True)->None: 111 | """ 112 | Freeze or unfreeze the encoder's weights based on the input flag. 113 | 114 | Parameters 115 | ---------- 116 | freeze : bool, optional 117 | If True, the encoder's weights are frozen, otherwise they are unfrozen. Default is True. 118 | 119 | Returns 120 | ------- 121 | None 122 | """ 123 | if freeze: 124 | print("Freezing encoder weights...") 125 | else: 126 | print("Unfreezing encoder weights...") 127 | for l in self.encoder.parameters(): 128 | l.requires_grad = not freeze 129 | 130 | def unfreeze_encoder(self)->None: 131 | """Unfreeze the encoder's weights. Convenience method calling `freeze_encoder` with `False`.""" 132 | self.freeze_encoder(False) 133 | 134 | def load(self, model_ckpt:str)->None: 135 | """ 136 | Load the model from checkpoint. 137 | 138 | The checkpoint dictionary must have a 'model' key with the saved model for value. 139 | 140 | Parameters 141 | ---------- 142 | model_ckpt : str 143 | The path to the checkpoint file containing the model's weights. 144 | 145 | Returns 146 | ------- 147 | None 148 | """ 149 | print("Load model weights from", model_ckpt) 150 | if torch.cuda.is_available(): 151 | self.cuda() 152 | device = torch.device('cuda') 153 | elif torch.backends.mps.is_available(): 154 | self.to('mps') 155 | device = torch.device('mps') 156 | else: 157 | self.cpu() 158 | device = torch.device('cpu') 159 | ckpt = torch.load(model_ckpt, map_location=device) 160 | if 'encoder.last_layer.weight' in ckpt['model'].keys(): 161 | del ckpt['model']['encoder.last_layer.weight'] 162 | print(self.load_state_dict(ckpt['model'], strict=False)) 163 | 164 | def forward(self, x:torch.Tensor)->torch.Tensor: 165 | """ 166 | Define the forward pass of the UNet model. 167 | 168 | Parameters 169 | ---------- 170 | x : torch.Tensor 171 | The input tensor representing the image to be segmented. 172 | 173 | Returns 174 | ------- 175 | torch.Tensor 176 | The output segmentation map tensor. 177 | """ 178 | out = self.encoder(x) 179 | out = self.decoder(out) 180 | return out 181 | 182 | #--------------------------------------------------------------------------- -------------------------------------------------------------------------------- /docs/tuto/dataset.md: -------------------------------------------------------------------------------- 1 | # Creating a Dataset 2 | Here we will see how to create a dataset to use in Biom3d and some common mistakes. 3 | 4 | 5 | ## Format 6 | For the moment, only 3 format are supported. 7 | 8 | ### Folders 9 | The simpliest way to create a Dataset is to use folders. You will need just a folder where you will place all your images. You can still use subfolder to separate your data if you want. For the instant, Biom3d support the following images formats : 10 | - TIFF 11 | - NIFTY 12 | - NUMPY 13 | 14 | #### Structure and naming 15 | Let's say you have 2 datasets and you want to merge them into one (to do a single batch of predictions for example) while still separating them. You can do : 16 | ``` 17 | Raws 18 | ├───Dataset1 19 | │ ├───1.tif 20 | │ ├───2.tif 21 | │ └───... 22 | └───Dataset2 23 | └───... 24 | ``` 25 | And give to Biom3d the folder raw. It will keep the structure in prediction in preprocessing : 26 | ``` 27 | Raws_preprocess 28 | ├───Dataset1 29 | │ ├───1.tif 30 | │ ├───2.tif 31 | │ └───... 32 | └───Dataset2 33 | └───... 34 | 35 | Or 36 | 37 | Prediction 38 | └───MyModelName 39 | ├───Dataset1 40 | │ ├───1.tif 41 | │ ├───2.tif 42 | │ └───... 43 | └───Dataset2 44 | └───... 45 | ``` 46 | If you do training, or evaluation, you will need a label (or masks) folder. It work exactly the same way. However the label folder should follow exactly the same structure as raw. 47 | ``` 48 | Labels 49 | ├───Dataset1 50 | │ ├───1.tif <- is the label of Raw/Dataset1/1.tif 51 | │ ├───2.tif <- is the label of Raw/Dataset1/2.tif 52 | │ └───... 53 | └───Dataset2 54 | └───... 55 | ``` 56 | 57 | Concerning the naming of the folder/images, you can do whatever you want (as long as label has the same structure as raw). 58 | 59 | #### Common mistakes 60 | ##### Preprocessing with multiple datasets 61 | If you have several datasets and you want to train a different model on each of them, you will preprocess the data. 62 | ``` 63 | ├───Raw1 64 | │ └───... 65 | ├───Raw2 66 | │ └───... 67 | ├───Label1 68 | │ └───... 69 | └───Label2 70 | └───... 71 | ``` 72 | However, if you're note careful they may be an error of type `"Different number of masks/foreground and images"`. This is because the output of the different preprocessing are mixing. By default, preprocessing create output folder where Biom3d is started so you will have something like : 73 | ``` 74 | ├───Raw1 75 | │ └───... 76 | ├───Raw1_out 77 | │ └───... 78 | ├───Raw2 79 | │ └───... 80 | ├───Raw2_out 81 | │ └───... 82 | ├───Label1 83 | │ └───... 84 | ├───Label1_out 85 | │ └───... 86 | ├───Label2 87 | │ └───... 88 | ├───Label2_out 89 | │ └───... 90 | └───fg_out 91 | └───... 92 | ``` 93 | Noticed something ? Yes you have a preprocessed folder per `raw` and `label` folder, but only one for foreground, which mean that it mix both datasets. And that is the source of the error. 94 | ``` 95 | A simple way to solve this is to correctly separate your data set. 96 | ├───Dataset1 97 | │ ├───Raw1 98 | │ └───Label1 99 | └───Dataset2 100 | ├───Raw2 101 | └───Label2 102 | ``` 103 | Then to start Biom3d from the `Dataset` folder : 104 | - In command line, simply open terminal directly in folder or cd inside then do the preprocess (and eventually train) command. If you are only doing preprocessing, you can also not change the structure but use `--img_outpath`, `--msk_outpath` and `--fg_outpath`. Note that by default, Biom3d call it's img and mask output the same name as the input with `_out` suffix and the foreground outdir `fg_out`. 105 | - With GUI, before clicking on start locally, select the dataset folder with the folder selector. 106 | 107 | ##### Evaluation on prediction 108 | A similar issue can happen with predictions and evaluation. When you do a a prediction and give an output folder, the predictions will actually be stored in a subfolder. They will be stored in `MyPredictionFolder/MyModelName`, not directly in `MyPredictionFolder` to avoid overwrite. So if you do an evaluation, give the correct subfolder or you may have an error. If you're using a function that does prediction and evaluation at the same time, it will be done automatically. 109 | 110 | ### HDF5 111 | You can also use HDF5 to store a dataset (that can be a composition of multiple datasets). For the moment they are not usable in the GUI, only in command line. 112 | 113 | #### Structure 114 | Unlike the folder, there is a naming convention, all raw images have to be in a `raw` dataset or group and masks in `label`. 115 | ``` 116 | MyDataset.h5 117 | ├───raw 118 | │ ├───0 119 | │ ├───1 120 | │ └───... 121 | └───label 122 | └───... 123 | 124 | Or 125 | MyDataset.h5 126 | ├───raw 127 | │ ├───dataset1 128 | │ │ ├───0 129 | │ │ └───... 130 | │ └───dataset2 131 | │ ├───0 132 | │ └───... 133 | └───label 134 | ├───dataset1 135 | │ ├───0 136 | │ └───... 137 | └───dataset2 138 | ├───0 139 | └───... 140 | 141 | ``` 142 | Like the folder, the path for the mask must be the same as the path for image, however they can be in different `.h5`, as long as they have the correct key. The preprocessing will create a `fg` group and a dataset per image with the same path as the mask. For example the mask `/label/dataset1/0` will have the foreground `/fg/dataset1/0/blob`. The blob is a special format for foreground. Prediction are stored behind the key `pred`, the prediction of `/raw/dataset1/0` is in `/pred/dataset1/0`. When you do a prediction and give the output_path, let's say `pred.h5` it will be stored in `pred_MyModelName.h5`, except if you give the same file as the input : `biom3d.pred -i data.h5 -o data.h5 -l MyModel` will add the key prediction in `data.h5`. 143 | 144 | #### Commons mistakes 145 | ##### Doing multiple preprocessing 146 | Due to some restriction, their are no overwriting, meaning that if you do multiple preprocessing over the same datas, you may encounter an error at the generation of the output file. We recomment either deleting/moving away the output file between preprocessing, or if for training, separating the preprocessing step and the training step and give different output files at each time. Or doing the same thing as for the folder and separate your data between your preprocessing. 147 | 148 | *Note : this is possible to set different output files for images, label and foreground in preprocessing, if you only give the `img_outpath` parameter, it will also store labels and foregrounds into.* 149 | 150 | ##### Evaluation on prediction 151 | This is actually the same problem as for the folder, if you do several prediction with the same model, it will not overwrite but add, so if you do an evaluation after, you will encounter an error stating you don't have the same number of labels and predictions. A simple way to fix it is to specify a different output file for each batch of prediction : 152 | ```shell 153 | python -m biom3d.pred -i data.h5 -o pred1.h5 -l MyModel 154 | python -m biom3d.pred -i data.h5 -o pred2.h5 -l MyModel 155 | ``` 156 | 157 | ### OMERO 158 | You can also use OMERO dataset, however, contrarly to the preceding format, you have to use different module to use them (we are currently working on this). For prediction, use `omeror_pred`, and training `omero_preprocess_train`, or it is included in the `gui`. 159 | It will download on your computer the dataset as folders, use them, and eventually upload the created folderss. 160 | 161 | ## Extra 162 | You can mix HDF5 and folders : 163 | ```shell 164 | python -m biom3d.pred -i data.h5 -o prediction_folder -l MyModel 165 | ``` 166 | Will use h5 as input but will use folder as output. That is the case for every command including input and output. The format is automatically recognize depending on the path you give (`.h5` for HDF5 and a path to a folder (not necessarily existing for output) for the folder), the sole restriction is that the format must be the same for all input/output : 167 | ```shell 168 | # This is valid 169 | python -m biom3d.preprocess --img_path img_folder --msk_path msk_folder --img_outpath data.h5 170 | python -m biom3d.preprocess --img_path img_folder --msk_path msk_folder --img_outpath data.h5 --msk_outpath mask.h5 171 | 172 | # This is not 173 | python -m biom3d.preprocess --img_path img_folder --msk_path msk_folder --img_outpath data.h5 --msk_outpath mask_folder_out 174 | python -m biom3d.preprocess --img_path img_folder --msk_path msk.h5 --img_outpath data.h5 --msk_outpath mask.h5 -------------------------------------------------------------------------------- /configs/pancreas_example.py: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------- 2 | # Configuration file. 3 | # Copy it and edit it as much as you want! :) 4 | # 5 | # FORMATTING DETAILS: 6 | # Each key and values must be written with the format: KEY = value 7 | # Do not forget the spaces before and after the '=' symbol! This is 8 | # especially import for the global variables, such as BATCH_SIZE or 9 | # PATCH_SIZE. 10 | #--------------------------------------------------------------------------- 11 | 12 | ############################################################################ 13 | # handy class for dictionary 14 | 15 | class Dict(dict): 16 | def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) 17 | def __getattr__(self, name): return self[name] 18 | def __setattr__(self, name, value): self[name] = value 19 | def __delattr__(self, name): del self[name] 20 | 21 | ############################################################################ 22 | 23 | #--------------------------------------------------------------------------- 24 | # Dataset builder-parameters 25 | # EDIT THE FOLLOWING PARAMATERS WITH YOUR OWN DATASETS PARAMETERS 26 | 27 | # Folder where pre-processed images are stored 28 | IMG_DIR = '/home/gumougeot/all/codes/python/biom3d/data/pancreas/imagesTs_tiny_out' 29 | # Folder where pre-processed masks are stored 30 | MSK_DIR = '/home/gumougeot/all/codes/python/biom3d/data/pancreas/labelsTs_tiny_out' 31 | # (optional) path to the .csv file storing "filename,hold_out,fold", where: 32 | # "filename" is the image name, 33 | # "hold_out" is either 0 (training image) or 1 (testing image), 34 | # "fold" (non-negative integer) indicates the k-th fold, 35 | # by default fold 0 of the training image (hold_out=0) is the validation set. 36 | # CSV_DIR = 'data/pancreas/folds_pancreas.csv' 37 | 38 | # CSV_DIR can be set to None, in which case the validation set will be 39 | # automatically chosen from the training set (20% of the training images/masks) 40 | CSV_DIR = None 41 | 42 | # model name 43 | DESC = 'unet_default' 44 | 45 | # number of classes of objects 46 | # the background does not count, so the minimum is 1 (the max is 255) 47 | NUM_CLASSES = 2 48 | #--------------------------------------------------------------------------- 49 | # Auto-config builder-parameters 50 | # PASTE AUTO-CONFIG RESULTS HERE 51 | 52 | # batch size 53 | BATCH_SIZE = 2 54 | # patch size passed to the model 55 | PATCH_SIZE = [40, 224, 224] 56 | # larger patch size used prior rotation augmentation to avoid "empty" corners. 57 | AUG_PATCH_SIZE = [56, 288, 288] 58 | # number of pooling done in the UNet 59 | NUM_POOLS = [3, 5, 5] 60 | # median spacing is used only during prediction to normalize the output images 61 | # it is commented here because we did not noticed any improvement yet 62 | # MEDIAN_SPACING=[0.79492199, 0.79492199, 2.5] 63 | MEDIAN_SPACING = [] 64 | 65 | #--------------------------------------------------------------------------- 66 | # Advanced paramaters (can be left as such) 67 | # training configs 68 | 69 | # whether to store also the best model 70 | SAVE_BEST = True 71 | 72 | # number of epochs 73 | # the number of epochs can be reduced for small training set (e.g. a set of 10 images/masks of 128x128x64) 74 | NB_EPOCHS = 1000 75 | 76 | # optimizer paramaters 77 | LR_START = 1e-2 # comment if need to reload learning rate after training interruption 78 | WEIGHT_DECAY = 3e-5 79 | 80 | # whether to use deep-supervision loss: 81 | # a loss is placed at each stage of the UNet model 82 | USE_DEEP_SUPERVISION = False 83 | 84 | # whether to use softmax loss instead of sigmoid 85 | # should not be set to True if object classes are overlapping in the masks 86 | USE_SOFTMAX = True 87 | 88 | # training loop parameters 89 | USE_FP16 = True 90 | NUM_WORKERS = 6 91 | PIN_MEMORY = True 92 | 93 | #--------------------------------------------------------------------------- 94 | # callback setup (can be left as such) 95 | # callbacks are routines that execute periodically during the training loop 96 | 97 | # folder where the training logs will be stored, including: 98 | # - model .pth files (state_dict) 99 | # - image snapshots of model training (only if USE_IMAGE_CLBK is True) 100 | # - logs with this configuration stored in .yaml format and tensorboard logs 101 | LOG_DIR = 'logs/' 102 | 103 | SAVE_MODEL_EVERY_EPOCH = 1 104 | USE_IMAGE_CLBK = True 105 | VAL_EVERY_EPOCH = SAVE_MODEL_EVERY_EPOCH 106 | SAVE_IMAGE_EVERY_EPOCH = SAVE_MODEL_EVERY_EPOCH 107 | USE_FG_CLBK = True 108 | 109 | #--------------------------------------------------------------------------- 110 | # dataset configs 111 | 112 | TRAIN_DATASET = Dict( 113 | fct="Torchio", 114 | kwargs=Dict( 115 | img_dir = IMG_DIR, 116 | msk_dir = MSK_DIR, 117 | batch_size = BATCH_SIZE, 118 | patch_size = PATCH_SIZE, 119 | nbof_steps = 250, 120 | folds_csv = CSV_DIR, 121 | fold = 0, 122 | val_split = 0.20, 123 | train = True, 124 | use_aug = True, 125 | aug_patch_size = AUG_PATCH_SIZE, 126 | use_softmax = USE_SOFTMAX, 127 | ) 128 | ) 129 | 130 | TRAIN_DATALOADER_KWARGS = Dict( 131 | batch_size = BATCH_SIZE, 132 | drop_last = True, 133 | shuffle = True, 134 | num_workers = NUM_WORKERS, 135 | pin_memory = PIN_MEMORY, 136 | ) 137 | 138 | VAL_DATASET = Dict( 139 | fct="Torchio", 140 | kwargs = Dict( 141 | img_dir = IMG_DIR, 142 | msk_dir = MSK_DIR, 143 | batch_size = BATCH_SIZE, 144 | patch_size = PATCH_SIZE, 145 | nbof_steps = 50, 146 | folds_csv = CSV_DIR, 147 | fold = 0, 148 | val_split = 0.20, 149 | train = False, 150 | use_aug = False, 151 | use_softmax = USE_SOFTMAX, 152 | fg_rate = 0.33, 153 | ) 154 | ) 155 | 156 | VAL_DATALOADER_KWARGS = Dict( 157 | batch_size = BATCH_SIZE, # TODO: change it in the final version 158 | drop_last = False, 159 | shuffle = False, 160 | num_workers = NUM_WORKERS, 161 | pin_memory = PIN_MEMORY, 162 | ) 163 | 164 | #--------------------------------------------------------------------------- 165 | # model configs 166 | 167 | MODEL = Dict( 168 | fct="UNet3DVGGDeep", # from the register 169 | kwargs = Dict( 170 | num_pools=NUM_POOLS, 171 | num_classes=NUM_CLASSES if not USE_SOFTMAX else NUM_CLASSES+1, 172 | factor = 32, 173 | use_deep=USE_DEEP_SUPERVISION, 174 | ) 175 | ) 176 | 177 | #--------------------------------------------------------------------------- 178 | # loss configs 179 | 180 | TRAIN_LOSS = Dict( 181 | fct="DiceBCE", 182 | kwargs = Dict(name="train_loss", use_softmax=USE_SOFTMAX) 183 | ) 184 | 185 | VAL_LOSS = Dict( 186 | fct="DiceBCE", 187 | kwargs = Dict(name="val_loss", use_softmax=USE_SOFTMAX) 188 | ) 189 | 190 | #--------------------------------------------------------------------------- 191 | # metrics configs 192 | 193 | 194 | VAL_METRICS = Dict( 195 | val_iou=Dict( 196 | fct="IoU", 197 | kwargs = Dict(name="val_iou", use_softmax=USE_SOFTMAX)), 198 | val_dice=Dict( 199 | fct="Dice", 200 | kwargs=Dict(name="val_dice", use_softmax=USE_SOFTMAX)), 201 | ) 202 | 203 | #--------------------------------------------------------------------------- 204 | # trainers configs 205 | 206 | TRAINER = Dict( 207 | fct="SegTrain", 208 | kwargs=Dict(), 209 | ) 210 | 211 | VALIDATER = Dict( 212 | fct="SegVal", 213 | kwargs=Dict(), 214 | ) 215 | 216 | #--------------------------------------------------------------------------- 217 | # predictors configs 218 | 219 | PREDICTOR = Dict( 220 | fct="SegPatch", 221 | kwargs=Dict(patch_size=PATCH_SIZE, tta=True, median_spacing=MEDIAN_SPACING, use_softmax=USE_SOFTMAX, keep_biggest_only=False), 222 | ) 223 | 224 | ############################################################################ 225 | # end of config file 226 | # do not write anything in or below this field 227 | 228 | CONFIG = Dict(**globals().copy()) # stores all variables in one Dict 229 | 230 | to_remove = ['__name__', '__doc__', '__package__', 231 | '__loader__' , '__spec__', '__annotations__', 232 | '__builtins__', '__file__', '__cached__', 'Dict'] 233 | 234 | for k in to_remove: 235 | if (k in CONFIG.keys()): del CONFIG[k] 236 | 237 | ############################################################################ 238 | --------------------------------------------------------------------------------