├── .gitattributes ├── .github └── workflows │ ├── deploy.yaml │ └── test.yaml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── diffdrrdata ├── __init__.py ├── _modidx.py ├── deepfluoro.py ├── ljubljana.py └── utils.py ├── environment.yml ├── index_files ├── figure-commonmark │ ├── cell-2-output-1.png │ └── cell-3-output-1.png └── figure-html │ ├── cell-2-output-1.png │ └── cell-3-output-1.png ├── notebooks ├── 00_deepfluoro.ipynb ├── 01_ljubljana.ipynb ├── 02_utils.ipynb ├── _quarto.yml ├── deepfluoro_camera_poses.html ├── favicon.png ├── index.ipynb ├── nbdev.yml ├── sidebar.yml └── styles.css ├── settings.ini └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ipynb linguist-vendored=true 2 | *.ipynb merge=nbdev-merge 3 | *.html linguist-documentation 4 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | permissions: 4 | contents: write 5 | pages: write 6 | 7 | on: 8 | push: 9 | branches: [ "main", "master" ] 10 | workflow_dispatch: 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v3 18 | 19 | - name: Activate conda env with environment.yml 20 | uses: mamba-org/setup-micromamba@v1 21 | with: 22 | environment-file: environment.yml 23 | cache-environment: true 24 | post-cleanup: 'all' 25 | 26 | - name: Install nbdev 27 | shell: bash -l {0} 28 | run: | 29 | pip install -U nbdev 30 | 31 | - name: Doing editable install 32 | shell: bash -l {0} 33 | run: | 34 | test -f setup.py && pip install -e ".[dev]" 35 | 36 | - name: Run nbdev_docs 37 | shell: bash -l {0} 38 | run: | 39 | nbdev_docs 40 | 41 | - name: Deploy to GitHub Pages 42 | uses: peaceiris/actions-gh-pages@v3 43 | with: 44 | github_token: ${{ github.token }} 45 | force_orphan: true 46 | publish_dir: ./_docs 47 | # The following lines assign commit authorship to the official GH-Actions bot for deploys to `gh-pages` branch. 48 | # You can swap them out with your own user credentials. 49 | user_name: github-actions[bot] 50 | user_email: 41898282+github-actions[bot]@users.noreply.github.com -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [workflow_dispatch, pull_request, push] 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout repository 9 | uses: actions/checkout@v3 10 | 11 | - name: Activate conda env with environment.yml 12 | uses: mamba-org/setup-micromamba@v1 13 | with: 14 | environment-file: environment.yml 15 | cache-environment: true 16 | post-cleanup: 'all' 17 | 18 | - name: Install nbdev 19 | shell: bash -l {0} 20 | run: | 21 | pip install -U nbdev 22 | 23 | - name: Doing editable install 24 | shell: bash -l {0} 25 | run: | 26 | test -f setup.py && pip install -e ".[dev]" 27 | 28 | - name: Check we are starting with clean git checkout 29 | shell: bash -l {0} 30 | run: | 31 | if [[ `git status --porcelain -uno` ]]; then 32 | git diff 33 | echo "git status is not clean" 34 | false 35 | fi 36 | 37 | - name: Trying to strip out notebooks 38 | shell: bash -l {0} 39 | run: | 40 | nbdev_clean 41 | git status -s # display the status to see which nbs need cleaning up 42 | if [[ `git status --porcelain -uno` ]]; then 43 | git status -uno 44 | echo -e "!!! Detected unstripped out notebooks\n!!!Remember to run nbdev_install_hooks" 45 | echo -e "This error can also happen if you are using an older version of nbdev relative to what is in CI. Please try to upgrade nbdev with the command `pip install -U nbdev`" 46 | false 47 | fi 48 | 49 | - name: Run nbdev_export 50 | shell: bash -l {0} 51 | run: | 52 | nbdev_export 53 | if [[ `git status --porcelain -uno` ]]; then 54 | echo "::error::Notebooks and library are not in sync. Please run nbdev_export." 55 | git status -uno 56 | git diff 57 | exit 1; 58 | fi 59 | 60 | - name: Run nbdev_test 61 | shell: bash -l {0} 62 | run: | 63 | nbdev_test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _docs/ 2 | _proc/ 3 | 4 | *.bak 5 | .gitattributes 6 | .last_checked 7 | .gitconfig 8 | *.bak 9 | *.log 10 | *~ 11 | ~* 12 | _tmp* 13 | tmp* 14 | tags 15 | *.pkg 16 | 17 | # Byte-compiled / optimized / DLL files 18 | __pycache__/ 19 | *.py[cod] 20 | *$py.class 21 | 22 | # C extensions 23 | *.so 24 | 25 | # Distribution / packaging 26 | .Python 27 | env/ 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | *.egg-info/ 41 | .installed.cfg 42 | *.egg 43 | 44 | # PyInstaller 45 | # Usually these files are written by a python script from a template 46 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 47 | *.manifest 48 | *.spec 49 | 50 | # Installer logs 51 | pip-log.txt 52 | pip-delete-this-directory.txt 53 | 54 | # Unit test / coverage reports 55 | htmlcov/ 56 | .tox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | .hypothesis/ 64 | 65 | # Translations 66 | *.mo 67 | *.pot 68 | 69 | # Django stuff: 70 | *.log 71 | local_settings.py 72 | 73 | # Flask stuff: 74 | instance/ 75 | .webassets-cache 76 | 77 | # Scrapy stuff: 78 | .scrapy 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | target/ 85 | 86 | # Jupyter Notebook 87 | .ipynb_checkpoints 88 | 89 | # pyenv 90 | .python-version 91 | 92 | # celery beat schedule file 93 | celerybeat-schedule 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # dotenv 99 | .env 100 | 101 | # virtualenv 102 | .venv 103 | venv/ 104 | ENV/ 105 | 106 | # Spyder project settings 107 | .spyderproject 108 | .spyproject 109 | 110 | # Rope project settings 111 | .ropeproject 112 | 113 | # mkdocs documentation 114 | /site 115 | 116 | # mypy 117 | .mypy_cache/ 118 | 119 | .vscode 120 | *.swp 121 | 122 | # osx generated files 123 | .DS_Store 124 | .DS_Store? 125 | .Trashes 126 | ehthumbs.db 127 | Thumbs.db 128 | .idea 129 | 130 | # pytest 131 | .pytest_cache 132 | 133 | # tools/trust-doc-nbs 134 | docs_src/.last_checked 135 | 136 | # symlinks to fastai 137 | docs_src/fastai 138 | tools/fastai 139 | 140 | # link checker 141 | checklink/cookies.txt 142 | 143 | # .gitconfig is now autogenerated 144 | .gitconfig 145 | 146 | # Quarto installer 147 | .deb 148 | .pkg 149 | 150 | # Quarto 151 | .quarto 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Vivek Gopalakrishnan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include settings.ini 2 | include LICENSE 3 | include CONTRIBUTING.md 4 | include README.md 5 | recursive-exclude * __pycache__ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DiffDRR Datasets 2 | ================ 3 | 4 | 5 | 6 | > Open-source 2D/3D registration datasets and dataloaders for 7 | > [`DiffDRR`](https://github.com/eigenvivek/DiffDRR/) 8 | 9 | [![CI](https://github.com/eigenvivek/DiffDRR-Datasets/actions/workflows/test.yaml/badge.svg)](https://github.com/eigenvivek/DiffDRR-Datasets/actions/workflows/test.yaml) 10 | [![Paper 11 | shield](https://img.shields.io/badge/arXiv-2208.12737-red.svg)](https://arxiv.org/abs/2208.12737) 12 | [![License: 13 | MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) 14 | [![Docs](https://github.com/eigenvivek/DiffDRR-Datasets/actions/workflows/deploy.yaml/badge.svg)](https://vivekg.dev/DiffDRR-Datasets/) 15 | [![Code style: 16 | black](https://img.shields.io/badge/Code%20style-black-black.svg)](https://github.com/psf/black) 17 | 18 | ## Install 19 | 20 | ``` zsh 21 | pip install diffdrrdata 22 | ``` 23 | 24 | ## DiffDRR 25 | 26 | [`DiffDRR`](https://github.com/eigenvivek/DiffDRR/) is an differentiable 27 | X-ray renderer used for solving inverse problems in tomographic imaging. 28 | If you find `DiffDRR` useful in your work, please cite our paper: 29 | 30 | @inproceedings{gopalakrishnan2022fast, 31 | title={Fast auto-differentiable digitally reconstructed radiographs for solving inverse problems in intraoperative imaging}, 32 | author={Gopalakrishnan, Vivek and Golland, Polina}, 33 | booktitle={Workshop on Clinical Image-Based Procedures}, 34 | pages={1--11}, 35 | year={2022}, 36 | organization={Springer} 37 | } 38 | 39 | ## Datasets 40 | 41 | We provide APIs to load the following open-source datasets into 42 | `DiffDRR`: 43 | 44 | | **Dataset** | **Anatomy** | **\# of Subjects** | **\# of 2D Images** | **CTs** | **X-rays** | **GT Fiducials** | 45 | |----------------------------------------------------------------------------|------------------|:------------------:|:-------------------:|:-------:|:----------:|:----------------:| 46 | | [`DeepFluoro`](https://github.com/rg2/DeepFluoroLabeling-IPCAI2020) | pelvis | 6 | 366 | ✅ | ✅ | ❌ | 47 | | [`Ljubljana`](https://lit.fe.uni-lj.si/en/research/resources/3D-2D-GS-CA/) | neurovasculature | 10 | 20 | ✅ | ✅ | ✅ | 48 | 49 | If you use any of these datasets, please cite the original papers. 50 | 51 | ### `DeepFluoro` 52 | 53 | [`DeepFluoro`](https://github.com/rg2/DeepFluoroLabeling-IPCAI2020) 54 | ([**Grupp et al., 55 | 2020**](https://link.springer.com/article/10.1007/s11548-020-02162-7)) 56 | provides paired X-ray fluoroscopy images and CT volume of the pelvis. 57 | The data were collected from six cadaveric subjects at John Hopkins 58 | University. Ground truth camera poses were estimated with an offline 59 | registration process. A visualization of the X-ray / CT pairs in the 60 | `DeepFluoro` dataset is available 61 | [here](https://vivekg.dev/DiffDRR-Datasets/deepfluoro_camera_poses.html). 62 | 63 | @article{grupp2020automatic, 64 | title={Automatic annotation of hip anatomy in fluoroscopy for robust and efficient 2D/3D registration}, 65 | author={Grupp, Robert B and Unberath, Mathias and Gao, Cong and Hegeman, Rachel A and Murphy, Ryan J and Alexander, Clayton P and Otake, Yoshito and McArthur, Benjamin A and Armand, Mehran and Taylor, Russell H}, 66 | journal={International journal of computer assisted radiology and surgery}, 67 | volume={15}, 68 | pages={759--769}, 69 | year={2020}, 70 | publisher={Springer} 71 | } 72 | 73 | ``` python 74 | import matplotlib.pyplot as plt 75 | import torch 76 | from diffdrr.drr import DRR 77 | from diffdrr.visualization import plot_drr 78 | 79 | from diffdrrdata.deepfluoro import DeepFluoroDataset, Transforms 80 | 81 | # Load a subject from the DeepFluoroDataset 82 | deepfluoro = DeepFluoroDataset(id_number=1) 83 | 84 | # Initialize the DRR module 85 | subsample = 4 86 | drr = DRR( 87 | deepfluoro.subject, 88 | deepfluoro.focal_len, 89 | deepfluoro.height // subsample, 90 | deepfluoro.delx * subsample, 91 | x0=deepfluoro.x0, 92 | y0=deepfluoro.y0, 93 | ) 94 | transform = Transforms(deepfluoro.height // subsample) 95 | 96 | # Render a DRR from the ground truth camera pose 97 | gt, pose = deepfluoro[0] 98 | img = drr(pose) 99 | gt, img = transform(gt), transform(img) 100 | plot_drr(torch.concat([gt, img, gt - img]), title=["Downsampled X-ray", "DRR", "Difference"]) 101 | plt.show() 102 | ``` 103 | 104 | ![](index_files/figure-commonmark/cell-2-output-1.png) 105 | 106 | ### `Ljubljana` 107 | 108 | [`Ljubljana`](https://lit.fe.uni-lj.si/en/research/resources/3D-2D-GS-CA/) 109 | (**[Mitrovic et al., 110 | 2013](https://ieeexplore.ieee.org/abstract/document/6507588)**) provides 111 | paired 2D/3D digital subtraction angiography (DSA) images. The data were 112 | collected from 10 patients undergoing endovascular image-guided 113 | interventions at the University of Ljubljana. Ground truth camera poses 114 | were estimated by registering surface fiducial markers. 115 | 116 | @article{pernus20133d, 117 | title={3D-2D registration of cerebral angiograms: A method and evaluation on clinical images}, 118 | author={Mitrović, Uros˘ and S˘piclin, Z˘iga and Likar, Bos˘tjan and Pernus˘, Franjo}, 119 | journal={IEEE transactions on medical imaging}, 120 | volume={32}, 121 | number={8}, 122 | pages={1550--1563}, 123 | year={2013}, 124 | publisher={IEEE} 125 | } 126 | 127 | ``` python 128 | from diffdrrdata.ljubljana import LjubljanaDataset, Transforms 129 | 130 | # Load a subject from the LjubljanaDataset 131 | ljubljana = LjubljanaDataset(id_number=1) 132 | gt, pose, focal_len, height, width, delx, dely, x0, y0 = ljubljana[0] 133 | 134 | # Initialize the DRR module 135 | subsample = 8 136 | drr = DRR( 137 | ljubljana.subject, 138 | focal_len, 139 | height // subsample, 140 | delx * subsample, 141 | width // subsample, 142 | dely * subsample, 143 | x0=x0, 144 | y0=y0, 145 | ) 146 | transform = Transforms(height // subsample, width // subsample) 147 | 148 | # Render a DRR from the ground truth camera pose 149 | img = drr(pose) 150 | gt, img = transform(gt), transform(img) 151 | plot_drr(torch.concat([gt, img, gt - img]), title=["Downsampled X-ray", "DRR", "Difference"]) 152 | plt.show() 153 | ``` 154 | 155 | ![](index_files/figure-commonmark/cell-3-output-1.png) 156 | -------------------------------------------------------------------------------- /diffdrrdata/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.7" 2 | -------------------------------------------------------------------------------- /diffdrrdata/_modidx.py: -------------------------------------------------------------------------------- 1 | # Autogenerated by nbdev 2 | 3 | d = { 'settings': { 'branch': 'main', 4 | 'doc_baseurl': '/DiffDRR-Datasets', 5 | 'doc_host': 'https://eigenvivek.github.io', 6 | 'git_url': 'https://github.com/eigenvivek/DiffDRR-Datasets', 7 | 'lib_path': 'diffdrrdata'}, 8 | 'syms': { 'diffdrrdata.deepfluoro': { 'diffdrrdata.deepfluoro.DeepFluoroDataset': ( 'deepfluoro.html#deepfluorodataset', 9 | 'diffdrrdata/deepfluoro.py'), 10 | 'diffdrrdata.deepfluoro.DeepFluoroDataset.__getitem__': ( 'deepfluoro.html#deepfluorodataset.__getitem__', 11 | 'diffdrrdata/deepfluoro.py'), 12 | 'diffdrrdata.deepfluoro.DeepFluoroDataset.__init__': ( 'deepfluoro.html#deepfluorodataset.__init__', 13 | 'diffdrrdata/deepfluoro.py'), 14 | 'diffdrrdata.deepfluoro.DeepFluoroDataset.__iter__': ( 'deepfluoro.html#deepfluorodataset.__iter__', 15 | 'diffdrrdata/deepfluoro.py'), 16 | 'diffdrrdata.deepfluoro.DeepFluoroDataset.__len__': ( 'deepfluoro.html#deepfluorodataset.__len__', 17 | 'diffdrrdata/deepfluoro.py'), 18 | 'diffdrrdata.deepfluoro.DeepFluoroDataset.rot_180_for_up': ( 'deepfluoro.html#deepfluorodataset.rot_180_for_up', 19 | 'diffdrrdata/deepfluoro.py'), 20 | 'diffdrrdata.deepfluoro.Transforms': ('deepfluoro.html#transforms', 'diffdrrdata/deepfluoro.py'), 21 | 'diffdrrdata.deepfluoro.Transforms.__call__': ( 'deepfluoro.html#transforms.__call__', 22 | 'diffdrrdata/deepfluoro.py'), 23 | 'diffdrrdata.deepfluoro.Transforms.__init__': ( 'deepfluoro.html#transforms.__init__', 24 | 'diffdrrdata/deepfluoro.py'), 25 | 'diffdrrdata.deepfluoro.load': ('deepfluoro.html#load', 'diffdrrdata/deepfluoro.py'), 26 | 'diffdrrdata.deepfluoro.parse_proj_params': ( 'deepfluoro.html#parse_proj_params', 27 | 'diffdrrdata/deepfluoro.py'), 28 | 'diffdrrdata.deepfluoro.parse_volume': ( 'deepfluoro.html#parse_volume', 29 | 'diffdrrdata/deepfluoro.py'), 30 | 'diffdrrdata.deepfluoro.preprocess': ('deepfluoro.html#preprocess', 'diffdrrdata/deepfluoro.py')}, 31 | 'diffdrrdata.ljubljana': { 'diffdrrdata.ljubljana.LjubljanaDataset': ( 'ljubljana.html#ljubljanadataset', 32 | 'diffdrrdata/ljubljana.py'), 33 | 'diffdrrdata.ljubljana.LjubljanaDataset.__getitem__': ( 'ljubljana.html#ljubljanadataset.__getitem__', 34 | 'diffdrrdata/ljubljana.py'), 35 | 'diffdrrdata.ljubljana.LjubljanaDataset.__init__': ( 'ljubljana.html#ljubljanadataset.__init__', 36 | 'diffdrrdata/ljubljana.py'), 37 | 'diffdrrdata.ljubljana.LjubljanaDataset.__iter__': ( 'ljubljana.html#ljubljanadataset.__iter__', 38 | 'diffdrrdata/ljubljana.py'), 39 | 'diffdrrdata.ljubljana.LjubljanaDataset.__len__': ( 'ljubljana.html#ljubljanadataset.__len__', 40 | 'diffdrrdata/ljubljana.py'), 41 | 'diffdrrdata.ljubljana.Transforms': ('ljubljana.html#transforms', 'diffdrrdata/ljubljana.py'), 42 | 'diffdrrdata.ljubljana.Transforms.__call__': ( 'ljubljana.html#transforms.__call__', 43 | 'diffdrrdata/ljubljana.py'), 44 | 'diffdrrdata.ljubljana.Transforms.__init__': ( 'ljubljana.html#transforms.__init__', 45 | 'diffdrrdata/ljubljana.py'), 46 | 'diffdrrdata.ljubljana.parse_proj': ('ljubljana.html#parse_proj', 'diffdrrdata/ljubljana.py'), 47 | 'diffdrrdata.ljubljana.parse_volume': ('ljubljana.html#parse_volume', 'diffdrrdata/ljubljana.py')}, 48 | 'diffdrrdata.utils': { 'diffdrrdata.utils.download_deepfluoro': ('utils.html#download_deepfluoro', 'diffdrrdata/utils.py'), 49 | 'diffdrrdata.utils.download_ljubljana': ('utils.html#download_ljubljana', 'diffdrrdata/utils.py'), 50 | 'diffdrrdata.utils.get_data_home': ('utils.html#get_data_home', 'diffdrrdata/utils.py'), 51 | 'diffdrrdata.utils.load_file': ('utils.html#load_file', 'diffdrrdata/utils.py')}}} 52 | -------------------------------------------------------------------------------- /diffdrrdata/deepfluoro.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../notebooks/00_deepfluoro.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['DeepFluoroDataset', 'preprocess', 'Transforms'] 5 | 6 | # %% ../notebooks/00_deepfluoro.ipynb 3 7 | from pathlib import Path 8 | 9 | import h5py 10 | import numpy as np 11 | import torch 12 | from .utils import load_file 13 | from torchio import LabelMap, ScalarImage, Subject 14 | from torchio.transforms.preprocessing import ToCanonical 15 | from torchvision.transforms.functional import center_crop, gaussian_blur 16 | 17 | from diffdrr.data import read 18 | from diffdrr.detector import parse_intrinsic_matrix 19 | from diffdrr.pose import RigidTransform 20 | 21 | # %% ../notebooks/00_deepfluoro.ipynb 6 22 | class DeepFluoroDataset(torch.utils.data.Dataset): 23 | """ 24 | A `torch.utils.data.Dataset` that stores the imaging data for subjects 25 | in the `DeepFluoro` dataset and provides an iterator over the X-ray 26 | fluoroscopy images and associated poses for each subject. Imaging data 27 | can be passed to a `diffdrr.drr.DRR` to renderer DRRs from ground truth 28 | camera poses. 29 | """ 30 | 31 | def __init__( 32 | self, 33 | id_number: int, # Subject ID in {1, ..., 6} 34 | preprocess: bool = True, # Convert X-rays from exponentiated to linear form 35 | bone_attenuation_multiplier: float = 1.0, # Scalar multiplier on density of high attenuation voxels (from `DiffDRR`, see [here](https://vivekg.dev/DiffDRR/tutorials/introduction.html#changing-the-appearance-of-the-rendered-drrs)) 36 | labels: int | list = None, # Labels from the mask of structures to render 37 | batchless: bool = False, # Return unbatched images and poses (e.g., to interface with a `torch.utils.data.DataLoader`) 38 | ): 39 | super().__init__() 40 | 41 | # Load the subject 42 | ( 43 | self.subject, 44 | self.projections, 45 | self.anatomical2world, 46 | self.world2camera, 47 | self.focal_len, 48 | self.height, 49 | self.width, 50 | self.delx, 51 | self.dely, 52 | self.x0, 53 | self.y0, 54 | ) = load(id_number, bone_attenuation_multiplier, labels) 55 | 56 | self.preprocess = preprocess 57 | if self.preprocess: 58 | self.height -= 100 59 | self.width -= 100 60 | self.batchless = batchless 61 | 62 | # Miscellaneous transformation matrices for wrangling SE(3) poses 63 | self.flip_z = RigidTransform( 64 | torch.tensor( 65 | [ 66 | [-1, 0, 0, 0], 67 | [0, -1, 0, 0], 68 | [0, 0, -1, 0], 69 | [0, 0, 0, 1], 70 | ] 71 | ).to(torch.float32) 72 | ) 73 | self.rot_180 = RigidTransform( 74 | torch.tensor( 75 | [ 76 | [-1, 0, 0, 0], 77 | [0, -1, 0, 0], 78 | [0, 0, 1, 0], 79 | [0, 0, 0, 1], 80 | ] 81 | ).to(torch.float32) 82 | ) 83 | self.reorient = RigidTransform(self.subject.reorient) 84 | 85 | def __len__(self): 86 | return len(self.projections) 87 | 88 | def __iter__(self): 89 | return iter(self[idx] for idx in range(len(self))) 90 | 91 | def __getitem__(self, idx): 92 | img = torch.from_numpy(self.projections[f"{idx:03d}/image/pixels"][:]) 93 | pose = self.projections[f"{idx:03d}/gt-poses/cam-to-pelvis-vol"][:] 94 | pose = RigidTransform(torch.from_numpy(pose)) 95 | pose = ( 96 | self.flip_z 97 | .compose(self.world2camera.inverse()) 98 | .compose(pose) 99 | .compose(self.anatomical2world) 100 | .compose(self.rot_180) 101 | ) 102 | if self.rot_180_for_up(idx): 103 | img = torch.rot90(img, k=2) 104 | pose = self.rot_180.compose(pose) 105 | pose = self.reorient.inverse().compose(pose) 106 | img = img.unsqueeze(0).unsqueeze(0) 107 | if self.preprocess: 108 | img = preprocess(img) 109 | if self.batchless: 110 | return img[0], pose.matrix[0] 111 | else: 112 | return img, pose 113 | 114 | def rot_180_for_up(self, idx): 115 | return self.projections[f"{idx:03d}/rot-180-for-up"][()] 116 | 117 | # %% ../notebooks/00_deepfluoro.ipynb 8 118 | def parse_volume(subject, bone_attenuation_multiplier, labels): 119 | # Get all parts of the volume 120 | volume = subject["vol/pixels"][:] 121 | volume = np.swapaxes(volume, 0, 2).copy() 122 | volume = torch.from_numpy(volume).unsqueeze(0).flip(1).flip(2) 123 | 124 | mask = subject["vol-seg/image/pixels"][:] 125 | mask = np.swapaxes(mask, 0, 2).copy() 126 | mask = torch.from_numpy(mask).unsqueeze(0).flip(1).flip(2) 127 | 128 | affine = np.eye(4) 129 | affine[:3, :3] = subject["vol/dir-mat"][:] 130 | affine[:3, 3:] = subject["vol/origin"][:] 131 | affine = torch.from_numpy(affine).to(torch.float32) 132 | 133 | defns = subject["vol-seg/labels-def"] 134 | defns = {idx: defns[f"{idx}"][()].decode() for idx in range(1, len(defns) + 1)} 135 | 136 | fiducials = torch.stack( 137 | [ 138 | torch.from_numpy(subject[f"vol-landmarks/{key}"][()]) 139 | for key in subject["vol-landmarks"].keys() 140 | ] 141 | ).permute(2, 0, 1) 142 | 143 | volume = ScalarImage(tensor=volume, affine=affine) 144 | labelmap = LabelMap(tensor=mask, affine=affine) 145 | 146 | # Move the fiducials's isocenter to the origin in world coordinates 147 | isocenter = volume.get_center() 148 | anatomical2world = RigidTransform( 149 | torch.tensor( 150 | [ 151 | [1, 0, 0, -isocenter[0]], 152 | [0, 1, 0, -isocenter[1]], 153 | [0, 0, 1, -isocenter[2]], 154 | [0, 0, 0, 1], 155 | ], 156 | dtype=torch.float32, 157 | ) 158 | ) 159 | 160 | # Package the subject 161 | subject = read( 162 | volume=volume, 163 | labelmap=labelmap, 164 | labels=labels, 165 | orientation="PA", 166 | bone_attenuation_multiplier=bone_attenuation_multiplier, 167 | label_def=defns, 168 | fiducials=fiducials, 169 | ) 170 | reorient = RigidTransform(torch.diag(torch.tensor([-1.0, -1.0, 1.0, 1.0]))) 171 | subject.fiducials = reorient(subject.fiducials) 172 | 173 | return subject, anatomical2world 174 | 175 | 176 | def parse_proj_params(f): 177 | proj_params = f["proj-params"] 178 | extrinsic = torch.from_numpy(proj_params["extrinsic"][:]) 179 | camera2world = RigidTransform(extrinsic) 180 | intrinsic = torch.from_numpy(proj_params["intrinsic"][:]) 181 | num_cols = proj_params["num-cols"][()] 182 | num_rows = proj_params["num-rows"][()] 183 | proj_col_spacing = float(proj_params["pixel-col-spacing"][()]) 184 | proj_row_spacing = float(proj_params["pixel-row-spacing"][()]) 185 | return ( 186 | intrinsic, 187 | camera2world, 188 | num_cols, 189 | num_rows, 190 | proj_col_spacing, 191 | proj_row_spacing, 192 | ) 193 | 194 | 195 | def load(id_number, bone_attenuation_multiplier, labels): 196 | f = load_file("ipcai_2020_full_res_data.h5") 197 | 198 | # Load dataset parameters 199 | ( 200 | intrinsic, 201 | camera2world, 202 | num_cols, 203 | num_rows, 204 | proj_col_spacing, 205 | proj_row_spacing, 206 | ) = parse_proj_params(f) 207 | 208 | focal_len, x0, y0 = parse_intrinsic_matrix( 209 | intrinsic, 210 | num_rows, 211 | num_cols, 212 | proj_row_spacing, 213 | proj_col_spacing, 214 | ) 215 | 216 | # Load subject data 217 | assert id_number in {1, 2, 3, 4, 5, 6} 218 | subject_id = [ 219 | "17-1882", 220 | "18-1109", 221 | "18-0725", 222 | "18-2799", 223 | "18-2800", 224 | "17-1905", 225 | ][id_number - 1] 226 | subject = f[subject_id] 227 | projections = subject["projections"] 228 | subject, anatomical2world = parse_volume( 229 | subject, bone_attenuation_multiplier, labels 230 | ) 231 | 232 | return ( 233 | subject, 234 | projections, 235 | anatomical2world, 236 | camera2world, 237 | focal_len, 238 | int(num_rows), 239 | int(num_cols), 240 | proj_row_spacing, 241 | proj_col_spacing, 242 | x0, 243 | y0, 244 | ) 245 | 246 | # %% ../notebooks/00_deepfluoro.ipynb 9 247 | def preprocess(img, initial_energy=torch.tensor(65487.0)): 248 | """Convert X-ray fluoroscopy from the exponentiated form to the linear form.""" 249 | img = center_crop(img, (1436, 1436)) 250 | img = gaussian_blur(img, (5, 5), sigma=1.0) 251 | img = initial_energy.log() - img.log() 252 | return img 253 | 254 | # %% ../notebooks/00_deepfluoro.ipynb 11 255 | from torchvision.transforms import Compose, Lambda, Normalize, Resize 256 | 257 | 258 | class Transforms: 259 | def __init__(self, height: int, eps: float = 1e-6): 260 | """Standardize, resize, and normalize X-rays and DRRs before inputting to a deep learning model.""" 261 | self.transforms = Compose( 262 | [ 263 | Lambda(lambda x: (x - x.min()) / (x.max() - x.min() + eps)), 264 | Resize((height, height), antialias=True), 265 | Normalize(mean=0.3080, std=0.1494), 266 | ] 267 | ) 268 | 269 | def __call__(self, x): 270 | return self.transforms(x) 271 | -------------------------------------------------------------------------------- /diffdrrdata/ljubljana.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../notebooks/01_ljubljana.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['LjubljanaDataset', 'Transforms'] 5 | 6 | # %% ../notebooks/01_ljubljana.ipynb 3 7 | from pathlib import Path 8 | 9 | import numpy as np 10 | import torch 11 | from diffdrr.data import read 12 | from diffdrr.pose import RigidTransform 13 | from torchio import ScalarImage, Subject 14 | 15 | from .utils import load_file 16 | 17 | # %% ../notebooks/01_ljubljana.ipynb 5 18 | class LjubljanaDataset(torch.utils.data.Dataset): 19 | """ 20 | A `torch.utils.data.Dataset` that stores the imaging data for subjects 21 | in the `Ljubljana` dataset. 22 | """ 23 | 24 | def __init__( 25 | self, 26 | id_number: int, # Subject ID in {1, ..., 10} 27 | preprocess: bool = True, # Convert X-rays from exponentiated to linear form 28 | ): 29 | self.f = load_file("ljubljana.h5") 30 | self.id_number = id_number 31 | self.subject, self.anatomical2world = parse_volume(self.f, self.id_number) 32 | self.preprocess = preprocess 33 | 34 | # Miscellaneous transformation matrices for wrangling SE(3) poses 35 | self.flip_z = RigidTransform( 36 | torch.tensor( 37 | [ 38 | [-1, 0, 0, 0], 39 | [0, -1, 0, 0], 40 | [0, 0, -1, 0], 41 | [0, 0, 0, 1], 42 | ] 43 | ).to(torch.float32) 44 | ) 45 | self.reorient = RigidTransform(self.subject.reorient) 46 | 47 | def __len__(self): 48 | return 2 49 | 50 | def __iter__(self): 51 | return iter(self[idx] for idx in range(len(self))) 52 | 53 | def __getitem__(self, idx): 54 | view = {0: "ap", 1: "lat"}[idx] 55 | img, world2camera, focal_len, height, width, delx, dely, x0, y0 = parse_proj( 56 | self.f, self.id_number, view 57 | ) 58 | 59 | if self.preprocess: 60 | img = img - img.mode().values.mode().values.item() # Subtract background color 61 | img = torch.clamp(img, -1, 0) + 1 # Restrict to [0, 1] 62 | img += 1 # Convert to log-scale 63 | img = img.max().log() - img.log() 64 | 65 | pose = ( 66 | self.reorient.inverse() 67 | .compose(self.flip_z) 68 | .compose(world2camera.inverse()) 69 | .compose(self.anatomical2world) 70 | ) 71 | 72 | return img, pose, focal_len, height, width, delx, dely, x0, y0 73 | 74 | # %% ../notebooks/01_ljubljana.ipynb 6 75 | def parse_volume(f, subject_id): 76 | subject = f[f"subject{subject_id:02d}"] 77 | 78 | # Get the volume 79 | volume = subject["volume/pixels"][:] 80 | volume = volume.copy() 81 | volume = torch.from_numpy(volume).unsqueeze(0).flip(1) 82 | volume[volume < 1000] = 0.0 # Discard a lot of the background from the 3D DSA 83 | 84 | affine = np.eye((4)) 85 | spacing = subject["volume/spacing"][:] 86 | affine[0, 0] = spacing[0] 87 | affine[1, 1] = spacing[1] 88 | affine[2, 2] = spacing[2] 89 | affine[:3, 3] = subject["volume/origin"][:] 90 | affine = torch.from_numpy(affine) 91 | 92 | volume = ScalarImage(tensor=volume, affine=affine) 93 | fiducials = torch.from_numpy(subject["points"][:]).unsqueeze(0) 94 | 95 | # Move the Subject's isocenter to the origin in world coordinates 96 | isocenter = volume.get_center() 97 | anatomical2world = RigidTransform( 98 | torch.tensor( 99 | [ 100 | [1.0, 0.0, 0.0, -isocenter[0]], 101 | [0.0, 1.0, 0.0, -isocenter[1]], 102 | [0.0, 0.0, 1.0, -isocenter[2]], 103 | [0.0, 0.0, 0.0, 1.0], 104 | ], 105 | dtype=torch.float32, 106 | ) 107 | ) 108 | 109 | # Package the subject 110 | subject = read( 111 | volume=volume, 112 | labelmap=None, 113 | bone_attenuation_multiplier=1.0, 114 | fiducials=fiducials, 115 | orientation="AP", 116 | ) 117 | 118 | return subject, anatomical2world 119 | 120 | # %% ../notebooks/01_ljubljana.ipynb 7 121 | from diffdrr.detector import parse_intrinsic_matrix 122 | 123 | 124 | def parse_proj(f, subject_id, view): 125 | proj = f[f"subject{subject_id:02d}/proj-{view}"] 126 | 127 | img = torch.from_numpy(proj["pixels"][:]).unsqueeze(0).unsqueeze(0) 128 | num_rows, num_cols = proj["pixels"].shape 129 | 130 | extrinsic = torch.from_numpy(proj["extrinsic"][:]) 131 | world2camera = RigidTransform(extrinsic) 132 | 133 | intrinsic = torch.from_numpy(proj["intrinsic"][:]) 134 | proj_col_spacing = float(proj["col-spacing"][()]) 135 | proj_row_spacing = float(proj["row-spacing"][()]) 136 | focal_len, x0, y0 = parse_intrinsic_matrix( 137 | intrinsic, 138 | num_rows, 139 | num_cols, 140 | proj_row_spacing, 141 | proj_col_spacing, 142 | ) 143 | 144 | return ( 145 | img, 146 | world2camera, 147 | focal_len, 148 | int(num_rows), 149 | int(num_cols), 150 | proj_row_spacing, 151 | proj_col_spacing, 152 | x0, 153 | y0, 154 | ) 155 | 156 | # %% ../notebooks/01_ljubljana.ipynb 8 157 | from torchvision.transforms import Compose, Lambda, Normalize, Resize 158 | 159 | 160 | class Transforms: 161 | def __init__(self, height: int, width: int, eps: float = 1e-6): 162 | """Standardize, resize, and normalize X-rays and DRRs before inputting to a deep learning model.""" 163 | self.transforms = Compose( 164 | [ 165 | Lambda(lambda x: (x - x.min()) / (x.max() - x.min() + eps)), 166 | Resize((height, width), antialias=True), 167 | Normalize(mean=0.0306, std=0.0564), 168 | ] 169 | ) 170 | 171 | def __call__(self, x): 172 | return self.transforms(x) 173 | -------------------------------------------------------------------------------- /diffdrrdata/utils.py: -------------------------------------------------------------------------------- 1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../notebooks/02_utils.ipynb. 2 | 3 | # %% auto 0 4 | __all__ = ['get_data_home', 'load_file', 'download_deepfluoro', 'download_ljubljana'] 5 | 6 | # %% ../notebooks/02_utils.ipynb 3 7 | import subprocess 8 | from pathlib import Path 9 | 10 | import h5py 11 | 12 | # %% ../notebooks/02_utils.ipynb 4 13 | def get_data_home() -> Path: 14 | """By default, datasets are saved in `~/user/diffdrr_data`.""" 15 | data_home = data_home = Path("~/user/diffdrr_data").expanduser() 16 | data_home.mkdir(exist_ok=True) 17 | return data_home 18 | 19 | # %% ../notebooks/02_utils.ipynb 5 20 | def load_file(filename: str): 21 | """Internal function for loading datasets.""" 22 | 23 | file_path = get_data_home() / filename 24 | 25 | if filename == "ipcai_2020_full_res_data.h5": # DeepFluoro dataset 26 | try: 27 | f = h5py.File(file_path) 28 | except FileNotFoundError: 29 | print(f"Downloading DeepFluoro dataset (~2.5 GB) to {file_path}") 30 | download_deepfluoro() 31 | f = h5py.File(file_path) 32 | elif filename == "ljubljana.h5": 33 | try: 34 | f = h5py.File(file_path) 35 | except FileNotFoundError: 36 | print(f"Downloading Ljubljana dataset (~1.0 GB) to {file_path}") 37 | download_ljubljana() 38 | f = h5py.File(file_path) 39 | else: 40 | raise ValueError(f"Unrecognized filename {filename}") 41 | 42 | return f 43 | 44 | # %% ../notebooks/02_utils.ipynb 6 45 | def download_deepfluoro(): 46 | data_home = get_data_home() 47 | download_link = "http://archive.data.jhu.edu/api/access/datafile/:persistentId/?persistentId=doi:10.7281/T1/IFSXNV/EAN9GH" 48 | download_path = data_home / "ipcai_2020_full_res_data.zip" 49 | subprocess.run( 50 | f"wget --no-check-certificate -O {download_path} {download_link}".split(" ") 51 | ) 52 | subprocess.run(f"unzip -o {download_path} -d {data_home}".split(" ")) 53 | subprocess.run(f"rm {download_path}".split(" ")) 54 | 55 | # %% ../notebooks/02_utils.ipynb 7 56 | def download_ljubljana(): 57 | data_home = get_data_home() 58 | download_link = "https://drive.usercontent.google.com/download?id=1x585pGLI8QGk21qZ2oGwwQ9LMJ09Tqrx&confirm=xxx" 59 | download_path = data_home / "ljubljana.zip" 60 | subprocess.run( 61 | f"curl {download_link} -o {download_path} ".split(" ") 62 | ) 63 | subprocess.run(f"unzip -o {download_path} -d {data_home}".split(" ")) 64 | subprocess.run(f"rm {download_path}".split(" ")) 65 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: diffdrr 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - pip 6 | - pytorch>=2.2 7 | - einops 8 | - torchvision 9 | - torchio 10 | - kornia 11 | - timm 12 | - numpy 13 | - matplotlib 14 | - seaborn 15 | - imageio 16 | - fastcore 17 | - scipy 18 | - tqdm 19 | - pyvista 20 | - vtk 21 | - h5py 22 | - pip: 23 | - diffdrr 24 | -------------------------------------------------------------------------------- /index_files/figure-commonmark/cell-2-output-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigenvivek/DiffDRR-Datasets/72e3ab2e0235be0cebd1294f30aec20246c868f2/index_files/figure-commonmark/cell-2-output-1.png -------------------------------------------------------------------------------- /index_files/figure-commonmark/cell-3-output-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigenvivek/DiffDRR-Datasets/72e3ab2e0235be0cebd1294f30aec20246c868f2/index_files/figure-commonmark/cell-3-output-1.png -------------------------------------------------------------------------------- /index_files/figure-html/cell-2-output-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigenvivek/DiffDRR-Datasets/72e3ab2e0235be0cebd1294f30aec20246c868f2/index_files/figure-html/cell-2-output-1.png -------------------------------------------------------------------------------- /index_files/figure-html/cell-3-output-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigenvivek/DiffDRR-Datasets/72e3ab2e0235be0cebd1294f30aec20246c868f2/index_files/figure-html/cell-3-output-1.png -------------------------------------------------------------------------------- /notebooks/02_utils.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "raw", 5 | "id": "49b44c8a-e356-43db-813a-e728e4e30264", 6 | "metadata": {}, 7 | "source": [ 8 | "---\n", 9 | "title: utils\n", 10 | "description: Utility functions for I/O of 2D/3D datasets\n", 11 | "output-file: utils.html\n", 12 | "---" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "id": "63f7a0de-4f8a-45d1-8015-0f48b1f78f9e", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "#| default_exp utils" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "id": "cfa58ce8-fb01-4d4a-8f31-0a186675f4fe", 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "#| hide\n", 33 | "from nbdev.showdoc import *" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "id": "11f31def-c54b-48a2-aa9b-2517555cca9b", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "#| export\n", 44 | "import subprocess\n", 45 | "from pathlib import Path\n", 46 | "\n", 47 | "import h5py" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "id": "ec4af237-bf0c-4ae4-84ea-5e2921b714c9", 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "#| export\n", 58 | "def get_data_home() -> Path:\n", 59 | " \"\"\"By default, datasets are saved in `~/user/diffdrr_data`.\"\"\"\n", 60 | " data_home = data_home = Path(\"~/user/diffdrr_data\").expanduser()\n", 61 | " data_home.mkdir(exist_ok=True)\n", 62 | " return data_home" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "id": "1c63c628-e279-45a9-b5f5-a12097436e6e", 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "#| export\n", 73 | "def load_file(filename: str):\n", 74 | " \"\"\"Internal function for loading datasets.\"\"\"\n", 75 | "\n", 76 | " file_path = get_data_home() / filename\n", 77 | "\n", 78 | " if filename == \"ipcai_2020_full_res_data.h5\": # DeepFluoro dataset\n", 79 | " try:\n", 80 | " f = h5py.File(file_path)\n", 81 | " except FileNotFoundError:\n", 82 | " print(f\"Downloading DeepFluoro dataset (~2.5 GB) to {file_path}\")\n", 83 | " download_deepfluoro()\n", 84 | " f = h5py.File(file_path)\n", 85 | " elif filename == \"ljubljana.h5\":\n", 86 | " try:\n", 87 | " f = h5py.File(file_path)\n", 88 | " except FileNotFoundError:\n", 89 | " print(f\"Downloading Ljubljana dataset (~1.0 GB) to {file_path}\")\n", 90 | " download_ljubljana()\n", 91 | " f = h5py.File(file_path)\n", 92 | " else:\n", 93 | " raise ValueError(f\"Unrecognized filename {filename}\")\n", 94 | "\n", 95 | " return f" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "99396416-4b6e-40aa-8519-94796f5d4ea7", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "#| export\n", 106 | "def download_deepfluoro():\n", 107 | " data_home = get_data_home()\n", 108 | " download_link = \"http://archive.data.jhu.edu/api/access/datafile/:persistentId/?persistentId=doi:10.7281/T1/IFSXNV/EAN9GH\"\n", 109 | " download_path = data_home / \"ipcai_2020_full_res_data.zip\"\n", 110 | " subprocess.run(\n", 111 | " f\"wget --no-check-certificate -O {download_path} {download_link}\".split(\" \")\n", 112 | " )\n", 113 | " subprocess.run(f\"unzip -o {download_path} -d {data_home}\".split(\" \"))\n", 114 | " subprocess.run(f\"rm {download_path}\".split(\" \"))" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "id": "b2b76bd6-e807-4a2b-a7f4-a34540651ebe", 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "#| export\n", 125 | "def download_ljubljana():\n", 126 | " data_home = get_data_home()\n", 127 | " download_link = \"https://drive.usercontent.google.com/download?id=1x585pGLI8QGk21qZ2oGwwQ9LMJ09Tqrx&confirm=xxx\"\n", 128 | " download_path = data_home / \"ljubljana.zip\"\n", 129 | " subprocess.run(\n", 130 | " f\"curl {download_link} -o {download_path} \".split(\" \")\n", 131 | " )\n", 132 | " subprocess.run(f\"unzip -o {download_path} -d {data_home}\".split(\" \"))\n", 133 | " subprocess.run(f\"rm {download_path}\".split(\" \"))" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "id": "4ed8da5d-950a-49f4-bba3-9da9df31d6ee", 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "#| hide\n", 144 | "import nbdev\n", 145 | "\n", 146 | "nbdev.nbdev_export()" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "id": "61b28135-c7b4-4d32-9491-bd6b22355b02", 153 | "metadata": {}, 154 | "outputs": [], 155 | "source": [] 156 | } 157 | ], 158 | "metadata": { 159 | "kernelspec": { 160 | "display_name": "python3", 161 | "language": "python", 162 | "name": "python3" 163 | }, 164 | "widgets": { 165 | "application/vnd.jupyter.widget-state+json": { 166 | "state": {}, 167 | "version_major": 2, 168 | "version_minor": 0 169 | } 170 | } 171 | }, 172 | "nbformat": 4, 173 | "nbformat_minor": 5 174 | } 175 | -------------------------------------------------------------------------------- /notebooks/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | 4 | format: 5 | html: 6 | theme: cosmo 7 | css: styles.css 8 | toc: true 9 | 10 | website: 11 | twitter-card: true 12 | open-graph: true 13 | repo-actions: [issue] 14 | favicon: favicon.png 15 | navbar: 16 | background: primary 17 | search: true 18 | right: 19 | - icon: github 20 | href: "https://github.com/eigenvivek/DiffDRR-Datasets" 21 | sidebar: 22 | style: floating 23 | 24 | metadata-files: [nbdev.yml, sidebar.yml] -------------------------------------------------------------------------------- /notebooks/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eigenvivek/DiffDRR-Datasets/72e3ab2e0235be0cebd1294f30aec20246c868f2/notebooks/favicon.png -------------------------------------------------------------------------------- /notebooks/nbdev.yml: -------------------------------------------------------------------------------- 1 | project: 2 | output-dir: _docs 3 | 4 | website: 5 | title: "diffdrrdata" 6 | site-url: "https://eigenvivek.github.io/DiffDRR-Datasets" 7 | description: "Open-source 2D/3D registration datasets and dataloaders for DiffDRR" 8 | repo-branch: main 9 | repo-url: "https://github.com/eigenvivek/DiffDRR-Datasets" 10 | -------------------------------------------------------------------------------- /notebooks/sidebar.yml: -------------------------------------------------------------------------------- 1 | website: 2 | sidebar: 3 | contents: 4 | - index.ipynb 5 | - 00_deepfluoro.ipynb 6 | - 01_ljubljana.ipynb 7 | - 02_utils.ipynb 8 | - deepfluoro_camera_poses.html 9 | -------------------------------------------------------------------------------- /notebooks/styles.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | margin-bottom: 1rem; 3 | } 4 | 5 | .cell > .sourceCode { 6 | margin-bottom: 0; 7 | } 8 | 9 | .cell-output > pre { 10 | margin-bottom: 0; 11 | } 12 | 13 | .cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { 14 | margin-left: 0.8rem; 15 | margin-top: 0; 16 | background: none; 17 | border-left: 2px solid lightsalmon; 18 | border-top-left-radius: 0; 19 | border-top-right-radius: 0; 20 | } 21 | 22 | .cell-output > .sourceCode { 23 | border: none; 24 | } 25 | 26 | .cell-output > .sourceCode { 27 | background: none; 28 | margin-top: 0; 29 | } 30 | 31 | div.description { 32 | padding-left: 2px; 33 | padding-top: 5px; 34 | font-style: italic; 35 | font-size: 135%; 36 | opacity: 70%; 37 | } 38 | -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | repo = DiffDRR-Datasets 3 | lib_name = diffdrrdata 4 | version = 0.0.7 5 | min_python = 3.7 6 | license = apache2 7 | black_formatting = False 8 | doc_path = _docs 9 | lib_path = diffdrrdata 10 | nbs_path = notebooks 11 | recursive = True 12 | tst_flags = notest 13 | put_version_in_init = True 14 | branch = main 15 | custom_sidebar = False 16 | doc_host = https://eigenvivek.github.io 17 | doc_baseurl = /DiffDRR-Datasets 18 | git_url = https://github.com/eigenvivek/DiffDRR-Datasets 19 | title = diffdrrdata 20 | audience = Developers 21 | author = Vivek Gopalakrishnan 22 | author_email = vivekg@mit.edu 23 | copyright = 2024 onwards, Vivek Gopalakrishnan 24 | description = Open-source 2D/3D registration datasets and dataloaders for DiffDRR 25 | keywords = nbdev jupyter notebook python 26 | language = English 27 | status = 3 28 | user = eigenvivek 29 | requirements = diffdrr>=0.5.0rc0 h5py 30 | readme_nb = index.ipynb 31 | allowed_metadata_keys = 32 | allowed_cell_metadata_keys = 33 | jupyter_hooks = False 34 | clean_ids = True 35 | clear_all = False 36 | cell_number = True 37 | skip_procs = 38 | 39 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import parse_version 2 | from configparser import ConfigParser 3 | import setuptools, shlex 4 | assert parse_version(setuptools.__version__)>=parse_version('36.2') 5 | 6 | # note: all settings are in settings.ini; edit there, not here 7 | config = ConfigParser(delimiters=['=']) 8 | config.read('settings.ini', encoding='utf-8') 9 | cfg = config['DEFAULT'] 10 | 11 | cfg_keys = 'version description keywords author author_email'.split() 12 | expected = cfg_keys + "lib_name user branch license status min_python audience language".split() 13 | for o in expected: assert o in cfg, "missing expected setting: {}".format(o) 14 | setup_cfg = {o:cfg[o] for o in cfg_keys} 15 | 16 | licenses = { 17 | 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'), 18 | 'mit': ('MIT License', 'OSI Approved :: MIT License'), 19 | 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'), 20 | 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'), 21 | 'bsd3': ('BSD License', 'OSI Approved :: BSD License'), 22 | } 23 | statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha', 24 | '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ] 25 | py_versions = '3.6 3.7 3.8 3.9 3.10'.split() 26 | 27 | requirements = shlex.split(cfg.get('requirements', '')) 28 | if cfg.get('pip_requirements'): requirements += shlex.split(cfg.get('pip_requirements', '')) 29 | min_python = cfg['min_python'] 30 | lic = licenses.get(cfg['license'].lower(), (cfg['license'], None)) 31 | dev_requirements = (cfg.get('dev_requirements') or '').split() 32 | 33 | setuptools.setup( 34 | name = cfg['lib_name'], 35 | license = lic[0], 36 | classifiers = [ 37 | 'Development Status :: ' + statuses[int(cfg['status'])], 38 | 'Intended Audience :: ' + cfg['audience'].title(), 39 | 'Natural Language :: ' + cfg['language'].title(), 40 | ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []), 41 | url = cfg['git_url'], 42 | packages = setuptools.find_packages(), 43 | include_package_data = True, 44 | install_requires = requirements, 45 | extras_require={ 'dev': dev_requirements }, 46 | dependency_links = cfg.get('dep_links','').split(), 47 | python_requires = '>=' + cfg['min_python'], 48 | long_description = open('README.md', encoding='utf-8').read(), 49 | long_description_content_type = 'text/markdown', 50 | zip_safe = False, 51 | entry_points = { 52 | 'console_scripts': cfg.get('console_scripts','').split(), 53 | 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d'] 54 | }, 55 | **setup_cfg) 56 | 57 | 58 | --------------------------------------------------------------------------------