diff --git a/model-zoo/models/mednist_ddpm/bundle/configs/common.yaml b/model-zoo/models/mednist_ddpm/bundle/configs/common.yaml new file mode 100644 index 00000000..e48b917b --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/configs/common.yaml @@ -0,0 +1,61 @@ +# This file defines common definitions used in training and inference, most importantly the network definition + +imports: +- $import os +- $import datetime +- $import torch +- $import scripts +- $import monai +- $import generative +- $import torch.distributed as dist + +image: $monai.utils.CommonKeys.IMAGE +label: $monai.utils.CommonKeys.LABEL +pred: $monai.utils.CommonKeys.PRED + +is_dist: '$dist.is_initialized()' +rank: '$dist.get_rank() if @is_dist else 0' +is_not_rank0: '$@rank > 0' +device: '$torch.device(f"cuda:{@rank}" if torch.cuda.is_available() else "cpu")' + +network_def: + _target_: generative.networks.nets.DiffusionModelUNet + spatial_dims: 2 + in_channels: 1 + out_channels: 1 + num_channels: [64, 128, 128] + attention_levels: [false, true, true] + num_res_blocks: 1 + num_head_channels: 128 + +network: $@network_def.to(@device) + +bundle_root: . +ckpt_path: $@bundle_root + '/models/model.pt' +use_amp: true +image_dim: 64 +image_size: [1, '@image_dim', '@image_dim'] +num_train_timesteps: 1000 + +base_transforms: +- _target_: LoadImaged + keys: '@image' + image_only: true +- _target_: EnsureChannelFirstd + keys: '@image' +- _target_: ScaleIntensityRanged + keys: '@image' + a_min: 0.0 + a_max: 255.0 + b_min: 0.0 + b_max: 1.0 + clip: true + +scheduler: + _target_: generative.networks.schedulers.DDPMScheduler + num_train_timesteps: '@num_train_timesteps' + +inferer: + _target_: generative.inferers.DiffusionInferer + scheduler: '@scheduler' + \ No newline at end of file diff --git a/model-zoo/models/mednist_ddpm/bundle/configs/infer.yaml b/model-zoo/models/mednist_ddpm/bundle/configs/infer.yaml new file mode 100644 index 00000000..f140c3b6 --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/configs/infer.yaml @@ -0,0 +1,38 @@ +# This defines an inference script for generating a random image to a Pytorch file + +batch_size: 1 +num_workers: 0 + +noise: $torch.rand(1,1,@image_dim,@image_dim) # create a random image every time this program is run + +out_file: "" # where to save the tensor to + +# using a lambda this defines a simple sampling function used below +sample: '$lambda x: @inferer.sample(input_noise=x, diffusion_model=@network, scheduler=@scheduler)' + +load_state: '$@network.load_state_dict(torch.load(@ckpt_path))' # command to load the saved model weights + +save_trans: + _target_: Compose + transforms: + - _target_: ScaleIntensity + minv: 0.0 + maxv: 255.0 + - _target_: ToTensor + track_meta: false + - _target_: SaveImage + output_ext: "jpg" + resample: false + output_dtype: '$torch.uint8' + separate_folder: false + output_postfix: '@out_file' + +# program to load the model weights, run `sample`, and store results to `out_file` +testing: +- '@load_state' +- '$torch.save(@sample(@noise.to(@device)), @out_file)' + +#alternative version which saves to a jpg file +testing_jpg: +- '@load_state' +- '$@save_trans(@sample(@noise.to(@device))[0])' \ No newline at end of file diff --git a/model-zoo/models/mednist_ddpm/bundle/configs/logging.conf b/model-zoo/models/mednist_ddpm/bundle/configs/logging.conf new file mode 100644 index 00000000..db85a0b9 --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/configs/logging.conf @@ -0,0 +1,21 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler + +[formatters] +keys=fullFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=fullFormatter +args=(sys.stdout,) + +[formatter_fullFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s \ No newline at end of file diff --git a/model-zoo/models/mednist_ddpm/bundle/configs/metadata.json b/model-zoo/models/mednist_ddpm/bundle/configs/metadata.json new file mode 100644 index 00000000..aef66f9f --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/configs/metadata.json @@ -0,0 +1,59 @@ +{ + "schema": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/meta_schema_20220729.json", + "version": "0.1.0", + "changelog": { + "0.1.0": "Initial version" + }, + "monai_version": "1.0.0", + "pytorch_version": "1.10.2", + "numpy_version": "1.21.2", + "optional_packages_version": {"generative":"0.1.0"}, + "task": "MedNIST Hand Generation", + "description": "", + "authors": "Walter Hugo Lopez Pinaya, Mark Graham, and Eric Kerfoot", + "copyright": "Copyright (c) KCL", + "references": [], + "intended_use": "This is suitable for research purposes only", + "image_classes": "Single channel magnitude data", + "data_source": "MedNIST", + "network_data_format": { + "inputs": { + "image": { + "type": "image", + "format": "magnitude", + "modality": "xray", + "num_channels": 1, + "spatial_shape": [ + 1, + 64, + 64 + ], + "dtype": "float32", + "value_range": [], + "is_patch_data": false, + "channel_def": { + "0": "image" + } + } + }, + "outputs": { + "pred": { + "type": "image", + "format": "magnitude", + "modality": "xray", + "num_channels": 1, + "spatial_shape": [ + 1, + 64, + 64 + ], + "dtype": "float32", + "value_range": [], + "is_patch_data": false, + "channel_def": { + "0": "image" + } + } + } + } +} diff --git a/model-zoo/models/mednist_ddpm/bundle/configs/train.yaml b/model-zoo/models/mednist_ddpm/bundle/configs/train.yaml new file mode 100644 index 00000000..739b3c1f --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/configs/train.yaml @@ -0,0 +1,157 @@ +# This defines the training script for the network + +# choose a new directory for every run +output_dir: $datetime.datetime.now().strftime('./results/output_%y%m%d_%H%M%S') +dataset_dir: ./data + +train_data: + _target_ : MedNISTDataset + root_dir: '@dataset_dir' + section: training + download: true + progress: false + seed: 0 + +val_data: + _target_ : MedNISTDataset + root_dir: '@dataset_dir' + section: validation + download: true + progress: false + seed: 0 + +train_datalist: '$[{"image": item["image"]} for item in @train_data.data if item["class_name"] == "Hand"]' +val_datalist: '$[{"image": item["image"]} for item in @val_data.data if item["class_name"] == "Hand"]' + +batch_size: 8 +num_substeps: 1 +num_workers: 4 +use_thread_workers: false + +lr: 0.000025 +rand_prob: 0.5 +num_epochs: 75 +val_interval: 5 +save_interval: 5 + +train_transforms: +- _target_: RandAffined + keys: '@image' + rotate_range: + - ['$-np.pi / 36', '$np.pi / 36'] + - ['$-np.pi / 36', '$np.pi / 36'] + translate_range: + - [-1, 1] + - [-1, 1] + scale_range: + - [-0.05, 0.05] + - [-0.05, 0.05] + spatial_size: [64, 64] + padding_mode: "zeros" + prob: '@rand_prob' + +train_ds: + _target_: Dataset + data: $@train_datalist + transform: + _target_: Compose + transforms: '$@base_transforms + @train_transforms' + +train_loader: + _target_: ThreadDataLoader + dataset: '@train_ds' + batch_size: '@batch_size' + repeats: '@num_substeps' + num_workers: '@num_workers' + use_thread_workers: '@use_thread_workers' + persistent_workers: '$@num_workers > 0' + shuffle: true + +val_ds: + _target_: Dataset + data: $@val_datalist + transform: + _target_: Compose + transforms: '@base_transforms' + +val_loader: + _target_: DataLoader + dataset: '@val_ds' + batch_size: '@batch_size' + num_workers: '@num_workers' + persistent_workers: '$@num_workers > 0' + shuffle: false + +lossfn: + _target_: torch.nn.MSELoss + +optimizer: + _target_: torch.optim.Adam + params: $@network.parameters() + lr: '@lr' + +prepare_batch: + _target_: scripts.DiffusionPrepareBatch + num_train_timesteps: '@num_train_timesteps' + +val_handlers: +- _target_: StatsHandler + name: train_log + output_transform: '$lambda x: None' + _disabled_: '@is_not_rank0' + +evaluator: + _target_: SupervisedEvaluator + device: '@device' + val_data_loader: '@val_loader' + network: '@network' + amp: '@use_amp' + inferer: '@inferer' + prepare_batch: '@prepare_batch' + key_val_metric: + val_mean_abs_error: + _target_: MeanAbsoluteError + output_transform: $monai.handlers.from_engine([@pred, @label]) + metric_cmp_fn: '$scripts.inv_metric_cmp_fn' + val_handlers: '$list(filter(bool, @val_handlers))' + +handlers: +- _target_: CheckpointLoader + _disabled_: $not os.path.exists(@ckpt_path) + load_path: '@ckpt_path' + load_dict: + model: '@network' +- _target_: ValidationHandler + validator: '@evaluator' + epoch_level: true + interval: '@val_interval' +- _target_: CheckpointSaver + save_dir: '@output_dir' + save_dict: + model: '@network' + save_interval: '@save_interval' + save_final: true + epoch_level: true + _disabled_: '@is_not_rank0' + +trainer: + _target_: SupervisedTrainer + max_epochs: '@num_epochs' + device: '@device' + train_data_loader: '@train_loader' + network: '@network' + loss_function: '@lossfn' + optimizer: '@optimizer' + inferer: '@inferer' + prepare_batch: '@prepare_batch' + key_train_metric: + train_acc: + _target_: MeanSquaredError + output_transform: $monai.handlers.from_engine([@pred, @label]) + metric_cmp_fn: '$scripts.inv_metric_cmp_fn' + train_handlers: '$list(filter(bool, @handlers))' + amp: '@use_amp' + +training: +- '$monai.utils.set_determinism(0)' +- '$@trainer.run()' diff --git a/model-zoo/models/mednist_ddpm/bundle/configs/train_multigpu.yaml b/model-zoo/models/mednist_ddpm/bundle/configs/train_multigpu.yaml new file mode 100644 index 00000000..2811612f --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/configs/train_multigpu.yaml @@ -0,0 +1,30 @@ +# This can be mixed in with the training script to enable multi-GPU training + +network: + _target_: torch.nn.parallel.DistributedDataParallel + module: $@network_def.to(@device) + device_ids: ['@device'] + find_unused_parameters: true + +tsampler: + _target_: DistributedSampler + dataset: '@train_ds' + even_divisible: true + shuffle: true +train_loader#sampler: '@tsampler' +train_loader#shuffle: false + +vsampler: + _target_: DistributedSampler + dataset: '@val_ds' + even_divisible: false + shuffle: false +val_loader#sampler: '@vsampler' + +training: +- $import torch.distributed as dist +- $dist.init_process_group(backend='nccl') +- $torch.cuda.set_device(@device) +- $monai.utils.set_determinism(seed=123), +- $@trainer.run() +- $dist.destroy_process_group() \ No newline at end of file diff --git a/model-zoo/models/mednist_ddpm/bundle/docs/2d_ddpm_bundle_tutorial.ipynb b/model-zoo/models/mednist_ddpm/bundle/docs/2d_ddpm_bundle_tutorial.ipynb new file mode 100644 index 00000000..4cd3f5d4 --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/docs/2d_ddpm_bundle_tutorial.ipynb @@ -0,0 +1,317 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c54f5831-58eb-4f9e-bb8a-2c2a6536a658", + "metadata": {}, + "source": [ + "# Denoising Diffusion Probabilistic Models with MedNIST Dataset Bundle \n", + "\n", + "This notebook discusses and uses the MONAI bundle it's included in for generating images from the MedNIST dataset using diffusion models. This is based off the 2d_ddpm_tutorial_ignite.ipynb notebook with a few changes.\n", + "\n", + "The bundle defines training and inference scripts whose use will be described here along with visualisations. The assumption with this notebook is that it's run within the bundle's `docs` directory and that the environment it runs in has `MONAI` and `GenerativeModels` installed. The command lines given are known to work in `bash` however may be problematic in Windows.\n", + "\n", + "First thing to do is import libraries and verify MONAI is present:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6d32f8a4-2bfe-4cfb-9abd-033b0c6080e6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MONAI version: 1.1.0+45.g1a018a7b\n", + "Numpy version: 1.21.5\n", + "Pytorch version: 1.12.1\n", + "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n", + "MONAI rev id: 1a018a7b3034a86360d999a6bcc796bad330bba4\n", + "MONAI __file__: /home/localek10/workspace/monai/MONAI_mine/monai/__init__.py\n", + "\n", + "Optional dependencies:\n", + "Pytorch Ignite version: 0.4.8\n", + "ITK version: 5.2.1\n", + "Nibabel version: 4.0.2\n", + "scikit-image version: 0.19.2\n", + "Pillow version: 9.2.0\n", + "Tensorboard version: 2.9.0\n", + "gdown version: 4.5.1\n", + "TorchVision version: 0.13.1\n", + "tqdm version: 4.64.0\n", + "lmdb version: 1.2.1\n", + "psutil version: 5.9.0\n", + "pandas version: 1.4.3\n", + "einops version: 0.6.0\n", + "transformers version: 4.18.0\n", + "mlflow version: 1.28.0\n", + "pynrrd version: 0.4.2\n", + "\n", + "For details about installing the optional dependencies, please visit:\n", + " https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies\n", + "\n" + ] + } + ], + "source": [ + "import os\n", + "import shutil\n", + "import tempfile\n", + "from pathlib import Path\n", + "\n", + "import torch\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import monai\n", + "from monai.bundle import ConfigParser\n", + "\n", + "# path to the bundle directory, this assumes you're running the notebook in its directory\n", + "bundle_root = str(Path(\".\").absolute().parent)\n", + "\n", + "monai.config.print_config()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d6fc6592-cb51-4527-97ee-add5d1cdbeb4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/tmp/tmpw33bol9_\n" + ] + } + ], + "source": [ + "directory = os.environ.get(\"MONAI_DATA_DIRECTORY\")\n", + "dataset_dir = tempfile.mkdtemp() if directory is None else directory\n", + "print(dataset_dir)" + ] + }, + { + "cell_type": "markdown", + "id": "678d2e51-dc2d-4ad9-a4c0-14a6f900398b", + "metadata": {}, + "source": [ + "A bundle can be run on the command line using the Fire library or by parsing the configuration manually then getting parsed content objects. The following is the command to train the network for the default number of epochs. It will define values in the config files which need to be set for a particular run, such as the dataset directory created above, and setting the PYTHONPATH variable. The configuration for this bundle is split into 4 yaml files, one having common definitions for training and inference, one to enable multi-GPU training, and one each for training and inference. Their combinations determine what your final configuration is, in this case the common and train files produce a training script. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d52a4ae9-0d6d-4bc4-a5b5-f84470711f2d", + "metadata": {}, + "outputs": [], + "source": [ + "# multiple config files need to be specified this way with '' quotes, variable used in command line must be in \"\" quotes\n", + "configs=f\"'{bundle_root}/configs/common.yaml', '{bundle_root}/configs/train.yaml'\"\n", + "\n", + "!PYTHONPATH={bundle_root} python -m monai.bundle run training \\\n", + " --meta_file {bundle_root}/configs/metadata.json \\\n", + " --config_file \"{configs}\" \\\n", + " --logging_file {bundle_root}/configs/logging.conf \\\n", + " --bundle_root {bundle_root} \\\n", + " --dataset_dir {dataset_dir}" + ] + }, + { + "cell_type": "markdown", + "id": "5030732c-deb5-448a-b575-385bda0fa308", + "metadata": {}, + "source": [ + "The test inference script can then be invoked as such to produce an output tensor saved to the given file with a randomly generated image. The `ckpt_path` value should point to the final checkpoint file created during the above training run, which will be in a subdirectory of `./result`. The training script's default behaviour is to create a new timestamped subdirectory in `./result` for every new run, this can be explicitly set by providing a `output_dir` value on the command line." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "40e6a3e9-3984-44b0-ba9a-5b8d58c7ea2d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2023-02-16 21:00:18,139 - INFO - --- input summary of monai.bundle.scripts.run ---\n", + "2023-02-16 21:00:18,139 - INFO - > runner_id: 'testing'\n", + "2023-02-16 21:00:18,139 - INFO - > meta_file: '/home/localek10/workspace/monai/GenerativeModels/model-zoo/models/mednist_ddpm/bundle/configs/metadata.json'\n", + "2023-02-16 21:00:18,139 - INFO - > config_file: ('/home/localek10/workspace/monai/GenerativeModels/model-zoo/models/mednist_ddpm/bundle/configs/common.yaml',\n", + " '/home/localek10/workspace/monai/GenerativeModels/model-zoo/models/mednist_ddpm/bundle/configs/infer.yaml')\n", + "2023-02-16 21:00:18,139 - INFO - > ckpt_path: './results/output_230215_174009/model_final_iteration=75000.pt'\n", + "2023-02-16 21:00:18,140 - INFO - > bundle_root: '/home/localek10/workspace/monai/GenerativeModels/model-zoo/models/mednist_ddpm/bundle'\n", + "2023-02-16 21:00:18,140 - INFO - > out_file: 'test.pt'\n", + "2023-02-16 21:00:18,140 - INFO - ---\n", + "\n", + "\n", + "100%|███████████████████████████████████████| 1000/1000 [00:10<00:00, 97.10it/s]\n", + "[[[], []], null]\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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fvGL2dSpU7O+vr40HT4Lf/zHf5w9/PDD2a9+9ats79692a233ppdeuml2dGjR4ttKmGcTz75ZPbv//7v2YEDB7IDBw5k9913X1ZTU5P96le/yrKsMsb4h37xi19kH/vYx7Krr746W7FiRXF7JYzz/vvvz2bOnJkdOnSo+NPT01P8fSWMMcuy7Pe//302Y8aM7Gtf+1r2P//zP9nBgwez//iP/8heffXVYpsUYy3bSeiP/uiPsmXLlg3b9qlPfSr77ne/m6hHcZ05CZ06dSprbm7OHnzwweK2EydOZI2Njdnf//3fJ+hhHD09PZmkrLOzM8uyyh1nlmXZhRdemP3DP/xDxY2xv78/a21tzTo6OrL58+cXJ6FKGef999+fXXPNNcHfVcoYsyzLvvOd72Q333yz+ftUYy3Lj+OGhoa0Z88eLVq0aNj2RYsWadeuXYl6la+DBw+qu7t72JgLhYLmz58/rsd8ekmOiy66SFJljvPkyZPasWOHjh07prlz51bcGO+++27deuut+vznPz9seyWN85VXXtG0adPU0tKiL33pS3rttdckVdYYn3zySc2ePVtf+MIXNGXKFF177bXaunVr8fepxlqWk9Dbb7+tkydPqqmpadj2pqYmdXd3J+pVvk6Pq5LGnGWZ7r33Xt18882aNWuWpMoa5759+3TeeeepUCho2bJleuKJJ3TllVdW1Bh37NihX/7yl2pvby/5XaWMc86cOXr00Uf1zDPPaOvWreru7ta8efP0zjvvVMwYJem1117T5s2b1draqmeeeUbLli3Tt7/9bT366KOS0j2fZbeUwx86vZTDaVmWlWyrNJU05nvuuUcvvfSS/vu//7vkd5Uwzk9+8pPau3evjhw5oscee0x33nmnOjs7i78f72Ps6urSihUrtHPnTk2aNMlsN97HuXjx4uJ/X3XVVZo7d64+8YlPaNu2bbrxxhsljf8xSu+v1TZ79my1tbVJkq699lrt379fmzdv1l/8xV8U233YYy3LK6GPfOQjmjhxYsns29PTUzJLV4rTaZxKGfO3vvUtPfnkk/r5z39eXB9Kqqxx1tbW6rLLLtPs2bPV3t6ua665Rj/60Y8qZox79uxRT0+Prr/+elVXV6u6ulqdnZ36u7/7O1VXVxfHMt7Heab6+npdddVVeuWVVyrmuZSkqVOn6sorrxy27YorrtAbb7whKd1rsywnodraWl1//fXq6OgYtr2jo0Pz5s1L1Kt8tbS0qLm5ediYh4aG1NnZOa7GnGWZ7rnnHj3++OP62c9+ppaWlmG/r5RxhmRZpsHBwYoZ4y233KJ9+/Zp7969xZ/Zs2frjjvu0N69e/Xxj3+8IsZ5psHBQf3617/W1KlTK+a5lKSbbrqp5OsSv/nNbzRjxgxJCV+buUUexuh0RPsf//Efs5dffjlbuXJlVl9fn/32t79N3bWz1t/fn7344ovZiy++mEnKNmzYkL344ovF2PmDDz6YNTY2Zo8//ni2b9++7Mtf/vK4i4J+85vfzBobG7Nnn312WOT1+PHjxTaVMM7Vq1dnzz33XHbw4MHspZdeyu67775swoQJ2c6dO7Msq4wxhvxhOi7LKmOcf/3Xf509++yz2WuvvZY9//zz2Z/+6Z9mDQ0NxfeaShhjlr0fs6+urs6+//3vZ6+88kr2T//0T1ldXV32k5/8pNgmxVjLdhLKsix76KGHshkzZmS1tbXZddddV4z5jlc///nPM0klP3feeWeWZe9HJO+///6subk5KxQK2Wc+85ls3759aTvtFBqfpOzhhx8utqmEcf7lX/5l8dz86Ec/mt1yyy3FCSjLKmOMIWdOQpUwztPfhampqcmmTZuWLVmyJNu/f3/x95UwxtP+7d/+LZs1a1ZWKBSyT33qU9mWLVuG/T7FWFlPCACQTFneEwIAnBuYhAAAyTAJAQCSYRICACTDJAQASIZJCACQDJMQACAZJiEAQDJMQgCAZJiEAADJMAkBAJL5/wB3dS17EkusPwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "configs=f\"'{bundle_root}/configs/common.yaml', '{bundle_root}/configs/infer.yaml'\"\n", + "\n", + "!PYTHONPATH={bundle_root} python -m monai.bundle run testing \\\n", + " --meta_file {bundle_root}/configs/metadata.json \\\n", + " --config_file \"{configs}\" \\\n", + " --ckpt_path ./results/output_230215_174009/model_final_iteration=75000.pt \\\n", + " --bundle_root {bundle_root} \\\n", + " --out_file test.pt\n", + "\n", + "test = torch.load(\"test.pt\", map_location=\"cpu\")\n", + "\n", + "plt.imshow(test[0, 0], vmin=0, vmax=1, cmap=\"gray\")" + ] + }, + { + "cell_type": "markdown", + "id": "f581c36e-4033-4005-8969-76205470588e", + "metadata": {}, + "source": [ + "The same can be done by creating the parser object, filling in its configuration, then resolving the Python objects from the constructed bundle data:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "cf8438b3-4c7d-48c4-bb41-ed7def73753f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 1000/1000 [00:09<00:00, 101.06it/s]\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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2NeyHYT+4A/+IFuyZMmItssuuyz79re/nahHcZ04CB07dixrbm7OHnrooWLb4cOHs8bGxuwf/uEfEvQwjt7e3kxS1tXVlWVZ9e5nlmXZOeeck/3jP/5j1e3jwMBA1tramnV2dmbz5s0rDkLVsp8PPPBAdvXVVwdfq5Z9zLIs+9a3vpXdfPPN5uup9rUsv44bHh7Wzp07tWDBghHtCxYs0Pbt2xP1Kl/79u1TT0/PiH0uFAqaN29eRe9zX1+fJOncc8+VVJ37efToUW3dulWHDh3SnDlzqm4f77nnHt122236/Oc/P6K9mvbzlVde0dSpU9XS0qIvfelLeu211yRV1z4+9dRTmjVrlr7whS9o8uTJuvbaa7Vp06bi66n2tSwHobfffltHjx5VU1PTiPampib19PQk6lW+ju9XNe1zlmW67777dPPNN2vmzJmSqms/d+/erbPOOkuFQkFLlizRk08+qSuuuKKq9nHr1q36xS9+oY6OjpLXqmU/Z8+erccee0zPPPOMNm3apJ6eHs2dO1fvvPNO1eyjJL322mvasGGDWltb9cwzz2jJkiX65je/qccee0xSuvNZdlM5fNSJMwdmWVYRMz6ejmra53vvvVcvvfSS/vu//7vktWrYz0svvVS7du3Se++9p8cff1x33XWXurq6iq9X+j52d3dr2bJl2rZtmyZMmGAuV+n7uXDhwuJ/X3nllZozZ44uvvhibd68WTfeeKOkyt9H6cO52mbNmqX29nZJ0rXXXqs9e/Zow4YN+ou/+Ivicp/0vpblndD555+vsWPHloy+vb29JaN0tTiexqmWff7GN76hp556Sj/72c+K80NJ1bWftbW1+sxnPqNZs2apo6NDV199tX7wgx9UzT7u3LlTvb29uv766zVu3DiNGzdOXV1d+vu//3uNGzeuuC+Vvp8nmjhxoq688kq98sorVXMuJWnKlCm64oorRrRdfvnlevPNNyWle2+W5SBUW1ur66+/Xp2dnSPaOzs7NXfu3ES9yldLS4uam5tH7PPw8LC6uroqap+zLNO9996rJ554Qj/96U/V0tIy4vVq2c+QLMs0NDRUNft46623avfu3dq1a1fxZ9asWbrzzju1a9cuffrTn66K/TzR0NCQfvWrX2nKlClVcy4l6aabbir5c4lf//rXmj59uqSE783cIg+n6XhE+5/+6Z+yl19+OVu+fHk2ceLE7PXXX0/dtVM2MDCQvfjii9mLL76YScrWrl2bvfjii8XY+UMPPZQ1NjZmTzzxRLZ79+7sy1/+csVFQb/+9a9njY2N2bPPPjsi8vr+++8Xl6mG/Vy5cmX23HPPZfv27cteeuml7P7778/GjBmTbdu2Lcuy6tjHkI+m47KsOvbzb/7mb7Jnn302e+2117Lnn38++9M//dOsoaGh+FlTDfuYZR/G7MeNG5d997vfzV555ZXsn//5n7P6+vrsRz/6UXGZFPtatoNQlmXZww8/nE2fPj2rra3NrrvuumLMt1L97Gc/yySV/Nx1111Zln0YkXzggQey5ubmrFAoZJ/97Gez3bt3p+20U2j/JGWPPPJIcZlq2M+//Mu/LF6bF1xwQXbrrbcWB6Asq459DDlxEKqG/Tz+tzDjx4/Ppk6dmi1atCjbs2dP8fVq2Mfj/v3f/z2bOXNmVigUsssuuyzbuHHjiNdT7CvzCQEAkinLZ0IAgDMDgxAAIBkGIQBAMgxCAIBkGIQAAMkwCAEAkmEQAgAkwyAEAEiGQQgAkAyDEAAgGQYhAEAy/w/fi81VGZdpEgAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import sys\n", + "\n", + "sys.path.append(bundle_root) # make sure we load the script files we need\n", + "\n", + "# configure the parser from the bundle's information\n", + "cp = ConfigParser()\n", + "cp.read_meta(f\"{bundle_root}/configs/metadata.json\")\n", + "cp.read_config([f\"{bundle_root}/configs/common.yaml\", f\"{bundle_root}/configs/infer.yaml\"])\n", + "cp[\"bundle_root\"] = bundle_root\n", + "cp[\"ckpt_path\"] = \"./results/output_230215_174009/model_final_iteration=75000.pt\"\n", + "\n", + "cp.get_parsed_content(\"load_state\") # load the saved state from the checkpoint just be resolving this value\n", + "\n", + "device = cp.get_parsed_content(\"device\") # device used by the bundle\n", + "sample = cp.get_parsed_content(\"sample\") # test sampling function\n", + "\n", + "image_dim = cp[\"image_dim\"] # get the stored dimension value, no need to resolve anything\n", + "\n", + "noise = torch.rand(1, 1, image_dim, image_dim).to(device) # or cp.get_parsed_content(\"noise\")\n", + "\n", + "test = sample(noise)\n", + "\n", + "plt.imshow(test[0, 0].cpu(), vmin=0, vmax=1, cmap=\"gray\")" + ] + }, + { + "cell_type": "markdown", + "id": "2feab4e5-2745-4d35-9eec-a2bb8340cf51", + "metadata": {}, + "source": [ + "Multi-GPU can be enabled by including the `train_multigpu.yaml` configuration file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "173cda1c-ac90-410f-b34d-b6cbb0044c7a", + "metadata": {}, + "outputs": [], + "source": [ + "configs=f\"'{bundle_root}/configs/common.yaml', '{bundle_root}/configs/train.yaml', '{bundle_root}/configs/train_multigpu.yaml'\"\n", + "\n", + "!PYTHONPATH={bundle_root} torchrun --standalone --nnodes=1 --nproc_per_node=2 -m monai.bundle run training \\\n", + " --meta_file {bundle_root}/configs/metadata.json \\\n", + " --config_file \"{configs}\" \\\n", + " --logging_file {bundle_root}/configs/logging.conf \\\n", + " --bundle_root {bundle_root} \\\n", + " --dataset_dir {dataset_dir}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb719023-8250-43c4-ab10-911829332498", + "metadata": {}, + "outputs": [], + "source": [ + "if directory is None:\n", + " shutil.rmtree(dataset_dir)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:monai]", + "language": "python", + "name": "conda-env-monai-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/model-zoo/models/mednist_ddpm/bundle/docs/README.md b/model-zoo/models/mednist_ddpm/bundle/docs/README.md new file mode 100644 index 00000000..6483aff5 --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/docs/README.md @@ -0,0 +1,9 @@ + +# MedNIST DDPM Example Bundle + +This implements roughly equivalent code to the "Denoising Diffusion Probabilistic Models with MedNIST Dataset" example notebook. This includes scripts for training with single or multiple GPUs and a visualisation notebook. + +The files included here demonstrate how to use the bundle: + * [2d_ddpm_bundle_tutorial.ipynb](./2d_ddpm_bundle_tutorial.ipynb) - demonstrates command line and in-code invocation of the bundle's training and inference scripts + * [sub_train.sh](sub_train.sh) - SLURM submission script example for training + * [sub_train_multigpu.sh](sub_train_multigpu.sh) - SLURM submission script example for training with multiple GPUs diff --git a/model-zoo/models/mednist_ddpm/bundle/docs/sub_train.sh b/model-zoo/models/mednist_ddpm/bundle/docs/sub_train.sh new file mode 100755 index 00000000..237b16f5 --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/docs/sub_train.sh @@ -0,0 +1,34 @@ +#! /bin/bash +#SBATCH --nodes=1 +#SBATCH -J mednist_train +#SBATCH -c 4 +#SBATCH --gres=gpu:1 +#SBATCH --time=2:00:00 +#SBATCH -p small + +set -v + +# change this if run submitted from a different directory +export BUNDLE="$(pwd)/.." + +# have to set PYTHONPATH to find MONAI and GenerativeModels as well as the bundle's script directory +export PYTHONPATH="$HOME/MONAI:$HOME/GenerativeModels:$BUNDLE" + +# change this to load a checkpoint instead of started from scratch +CKPT=none + +CONFIG="'$BUNDLE/configs/common.yaml', '$BUNDLE/configs/train.yaml'" + +# change this to point to where MedNIST is located +DATASET="$(pwd)" + +# it's useful to include the configuration in the log file +cat "$BUNDLE/configs/common.yaml" +cat "$BUNDLE/configs/train.yaml" + +python -m monai.bundle run training \ + --meta_file "$BUNDLE/configs/metadata.json" \ + --config_file "$CONFIG" \ + --logging_file "$BUNDLE/configs/logging.conf" \ + --bundle_root "$BUNDLE" \ + --dataset_dir "$DATASET" diff --git a/model-zoo/models/mednist_ddpm/bundle/docs/sub_train_multigpu.sh b/model-zoo/models/mednist_ddpm/bundle/docs/sub_train_multigpu.sh new file mode 100644 index 00000000..7c424af0 --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/docs/sub_train_multigpu.sh @@ -0,0 +1,36 @@ +#! /bin/bash +#SBATCH --nodes=1 +#SBATCH -J mednist_train +#SBATCH -c 4 +#SBATCH --gres=gpu:2 +#SBATCH --time=2:00:00 +#SBATCH -p big + +set -v + +# change this if run submitted from a different directory +export BUNDLE="$(pwd)/.." + +# have to set PYTHONPATH to find MONAI and GenerativeModels as well as the bundle's script directory +export PYTHONPATH="$HOME/MONAI:$HOME/GenerativeModels:$BUNDLE" + +# change this to load a checkpoint instead of started from scratch +CKPT=none + +CONFIG="'$BUNDLE/configs/common.yaml', '$BUNDLE/configs/train.yaml', '$BUNDLE/configs/train_multigpu.yaml'" + +# change this to point to where MedNIST is located +DATASET="$(pwd)" + +# it's useful to include the configuration in the log file +cat "$BUNDLE/configs/common.yaml" +cat "$BUNDLE/configs/train.yaml" +cat "$BUNDLE/configs/train_multigpu.yaml" + +# remember to change arguments to match how many nodes and GPUs you have +torchrun --standalone --nnodes=1 --nproc_per_node=2 -m monai.bundle run training \ + --meta_file "$BUNDLE/configs/metadata.json" \ + --config_file "$CONFIG" \ + --logging_file "$BUNDLE/configs/logging.conf" \ + --bundle_root "$BUNDLE" \ + --dataset_dir "$DATASET" \ No newline at end of file diff --git a/model-zoo/models/mednist_ddpm/bundle/scripts/__init__.py b/model-zoo/models/mednist_ddpm/bundle/scripts/__init__.py new file mode 100644 index 00000000..344830d2 --- /dev/null +++ b/model-zoo/models/mednist_ddpm/bundle/scripts/__init__.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from typing import Dict, Mapping, Optional, Union + +import torch +from monai.engines import PrepareBatch, default_prepare_batch + + +class DiffusionPrepareBatch(PrepareBatch): + """ + This class is used as a callable for the `prepare_batch` parameter of engine classes for diffusion training. + + Assuming a supervised training process, it will generate a noise field using `get_noise` for an input image, and + return the image and noise field as the image/target pair plus the noise field the kwargs under the key "noise". + This assumes the inferer being used in conjunction with this class expects a "noise" parameter to be provided. + + If the `condition_name` is provided, this must refer to a key in the input dictionary containing the condition + field to be passed to the inferer. This will appear in the keyword arguments under the key "condition". + + """ + + def __init__(self, num_train_timesteps: int, condition_name: str | None = None) -> None: + self.condition_name = condition_name + self.num_train_timesteps = num_train_timesteps + + def get_noise(self, images: torch.Tensor) -> torch.Tensor: + """Returns the noise tensor for input tensor `images`, override this for different noise distributions.""" + return torch.randn_like(images) + + def get_timesteps(self, images: torch.Tensor) -> torch.Tensor: + return torch.randint(0, self.num_train_timesteps, (images.shape[0],), device=images.device).long() + + def __call__( + self, + batchdata: Dict[str, torch.Tensor], + device: Union[str, torch.device] | None = None, + non_blocking: bool = False, + **kwargs, + ): + images, _ = default_prepare_batch(batchdata, device, non_blocking, **kwargs) + noise = self.get_noise(images).to(device, non_blocking=non_blocking, **kwargs) + timesteps = self.get_timesteps(images).to(device, non_blocking=non_blocking, **kwargs) + + kwargs = {"noise": noise, "timesteps": timesteps} + + if self.condition_name is not None and isinstance(batchdata, Mapping): + kwargs["conditioning"] = batchdata[self.condition_name].to(device, non_blocking=non_blocking, **kwargs) + + # return input, target, arguments, and keyword arguments where noise is the target and also a keyword value + return images, noise, (), kwargs + + +def inv_metric_cmp_fn(current_metric: float, prev_best: float) -> bool: + """ + This inverts comparison for those metrics which reduce like loss values, such that the lower one is better. + + Args: + current_metric: metric value of current round computation. + prev_best: the best metric value of previous rounds to compare with. + """ + return current_metric < prev_best diff --git a/tests/utils.py b/tests/utils.py index cb1cabdc..601bd9e9 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,8 @@ # COPIED FROM https://github.com/Project-MONAI/MONAI/blob/fdd07f36ecb91cfcd491533f4792e1a67a9f89fc/tests/utils.py # --------------------------------------------------------------- +from __future__ import annotations + # Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,7 +13,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - from __future__ import annotations import copy