├── .github
└── workflows
│ └── python-publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── __init__.py
├── dockerfiles
└── Dockerfile
├── notebooks
├── Build_plastimatch_for_Ubuntu_22_04_LTS.ipynb
└── pyplastimatch_MWE.ipynb
├── pyplastimatch
├── __init__.py
├── pyplastimatch.py
└── utils
│ ├── __init__.py
│ ├── data.py
│ ├── eval.py
│ ├── install.py
│ └── widgets.py
├── pyproject.toml
├── requirements.txt
└── setup.py
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will upload a Python Package using Twine when a release is created
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Upload Python Package
10 |
11 | on:
12 | release:
13 | types: [published]
14 |
15 | permissions:
16 | contents: read
17 |
18 | jobs:
19 | deploy:
20 |
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - uses: actions/checkout@v3
25 | - name: Set up Python
26 | uses: actions/setup-python@v3
27 | with:
28 | python-version: '3.x'
29 | - name: Install dependencies
30 | run: |
31 | python -m pip install --upgrade pip
32 | pip install build
33 | - name: Build package
34 | run: python -m build
35 | - name: Publish package
36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
37 | with:
38 | user: __token__
39 | password: ${{ secrets.PYPI_API_TOKEN }}
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | # repo specific
132 | data/
133 | _dev_*
134 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 Imaging Data Commons
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4 | following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7 | disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
10 | disclaimer in the documentation and/or other materials provided with the distribution.
11 |
12 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
21 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PyPlastimatch
2 |
3 | PyPlastimatch is a python wrapper for [Plastimatch](http://plastimatch.org/), an ITK-based open source software designed for volumetric medical image processing and radiation therapy applications.
4 |
5 | The main reason behing the development of PyPlastimatch is being able to use the Plastimatch functions within python scripts without having to code using `os.system` or `subprocess` all the time. Also, we are working on making the output of some of the functions Plastimatch implements for evaluation (e.g., Dice Coefficient and Hausdorff Distance) more pythonic/easily usable in python-based data analysis pipelines.
6 |
7 | Together with the wrapping functions, we are also developing simple but handy functions that can be used for quick data exploration (e.g., simple widgets based on ipywidgets) in ipython notebooks and JupyterLab.
8 |
9 |
10 |
11 | PyPlastimatch is completely independent from the main software Plastimatch, and it is being developed mainly for internal use. For this reason, most of the Plastimatch functions might be missing. If you would like to see something added or point out how we could improve anything in the wrapper, you are very welcome to [open an issue at the issue page](https://github.com/AIM-Harvard/pyplastimatch/issues).
12 |
13 |
14 | # Table of Contents
15 | - [Install Via pip](#install-via-pip)
16 | - [Dependencies](#dependencies)
17 | - [Python](#python)
18 | - [Plastimatch](#plastimatch)
19 | - [dcmqi](#dcmqi)
20 | - [Usage Example](#usage-example)
21 | - [Run in a Docker Container](#ubuntu-2204-lts-plastimatch-docker-container)
22 | - [Further Reading](#further-reading)
23 |
24 |
25 | # Install Via `pip`
26 |
27 | PyPlastimatch can be installed via pip:
28 |
29 | ```
30 | pip install pyplastimatch
31 | ```
32 |
33 | # Dependencies
34 |
35 | ## Python
36 |
37 | If you decide to clone the PyPlastimatch repository and not to install it with `pip`, in order to run the code as intended, all the python libraries found in `requirements.txt` must be installed. This can be done running the command:
38 |
39 | ```
40 | pip3 install -r requirements.txt
41 | ```
42 |
43 | ## Plastimatch
44 |
45 | Since PyPlastimatch is a python wrapper and doesn't include any processing code, Plastimatch must be installed on the machine separately.
46 |
47 | ### Ubuntu 20.04 LTS
48 |
49 | For users running Ubuntu 20.04 LTS (and distributions that fetch the same packages), Plastimatch can be installed simply by running:
50 |
51 | ```
52 | sudo apt install plastimatch
53 | ```
54 |
55 | ### Ubuntu 22.04 LTS
56 |
57 | Users running Ubuntu 22.04 LTS will unfortunately not be able to install Plastimatch via `apt` (see [this Issue on the official Plastimatch GitLab](https://gitlab.com/plastimatch/plastimatch/-/issues/87)). To remedy this, we compiled a binary file for Ubuntu 22.04 LTS that you can find [in our releases](https://github.com/AIM-Harvard/pyplastimatch/releases) and you can download running the following once PyPlastimatch is installed (from a Python3 shell):
58 |
59 | ```
60 | from pyplastimatch.utils.install import install_precompiled_binaries
61 | install_precompiled_binaries()
62 | ```
63 |
64 | and, of course, its equivalent from CLI:
65 |
66 | ```
67 | RUN python3 -c 'from pyplastimatch.utils.install import install_precompiled_binaries; install_precompiled_binaries()'
68 | ```
69 |
70 | The plastimatch binary we provide was compiled dinamically, so it will not work without installing some dependencies (`itk` via `pip`, and some system dependencies that the `install_precompiled_binaries()` function takes care of automatically). Depending on the python version you are using and your environment (i.e., packages already installed), you might need to install `itk` via `pip` before installing `pyplastimatch`.
71 |
72 | In the future, we might support binaries pre-compiled statically, and for other distributions/OSs.
73 |
74 | ### Other OSs
75 |
76 | For Windows users, Plastimatch can be installed following [the guide at this webpage](http://plastimatch.org/windows_installation.html).
77 |
78 | ### Building from Source
79 |
80 | Plastimatch can also be build from source following [the guide at this webpage](http://plastimatch.org/building_plastimatch.html). The guide could be slightly outdated, but it should be enough to get you started.
81 |
82 | ## DCMQI
83 |
84 | Some functions might be based on the [DICOM for Quantitative Imaging (dcmqi) library](https://github.com/QIICR/dcmqi), that must be installed separately (e.g., under Linux, download the latest release, move the content of the `bin` folder under `usr/local/bin`, and make the files executable).
85 |
86 |
87 | # Usage Example
88 |
89 | You can try PyPlastimatch on Google Colab without installing anything. To open the Colab notebook, click here: [](https://colab.research.google.com/github/AIM-Harvard/pyplastimatch/blob/main/notebooks/pyplastimatch_MWE.ipynb)
90 |
91 | Since Plastimatch and this wrapper are being used for the development of AI-base medical image analysis pipelines on the [NIH CRDC Imaging Data Commons](https://datacommons.cancer.gov/repository/imaging-data-commons) platform, some example notebooks using PyPlastimatch can be found at the [IDC-Examples/notebooks](https://github.com/ImagingDataCommons/IDC-Examples/tree/master/notebooks) repository as well.
92 |
93 | Note: provided you have a Google Cloud Platform project correctly set up, you will be able to run this and all the other notebooks for free, entirely on the cloud. Should you find any issues with the notebooks or would like to learn more, you can get in touch here, on the IDC repositories, or the [IDC forum](https://discourse.canceridc.dev/).
94 |
95 | # Ubuntu 22.04 LTS Plastimatch Docker Container
96 |
97 | If you want to test Plastimatch for Ubuntu 22.04 LTS, you can use the Docker image we shared for this purpose under `dockerfiles`.
98 |
99 | ## Build the Docker Container
100 |
101 | To build the Ubuntu 22.04 LTS Platimatch Docker container, run the following commands from the root of the PyPlastimatch repository:
102 |
103 | ```
104 | cd dockerfiles/
105 |
106 | docker build --tag pypla_22.04 . --no-cache
107 | ```
108 |
109 | ## Run the Docker Container
110 |
111 | Assuming the data you want to convert/manipulate with Plastimatch is stored at `/home/dennis/Desktop/sample_data/`, the Docker command to run will look like the following
112 |
113 | ```
114 | docker run --rm -it --entrypoint bash -v /home/dennis/Desktop/sample_data/:/app/data pypla_22.04
115 | ```
116 |
117 | This will mount the data directory to the container's `/app/data` directory, and you can then run Plastimatch commands from within the container. For example, if `/home/dennis/Desktop/sample_data/` is structured as follows:
118 |
119 | ```
120 | (base) dennis@W2-S1:~$ tree /home/dennis/Desktop/sample_data/ -L 1
121 | /home/dennis/Desktop/sample_data/
122 | └── dicom
123 | ```
124 |
125 | Then, once inside the container, you can run the following command to convert the DICOM files to a volume saved in the NRRD format:
126 |
127 | ```
128 | cd /app/data
129 |
130 | plastimatch convert --input input_dcm/ --output-img test.nrrd
131 | ```
132 |
133 |
134 | # Further Reading
135 | [Paolo Zaffino's (un)"official" wrapper](https://gitlab.com/plastimatch/plastimatch/-/tree/master/extra/python).
136 |
137 | Further discussion about the python-wrapping of Plastimatch can be found at [this discourse.slicer thread](https://discourse.slicer.org/t/python-wrapping-of-plastimatch/6722/10).
138 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | from .pyplastimatch import *
2 |
--------------------------------------------------------------------------------
/dockerfiles/Dockerfile:
--------------------------------------------------------------------------------
1 | from ubuntu:22.04
2 |
3 | RUN apt-get update && apt-get install -y \
4 | build-essential \
5 | cmake \
6 | git \
7 | libboost-all-dev \
8 | libssl-dev \
9 | libzmq3-dev \
10 | pkg-config \
11 | python3 \
12 | python3-pip \
13 | python3-setuptools \
14 | python3-wheel \
15 | wget \
16 | && rm -rf /var/lib/apt/lists/*
17 |
18 | RUN pip install pyplastimatch
19 | RUN python3 -c 'from pyplastimatch.utils.install import install_precompiled_binaries; install_precompiled_binaries()'
20 |
21 | ENTRYPOINT ["plastimatch"]
22 |
23 |
--------------------------------------------------------------------------------
/notebooks/pyplastimatch_MWE.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "authorship_tag": "ABX9TyPCsW/YTN1DGboYSjsIHCuv",
8 | "include_colab_link": true
9 | },
10 | "kernelspec": {
11 | "name": "python3",
12 | "display_name": "Python 3"
13 | },
14 | "language_info": {
15 | "name": "python"
16 | },
17 | "widgets": {
18 | "application/vnd.jupyter.widget-state+json": {
19 | "2e7211359fd04858bd68bb17b0f90e36": {
20 | "model_module": "@jupyter-widgets/controls",
21 | "model_name": "HBoxModel",
22 | "model_module_version": "1.5.0",
23 | "state": {
24 | "_dom_classes": [],
25 | "_model_module": "@jupyter-widgets/controls",
26 | "_model_module_version": "1.5.0",
27 | "_model_name": "HBoxModel",
28 | "_view_count": null,
29 | "_view_module": "@jupyter-widgets/controls",
30 | "_view_module_version": "1.5.0",
31 | "_view_name": "HBoxView",
32 | "box_style": "",
33 | "children": [
34 | "IPY_MODEL_9425915a065147a084eabcd305c0cc74",
35 | "IPY_MODEL_99c95284225c447692e1ccd31799457e",
36 | "IPY_MODEL_2e4afc6613d04723a69d88ed5845ba9e"
37 | ],
38 | "layout": "IPY_MODEL_723335f0243d4ae999d30d0689edd2dc"
39 | }
40 | },
41 | "9425915a065147a084eabcd305c0cc74": {
42 | "model_module": "@jupyter-widgets/controls",
43 | "model_name": "HTMLModel",
44 | "model_module_version": "1.5.0",
45 | "state": {
46 | "_dom_classes": [],
47 | "_model_module": "@jupyter-widgets/controls",
48 | "_model_module_version": "1.5.0",
49 | "_model_name": "HTMLModel",
50 | "_view_count": null,
51 | "_view_module": "@jupyter-widgets/controls",
52 | "_view_module_version": "1.5.0",
53 | "_view_name": "HTMLView",
54 | "description": "",
55 | "description_tooltip": null,
56 | "layout": "IPY_MODEL_a23f16c20fd94694ae50e24a48e65dc0",
57 | "placeholder": "",
58 | "style": "IPY_MODEL_22abd9afebcc42b7ba95f41e814cab79",
59 | "value": "Job ID bd68a27f-6d4e-47ff-84f5-605df4e59541 successfully executed: 100%"
60 | }
61 | },
62 | "99c95284225c447692e1ccd31799457e": {
63 | "model_module": "@jupyter-widgets/controls",
64 | "model_name": "FloatProgressModel",
65 | "model_module_version": "1.5.0",
66 | "state": {
67 | "_dom_classes": [],
68 | "_model_module": "@jupyter-widgets/controls",
69 | "_model_module_version": "1.5.0",
70 | "_model_name": "FloatProgressModel",
71 | "_view_count": null,
72 | "_view_module": "@jupyter-widgets/controls",
73 | "_view_module_version": "1.5.0",
74 | "_view_name": "ProgressView",
75 | "bar_style": "success",
76 | "description": "",
77 | "description_tooltip": null,
78 | "layout": "IPY_MODEL_6b9e7b40fb23418999f3653e6a005dd0",
79 | "max": 1,
80 | "min": 0,
81 | "orientation": "horizontal",
82 | "style": "IPY_MODEL_e54bcb832e1e4938b8eacb7edbc42b23",
83 | "value": 1
84 | }
85 | },
86 | "2e4afc6613d04723a69d88ed5845ba9e": {
87 | "model_module": "@jupyter-widgets/controls",
88 | "model_name": "HTMLModel",
89 | "model_module_version": "1.5.0",
90 | "state": {
91 | "_dom_classes": [],
92 | "_model_module": "@jupyter-widgets/controls",
93 | "_model_module_version": "1.5.0",
94 | "_model_name": "HTMLModel",
95 | "_view_count": null,
96 | "_view_module": "@jupyter-widgets/controls",
97 | "_view_module_version": "1.5.0",
98 | "_view_name": "HTMLView",
99 | "description": "",
100 | "description_tooltip": null,
101 | "layout": "IPY_MODEL_a09595a3bd5e42369ca1aaba3534946f",
102 | "placeholder": "",
103 | "style": "IPY_MODEL_60c8e8b3232e42799cf0decae5da8dc4",
104 | "value": ""
105 | }
106 | },
107 | "723335f0243d4ae999d30d0689edd2dc": {
108 | "model_module": "@jupyter-widgets/base",
109 | "model_name": "LayoutModel",
110 | "model_module_version": "1.2.0",
111 | "state": {
112 | "_model_module": "@jupyter-widgets/base",
113 | "_model_module_version": "1.2.0",
114 | "_model_name": "LayoutModel",
115 | "_view_count": null,
116 | "_view_module": "@jupyter-widgets/base",
117 | "_view_module_version": "1.2.0",
118 | "_view_name": "LayoutView",
119 | "align_content": null,
120 | "align_items": null,
121 | "align_self": null,
122 | "border": null,
123 | "bottom": null,
124 | "display": null,
125 | "flex": null,
126 | "flex_flow": null,
127 | "grid_area": null,
128 | "grid_auto_columns": null,
129 | "grid_auto_flow": null,
130 | "grid_auto_rows": null,
131 | "grid_column": null,
132 | "grid_gap": null,
133 | "grid_row": null,
134 | "grid_template_areas": null,
135 | "grid_template_columns": null,
136 | "grid_template_rows": null,
137 | "height": null,
138 | "justify_content": null,
139 | "justify_items": null,
140 | "left": null,
141 | "margin": null,
142 | "max_height": null,
143 | "max_width": null,
144 | "min_height": null,
145 | "min_width": null,
146 | "object_fit": null,
147 | "object_position": null,
148 | "order": null,
149 | "overflow": null,
150 | "overflow_x": null,
151 | "overflow_y": null,
152 | "padding": null,
153 | "right": null,
154 | "top": null,
155 | "visibility": null,
156 | "width": null
157 | }
158 | },
159 | "a23f16c20fd94694ae50e24a48e65dc0": {
160 | "model_module": "@jupyter-widgets/base",
161 | "model_name": "LayoutModel",
162 | "model_module_version": "1.2.0",
163 | "state": {
164 | "_model_module": "@jupyter-widgets/base",
165 | "_model_module_version": "1.2.0",
166 | "_model_name": "LayoutModel",
167 | "_view_count": null,
168 | "_view_module": "@jupyter-widgets/base",
169 | "_view_module_version": "1.2.0",
170 | "_view_name": "LayoutView",
171 | "align_content": null,
172 | "align_items": null,
173 | "align_self": null,
174 | "border": null,
175 | "bottom": null,
176 | "display": null,
177 | "flex": null,
178 | "flex_flow": null,
179 | "grid_area": null,
180 | "grid_auto_columns": null,
181 | "grid_auto_flow": null,
182 | "grid_auto_rows": null,
183 | "grid_column": null,
184 | "grid_gap": null,
185 | "grid_row": null,
186 | "grid_template_areas": null,
187 | "grid_template_columns": null,
188 | "grid_template_rows": null,
189 | "height": null,
190 | "justify_content": null,
191 | "justify_items": null,
192 | "left": null,
193 | "margin": null,
194 | "max_height": null,
195 | "max_width": null,
196 | "min_height": null,
197 | "min_width": null,
198 | "object_fit": null,
199 | "object_position": null,
200 | "order": null,
201 | "overflow": null,
202 | "overflow_x": null,
203 | "overflow_y": null,
204 | "padding": null,
205 | "right": null,
206 | "top": null,
207 | "visibility": null,
208 | "width": null
209 | }
210 | },
211 | "22abd9afebcc42b7ba95f41e814cab79": {
212 | "model_module": "@jupyter-widgets/controls",
213 | "model_name": "DescriptionStyleModel",
214 | "model_module_version": "1.5.0",
215 | "state": {
216 | "_model_module": "@jupyter-widgets/controls",
217 | "_model_module_version": "1.5.0",
218 | "_model_name": "DescriptionStyleModel",
219 | "_view_count": null,
220 | "_view_module": "@jupyter-widgets/base",
221 | "_view_module_version": "1.2.0",
222 | "_view_name": "StyleView",
223 | "description_width": ""
224 | }
225 | },
226 | "6b9e7b40fb23418999f3653e6a005dd0": {
227 | "model_module": "@jupyter-widgets/base",
228 | "model_name": "LayoutModel",
229 | "model_module_version": "1.2.0",
230 | "state": {
231 | "_model_module": "@jupyter-widgets/base",
232 | "_model_module_version": "1.2.0",
233 | "_model_name": "LayoutModel",
234 | "_view_count": null,
235 | "_view_module": "@jupyter-widgets/base",
236 | "_view_module_version": "1.2.0",
237 | "_view_name": "LayoutView",
238 | "align_content": null,
239 | "align_items": null,
240 | "align_self": null,
241 | "border": null,
242 | "bottom": null,
243 | "display": null,
244 | "flex": null,
245 | "flex_flow": null,
246 | "grid_area": null,
247 | "grid_auto_columns": null,
248 | "grid_auto_flow": null,
249 | "grid_auto_rows": null,
250 | "grid_column": null,
251 | "grid_gap": null,
252 | "grid_row": null,
253 | "grid_template_areas": null,
254 | "grid_template_columns": null,
255 | "grid_template_rows": null,
256 | "height": null,
257 | "justify_content": null,
258 | "justify_items": null,
259 | "left": null,
260 | "margin": null,
261 | "max_height": null,
262 | "max_width": null,
263 | "min_height": null,
264 | "min_width": null,
265 | "object_fit": null,
266 | "object_position": null,
267 | "order": null,
268 | "overflow": null,
269 | "overflow_x": null,
270 | "overflow_y": null,
271 | "padding": null,
272 | "right": null,
273 | "top": null,
274 | "visibility": null,
275 | "width": null
276 | }
277 | },
278 | "e54bcb832e1e4938b8eacb7edbc42b23": {
279 | "model_module": "@jupyter-widgets/controls",
280 | "model_name": "ProgressStyleModel",
281 | "model_module_version": "1.5.0",
282 | "state": {
283 | "_model_module": "@jupyter-widgets/controls",
284 | "_model_module_version": "1.5.0",
285 | "_model_name": "ProgressStyleModel",
286 | "_view_count": null,
287 | "_view_module": "@jupyter-widgets/base",
288 | "_view_module_version": "1.2.0",
289 | "_view_name": "StyleView",
290 | "bar_color": null,
291 | "description_width": ""
292 | }
293 | },
294 | "a09595a3bd5e42369ca1aaba3534946f": {
295 | "model_module": "@jupyter-widgets/base",
296 | "model_name": "LayoutModel",
297 | "model_module_version": "1.2.0",
298 | "state": {
299 | "_model_module": "@jupyter-widgets/base",
300 | "_model_module_version": "1.2.0",
301 | "_model_name": "LayoutModel",
302 | "_view_count": null,
303 | "_view_module": "@jupyter-widgets/base",
304 | "_view_module_version": "1.2.0",
305 | "_view_name": "LayoutView",
306 | "align_content": null,
307 | "align_items": null,
308 | "align_self": null,
309 | "border": null,
310 | "bottom": null,
311 | "display": null,
312 | "flex": null,
313 | "flex_flow": null,
314 | "grid_area": null,
315 | "grid_auto_columns": null,
316 | "grid_auto_flow": null,
317 | "grid_auto_rows": null,
318 | "grid_column": null,
319 | "grid_gap": null,
320 | "grid_row": null,
321 | "grid_template_areas": null,
322 | "grid_template_columns": null,
323 | "grid_template_rows": null,
324 | "height": null,
325 | "justify_content": null,
326 | "justify_items": null,
327 | "left": null,
328 | "margin": null,
329 | "max_height": null,
330 | "max_width": null,
331 | "min_height": null,
332 | "min_width": null,
333 | "object_fit": null,
334 | "object_position": null,
335 | "order": null,
336 | "overflow": null,
337 | "overflow_x": null,
338 | "overflow_y": null,
339 | "padding": null,
340 | "right": null,
341 | "top": null,
342 | "visibility": null,
343 | "width": null
344 | }
345 | },
346 | "60c8e8b3232e42799cf0decae5da8dc4": {
347 | "model_module": "@jupyter-widgets/controls",
348 | "model_name": "DescriptionStyleModel",
349 | "model_module_version": "1.5.0",
350 | "state": {
351 | "_model_module": "@jupyter-widgets/controls",
352 | "_model_module_version": "1.5.0",
353 | "_model_name": "DescriptionStyleModel",
354 | "_view_count": null,
355 | "_view_module": "@jupyter-widgets/base",
356 | "_view_module_version": "1.2.0",
357 | "_view_name": "StyleView",
358 | "description_width": ""
359 | }
360 | },
361 | "ecff57a547dc487f9066d0995afc6bdc": {
362 | "model_module": "@jupyter-widgets/controls",
363 | "model_name": "HBoxModel",
364 | "model_module_version": "1.5.0",
365 | "state": {
366 | "_dom_classes": [],
367 | "_model_module": "@jupyter-widgets/controls",
368 | "_model_module_version": "1.5.0",
369 | "_model_name": "HBoxModel",
370 | "_view_count": null,
371 | "_view_module": "@jupyter-widgets/controls",
372 | "_view_module_version": "1.5.0",
373 | "_view_name": "HBoxView",
374 | "box_style": "",
375 | "children": [
376 | "IPY_MODEL_cd2e508902684b94b07c9050632cf19e",
377 | "IPY_MODEL_c99ae7396a2547b4b11484e19b76b9fd",
378 | "IPY_MODEL_0aed05a56e8a46008d21b8f381cd3e72"
379 | ],
380 | "layout": "IPY_MODEL_8e23115f8eb14cfd8a33edd96e817d6b"
381 | }
382 | },
383 | "cd2e508902684b94b07c9050632cf19e": {
384 | "model_module": "@jupyter-widgets/controls",
385 | "model_name": "HTMLModel",
386 | "model_module_version": "1.5.0",
387 | "state": {
388 | "_dom_classes": [],
389 | "_model_module": "@jupyter-widgets/controls",
390 | "_model_module_version": "1.5.0",
391 | "_model_name": "HTMLModel",
392 | "_view_count": null,
393 | "_view_module": "@jupyter-widgets/controls",
394 | "_view_module_version": "1.5.0",
395 | "_view_name": "HTMLView",
396 | "description": "",
397 | "description_tooltip": null,
398 | "layout": "IPY_MODEL_7162bfba772745f1ade50ad68d5a9a44",
399 | "placeholder": "",
400 | "style": "IPY_MODEL_4117f7ebf8e94f9cb64238ead021b4df",
401 | "value": "Downloading: 100%"
402 | }
403 | },
404 | "c99ae7396a2547b4b11484e19b76b9fd": {
405 | "model_module": "@jupyter-widgets/controls",
406 | "model_name": "FloatProgressModel",
407 | "model_module_version": "1.5.0",
408 | "state": {
409 | "_dom_classes": [],
410 | "_model_module": "@jupyter-widgets/controls",
411 | "_model_module_version": "1.5.0",
412 | "_model_name": "FloatProgressModel",
413 | "_view_count": null,
414 | "_view_module": "@jupyter-widgets/controls",
415 | "_view_module_version": "1.5.0",
416 | "_view_name": "ProgressView",
417 | "bar_style": "success",
418 | "description": "",
419 | "description_tooltip": null,
420 | "layout": "IPY_MODEL_b8a8ab02484140a2ac67d570178bad6d",
421 | "max": 55734,
422 | "min": 0,
423 | "orientation": "horizontal",
424 | "style": "IPY_MODEL_934dbc11963a4e61b41f14a3bd8c727c",
425 | "value": 55734
426 | }
427 | },
428 | "0aed05a56e8a46008d21b8f381cd3e72": {
429 | "model_module": "@jupyter-widgets/controls",
430 | "model_name": "HTMLModel",
431 | "model_module_version": "1.5.0",
432 | "state": {
433 | "_dom_classes": [],
434 | "_model_module": "@jupyter-widgets/controls",
435 | "_model_module_version": "1.5.0",
436 | "_model_name": "HTMLModel",
437 | "_view_count": null,
438 | "_view_module": "@jupyter-widgets/controls",
439 | "_view_module_version": "1.5.0",
440 | "_view_name": "HTMLView",
441 | "description": "",
442 | "description_tooltip": null,
443 | "layout": "IPY_MODEL_dfca79f783c649daab0088013d7d740a",
444 | "placeholder": "",
445 | "style": "IPY_MODEL_4cf5762bfcbf47a8b430824202b81392",
446 | "value": ""
447 | }
448 | },
449 | "8e23115f8eb14cfd8a33edd96e817d6b": {
450 | "model_module": "@jupyter-widgets/base",
451 | "model_name": "LayoutModel",
452 | "model_module_version": "1.2.0",
453 | "state": {
454 | "_model_module": "@jupyter-widgets/base",
455 | "_model_module_version": "1.2.0",
456 | "_model_name": "LayoutModel",
457 | "_view_count": null,
458 | "_view_module": "@jupyter-widgets/base",
459 | "_view_module_version": "1.2.0",
460 | "_view_name": "LayoutView",
461 | "align_content": null,
462 | "align_items": null,
463 | "align_self": null,
464 | "border": null,
465 | "bottom": null,
466 | "display": null,
467 | "flex": null,
468 | "flex_flow": null,
469 | "grid_area": null,
470 | "grid_auto_columns": null,
471 | "grid_auto_flow": null,
472 | "grid_auto_rows": null,
473 | "grid_column": null,
474 | "grid_gap": null,
475 | "grid_row": null,
476 | "grid_template_areas": null,
477 | "grid_template_columns": null,
478 | "grid_template_rows": null,
479 | "height": null,
480 | "justify_content": null,
481 | "justify_items": null,
482 | "left": null,
483 | "margin": null,
484 | "max_height": null,
485 | "max_width": null,
486 | "min_height": null,
487 | "min_width": null,
488 | "object_fit": null,
489 | "object_position": null,
490 | "order": null,
491 | "overflow": null,
492 | "overflow_x": null,
493 | "overflow_y": null,
494 | "padding": null,
495 | "right": null,
496 | "top": null,
497 | "visibility": null,
498 | "width": null
499 | }
500 | },
501 | "7162bfba772745f1ade50ad68d5a9a44": {
502 | "model_module": "@jupyter-widgets/base",
503 | "model_name": "LayoutModel",
504 | "model_module_version": "1.2.0",
505 | "state": {
506 | "_model_module": "@jupyter-widgets/base",
507 | "_model_module_version": "1.2.0",
508 | "_model_name": "LayoutModel",
509 | "_view_count": null,
510 | "_view_module": "@jupyter-widgets/base",
511 | "_view_module_version": "1.2.0",
512 | "_view_name": "LayoutView",
513 | "align_content": null,
514 | "align_items": null,
515 | "align_self": null,
516 | "border": null,
517 | "bottom": null,
518 | "display": null,
519 | "flex": null,
520 | "flex_flow": null,
521 | "grid_area": null,
522 | "grid_auto_columns": null,
523 | "grid_auto_flow": null,
524 | "grid_auto_rows": null,
525 | "grid_column": null,
526 | "grid_gap": null,
527 | "grid_row": null,
528 | "grid_template_areas": null,
529 | "grid_template_columns": null,
530 | "grid_template_rows": null,
531 | "height": null,
532 | "justify_content": null,
533 | "justify_items": null,
534 | "left": null,
535 | "margin": null,
536 | "max_height": null,
537 | "max_width": null,
538 | "min_height": null,
539 | "min_width": null,
540 | "object_fit": null,
541 | "object_position": null,
542 | "order": null,
543 | "overflow": null,
544 | "overflow_x": null,
545 | "overflow_y": null,
546 | "padding": null,
547 | "right": null,
548 | "top": null,
549 | "visibility": null,
550 | "width": null
551 | }
552 | },
553 | "4117f7ebf8e94f9cb64238ead021b4df": {
554 | "model_module": "@jupyter-widgets/controls",
555 | "model_name": "DescriptionStyleModel",
556 | "model_module_version": "1.5.0",
557 | "state": {
558 | "_model_module": "@jupyter-widgets/controls",
559 | "_model_module_version": "1.5.0",
560 | "_model_name": "DescriptionStyleModel",
561 | "_view_count": null,
562 | "_view_module": "@jupyter-widgets/base",
563 | "_view_module_version": "1.2.0",
564 | "_view_name": "StyleView",
565 | "description_width": ""
566 | }
567 | },
568 | "b8a8ab02484140a2ac67d570178bad6d": {
569 | "model_module": "@jupyter-widgets/base",
570 | "model_name": "LayoutModel",
571 | "model_module_version": "1.2.0",
572 | "state": {
573 | "_model_module": "@jupyter-widgets/base",
574 | "_model_module_version": "1.2.0",
575 | "_model_name": "LayoutModel",
576 | "_view_count": null,
577 | "_view_module": "@jupyter-widgets/base",
578 | "_view_module_version": "1.2.0",
579 | "_view_name": "LayoutView",
580 | "align_content": null,
581 | "align_items": null,
582 | "align_self": null,
583 | "border": null,
584 | "bottom": null,
585 | "display": null,
586 | "flex": null,
587 | "flex_flow": null,
588 | "grid_area": null,
589 | "grid_auto_columns": null,
590 | "grid_auto_flow": null,
591 | "grid_auto_rows": null,
592 | "grid_column": null,
593 | "grid_gap": null,
594 | "grid_row": null,
595 | "grid_template_areas": null,
596 | "grid_template_columns": null,
597 | "grid_template_rows": null,
598 | "height": null,
599 | "justify_content": null,
600 | "justify_items": null,
601 | "left": null,
602 | "margin": null,
603 | "max_height": null,
604 | "max_width": null,
605 | "min_height": null,
606 | "min_width": null,
607 | "object_fit": null,
608 | "object_position": null,
609 | "order": null,
610 | "overflow": null,
611 | "overflow_x": null,
612 | "overflow_y": null,
613 | "padding": null,
614 | "right": null,
615 | "top": null,
616 | "visibility": null,
617 | "width": null
618 | }
619 | },
620 | "934dbc11963a4e61b41f14a3bd8c727c": {
621 | "model_module": "@jupyter-widgets/controls",
622 | "model_name": "ProgressStyleModel",
623 | "model_module_version": "1.5.0",
624 | "state": {
625 | "_model_module": "@jupyter-widgets/controls",
626 | "_model_module_version": "1.5.0",
627 | "_model_name": "ProgressStyleModel",
628 | "_view_count": null,
629 | "_view_module": "@jupyter-widgets/base",
630 | "_view_module_version": "1.2.0",
631 | "_view_name": "StyleView",
632 | "bar_color": null,
633 | "description_width": ""
634 | }
635 | },
636 | "dfca79f783c649daab0088013d7d740a": {
637 | "model_module": "@jupyter-widgets/base",
638 | "model_name": "LayoutModel",
639 | "model_module_version": "1.2.0",
640 | "state": {
641 | "_model_module": "@jupyter-widgets/base",
642 | "_model_module_version": "1.2.0",
643 | "_model_name": "LayoutModel",
644 | "_view_count": null,
645 | "_view_module": "@jupyter-widgets/base",
646 | "_view_module_version": "1.2.0",
647 | "_view_name": "LayoutView",
648 | "align_content": null,
649 | "align_items": null,
650 | "align_self": null,
651 | "border": null,
652 | "bottom": null,
653 | "display": null,
654 | "flex": null,
655 | "flex_flow": null,
656 | "grid_area": null,
657 | "grid_auto_columns": null,
658 | "grid_auto_flow": null,
659 | "grid_auto_rows": null,
660 | "grid_column": null,
661 | "grid_gap": null,
662 | "grid_row": null,
663 | "grid_template_areas": null,
664 | "grid_template_columns": null,
665 | "grid_template_rows": null,
666 | "height": null,
667 | "justify_content": null,
668 | "justify_items": null,
669 | "left": null,
670 | "margin": null,
671 | "max_height": null,
672 | "max_width": null,
673 | "min_height": null,
674 | "min_width": null,
675 | "object_fit": null,
676 | "object_position": null,
677 | "order": null,
678 | "overflow": null,
679 | "overflow_x": null,
680 | "overflow_y": null,
681 | "padding": null,
682 | "right": null,
683 | "top": null,
684 | "visibility": null,
685 | "width": null
686 | }
687 | },
688 | "4cf5762bfcbf47a8b430824202b81392": {
689 | "model_module": "@jupyter-widgets/controls",
690 | "model_name": "DescriptionStyleModel",
691 | "model_module_version": "1.5.0",
692 | "state": {
693 | "_model_module": "@jupyter-widgets/controls",
694 | "_model_module_version": "1.5.0",
695 | "_model_name": "DescriptionStyleModel",
696 | "_view_count": null,
697 | "_view_module": "@jupyter-widgets/base",
698 | "_view_module_version": "1.2.0",
699 | "_view_name": "StyleView",
700 | "description_width": ""
701 | }
702 | }
703 | }
704 | }
705 | },
706 | "cells": [
707 | {
708 | "cell_type": "markdown",
709 | "metadata": {
710 | "id": "view-in-github",
711 | "colab_type": "text"
712 | },
713 | "source": [
714 | "
"
715 | ]
716 | },
717 | {
718 | "cell_type": "markdown",
719 | "source": [
720 | "## Download Binaries and Setup"
721 | ],
722 | "metadata": {
723 | "id": "uVjGtlID9KCi"
724 | }
725 | },
726 | {
727 | "cell_type": "code",
728 | "source": [
729 | "%%capture\n",
730 | "!pip install pyplastimatch"
731 | ],
732 | "metadata": {
733 | "id": "6-p8ETTHzePm"
734 | },
735 | "execution_count": 1,
736 | "outputs": []
737 | },
738 | {
739 | "cell_type": "code",
740 | "source": [
741 | "from pyplastimatch.utils.install import install_precompiled_binaries\n",
742 | "\n",
743 | "install_precompiled_binaries()"
744 | ],
745 | "metadata": {
746 | "id": "pcH3R68t-ETC",
747 | "outputId": "82270e51-3f09-4a1f-ffa7-9266aca70df6",
748 | "colab": {
749 | "base_uri": "https://localhost:8080/"
750 | }
751 | },
752 | "execution_count": 2,
753 | "outputs": [
754 | {
755 | "output_type": "stream",
756 | "name": "stdout",
757 | "text": [
758 | "PyPlastimatch Plastimatch installation utility.\n",
759 | "NOTE: this utility is not meant to be replace the normal install of Plastimatch via apt.\n",
760 | "Rather, it is meant to be used in case a Plastimatch binary is not available for a specific distribution.\n",
761 | "\n",
762 | "System distribution: Ubuntu 22.04\n",
763 | "\n",
764 | "Downloading meta JSON in the temp directory /tmp/tmpedlc8ryd/release_meta.json... Done.\n",
765 | "Matching distribution found in the latest PyPlastimatch release.\n",
766 | "\n",
767 | "Downloading binary in the temp directory /tmp/tmpedlc8ryd/plastimatch-ubuntu_22_04... Done.\n",
768 | "\n",
769 | "Installing binaries... Done.\n",
770 | "Installing dependencies... Done.\n"
771 | ]
772 | }
773 | ]
774 | },
775 | {
776 | "cell_type": "markdown",
777 | "source": [
778 | "---"
779 | ],
780 | "metadata": {
781 | "id": "qFvdSQCbzA4S"
782 | }
783 | },
784 | {
785 | "cell_type": "code",
786 | "source": [
787 | "!plastimatch"
788 | ],
789 | "metadata": {
790 | "colab": {
791 | "base_uri": "https://localhost:8080/"
792 | },
793 | "id": "A8hp2Al72I2Z",
794 | "outputId": "c2481d7b-9fb3-4eb4-d60d-9473c4d5a4c8"
795 | },
796 | "execution_count": 3,
797 | "outputs": [
798 | {
799 | "output_type": "stream",
800 | "name": "stdout",
801 | "text": [
802 | "plastimatch version 1.9.4-46-g950dde17\n",
803 | "Usage: plastimatch command [options]\n",
804 | "Commands:\n",
805 | " add adjust average bbox boundary \n",
806 | " crop compare compose convert dice \n",
807 | " diff dmap dose drr dvh \n",
808 | " fdk fill filter gamma header \n",
809 | " intersect jacobian lm-warp mabs mask \n",
810 | " maximum ml-convert multiply probe register \n",
811 | " resample scale segment sift stats \n",
812 | " synth synth-vf threshold thumbnail union \n",
813 | " warp wed xf-convert xf-invert \n",
814 | "\n",
815 | "For detailed usage of a specific command, type:\n",
816 | " plastimatch command\n"
817 | ]
818 | }
819 | ]
820 | },
821 | {
822 | "cell_type": "markdown",
823 | "source": [
824 | "---\n",
825 | "\n",
826 | "## Run Plastimatch\n",
827 | "\n",
828 | "Sanity check to see if plastimatch works as intended, using IDC data."
829 | ],
830 | "metadata": {
831 | "id": "9nBUjRzd9F4B"
832 | }
833 | },
834 | {
835 | "cell_type": "code",
836 | "source": [
837 | "import os\n",
838 | "import time\n",
839 | "import random\n",
840 | "\n",
841 | "import pyplastimatch as pypla"
842 | ],
843 | "metadata": {
844 | "id": "G7jtAEzq9QrV"
845 | },
846 | "execution_count": 4,
847 | "outputs": []
848 | },
849 | {
850 | "cell_type": "code",
851 | "source": [
852 | "from google.colab import files\n",
853 | "from google.cloud import storage\n",
854 | "from google.cloud import bigquery as bq\n",
855 | "\n",
856 | "project_id = \"idc-sandbox-000\""
857 | ],
858 | "metadata": {
859 | "id": "bhhZi4c5-Fc9"
860 | },
861 | "execution_count": 5,
862 | "outputs": []
863 | },
864 | {
865 | "cell_type": "code",
866 | "source": [
867 | "from google.colab import auth\n",
868 | "auth.authenticate_user()"
869 | ],
870 | "metadata": {
871 | "id": "kOLJNsA_-8mm"
872 | },
873 | "execution_count": 6,
874 | "outputs": []
875 | },
876 | {
877 | "cell_type": "code",
878 | "source": [
879 | "s5cmd_release_url = \"https://github.com/peak/s5cmd/releases/download/v2.0.0/s5cmd_2.0.0_Linux-64bit.tar.gz\"\n",
880 | "s5cmd_download_path = \"s5cmd_2.0.0_Linux-64bit.tar.gz\"\n",
881 | "s5cmd_path = \"s5cmd\"\n",
882 | "\n",
883 | "!wget $s5cmd_release_url\n",
884 | "!mkdir -p $s5cmd_path && tar zxf $s5cmd_download_path -C $s5cmd_path\n",
885 | "!cp s5cmd/s5cmd /usr/bin && rm $s5cmd_download_path && rm -r $s5cmd_path"
886 | ],
887 | "metadata": {
888 | "colab": {
889 | "base_uri": "https://localhost:8080/"
890 | },
891 | "id": "hdjHjAJc-Pvx",
892 | "outputId": "8f7fb3d1-5452-45c1-dca7-e9e6cbe1a35a"
893 | },
894 | "execution_count": 7,
895 | "outputs": [
896 | {
897 | "output_type": "stream",
898 | "name": "stdout",
899 | "text": [
900 | "--2023-09-27 19:47:39-- https://github.com/peak/s5cmd/releases/download/v2.0.0/s5cmd_2.0.0_Linux-64bit.tar.gz\n",
901 | "Resolving github.com (github.com)... 192.30.255.113\n",
902 | "Connecting to github.com (github.com)|192.30.255.113|:443... connected.\n",
903 | "HTTP request sent, awaiting response... 302 Found\n",
904 | "Location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/73909333/2e177ae0-614f-48ba-92fd-04cf9bf41529?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20230927%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230927T194739Z&X-Amz-Expires=300&X-Amz-Signature=6477418cd0fe5afb6c26e071864e0f4569b812c90cfe377a4666f9250c100072&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=73909333&response-content-disposition=attachment%3B%20filename%3Ds5cmd_2.0.0_Linux-64bit.tar.gz&response-content-type=application%2Foctet-stream [following]\n",
905 | "--2023-09-27 19:47:39-- https://objects.githubusercontent.com/github-production-release-asset-2e65be/73909333/2e177ae0-614f-48ba-92fd-04cf9bf41529?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20230927%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230927T194739Z&X-Amz-Expires=300&X-Amz-Signature=6477418cd0fe5afb6c26e071864e0f4569b812c90cfe377a4666f9250c100072&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=73909333&response-content-disposition=attachment%3B%20filename%3Ds5cmd_2.0.0_Linux-64bit.tar.gz&response-content-type=application%2Foctet-stream\n",
906 | "Resolving objects.githubusercontent.com (objects.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n",
907 | "Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.108.133|:443... connected.\n",
908 | "HTTP request sent, awaiting response... 200 OK\n",
909 | "Length: 4276789 (4.1M) [application/octet-stream]\n",
910 | "Saving to: ‘s5cmd_2.0.0_Linux-64bit.tar.gz’\n",
911 | "\n",
912 | "s5cmd_2.0.0_Linux-6 100%[===================>] 4.08M 16.6MB/s in 0.2s \n",
913 | "\n",
914 | "2023-09-27 19:47:40 (16.6 MB/s) - ‘s5cmd_2.0.0_Linux-64bit.tar.gz’ saved [4276789/4276789]\n",
915 | "\n"
916 | ]
917 | }
918 | ]
919 | },
920 | {
921 | "cell_type": "code",
922 | "source": [
923 | "%%bigquery cohort_df --project=$project_id\n",
924 | "\n",
925 | "SELECT\n",
926 | " dicom_pivot.PatientID,\n",
927 | " dicom_pivot.Modality,\n",
928 | " dicom_pivot.collection_id,\n",
929 | " dicom_pivot.source_DOI,\n",
930 | " dicom_pivot.StudyInstanceUID,\n",
931 | " dicom_pivot.SeriesInstanceUID,\n",
932 | " dicom_pivot.SOPInstanceUID,\n",
933 | " dicom_pivot.gcs_url\n",
934 | "FROM\n",
935 | " `bigquery-public-data.idc_v15.dicom_pivot` dicom_pivot\n",
936 | "WHERE\n",
937 | " StudyInstanceUID IN (\n",
938 | " SELECT\n",
939 | " StudyInstanceUID\n",
940 | " FROM\n",
941 | " `bigquery-public-data.idc_v15.dicom_pivot` dicom_pivot\n",
942 | " WHERE\n",
943 | " (\n",
944 | " dicom_pivot.collection_id IN ('Community', 'nsclc_radiomics')\n",
945 | " )\n",
946 | " GROUP BY\n",
947 | " StudyInstanceUID\n",
948 | " )\n",
949 | "GROUP BY\n",
950 | " dicom_pivot.PatientID,\n",
951 | " dicom_pivot.Modality,\n",
952 | " dicom_pivot.collection_id,\n",
953 | " dicom_pivot.source_DOI,\n",
954 | " dicom_pivot.StudyInstanceUID,\n",
955 | " dicom_pivot.SeriesInstanceUID,\n",
956 | " dicom_pivot.SOPInstanceUID,\n",
957 | " dicom_pivot.gcs_url\n",
958 | "ORDER BY\n",
959 | " dicom_pivot.PatientID ASC,\n",
960 | " dicom_pivot.Modality ASC,\n",
961 | " dicom_pivot.collection_id ASC,\n",
962 | " dicom_pivot.source_DOI ASC,\n",
963 | " dicom_pivot.StudyInstanceUID ASC,\n",
964 | " dicom_pivot.SeriesInstanceUID ASC,\n",
965 | " dicom_pivot.SOPInstanceUID ASC,\n",
966 | " dicom_pivot.gcs_url ASC"
967 | ],
968 | "metadata": {
969 | "colab": {
970 | "base_uri": "https://localhost:8080/",
971 | "height": 81,
972 | "referenced_widgets": [
973 | "2e7211359fd04858bd68bb17b0f90e36",
974 | "9425915a065147a084eabcd305c0cc74",
975 | "99c95284225c447692e1ccd31799457e",
976 | "2e4afc6613d04723a69d88ed5845ba9e",
977 | "723335f0243d4ae999d30d0689edd2dc",
978 | "a23f16c20fd94694ae50e24a48e65dc0",
979 | "22abd9afebcc42b7ba95f41e814cab79",
980 | "6b9e7b40fb23418999f3653e6a005dd0",
981 | "e54bcb832e1e4938b8eacb7edbc42b23",
982 | "a09595a3bd5e42369ca1aaba3534946f",
983 | "60c8e8b3232e42799cf0decae5da8dc4",
984 | "ecff57a547dc487f9066d0995afc6bdc",
985 | "cd2e508902684b94b07c9050632cf19e",
986 | "c99ae7396a2547b4b11484e19b76b9fd",
987 | "0aed05a56e8a46008d21b8f381cd3e72",
988 | "8e23115f8eb14cfd8a33edd96e817d6b",
989 | "7162bfba772745f1ade50ad68d5a9a44",
990 | "4117f7ebf8e94f9cb64238ead021b4df",
991 | "b8a8ab02484140a2ac67d570178bad6d",
992 | "934dbc11963a4e61b41f14a3bd8c727c",
993 | "dfca79f783c649daab0088013d7d740a",
994 | "4cf5762bfcbf47a8b430824202b81392"
995 | ]
996 | },
997 | "id": "Ce6QO0Mh9ohK",
998 | "outputId": "559aab0f-a361-4b38-ae31-aa65a893d2a0"
999 | },
1000 | "execution_count": 8,
1001 | "outputs": [
1002 | {
1003 | "output_type": "display_data",
1004 | "data": {
1005 | "text/plain": [
1006 | "Query is running: 0%| |"
1007 | ],
1008 | "application/vnd.jupyter.widget-view+json": {
1009 | "version_major": 2,
1010 | "version_minor": 0,
1011 | "model_id": "2e7211359fd04858bd68bb17b0f90e36"
1012 | }
1013 | },
1014 | "metadata": {}
1015 | },
1016 | {
1017 | "output_type": "display_data",
1018 | "data": {
1019 | "text/plain": [
1020 | "Downloading: 0%| |"
1021 | ],
1022 | "application/vnd.jupyter.widget-view+json": {
1023 | "version_major": 2,
1024 | "version_minor": 0,
1025 | "model_id": "ecff57a547dc487f9066d0995afc6bdc"
1026 | }
1027 | },
1028 | "metadata": {}
1029 | }
1030 | ]
1031 | },
1032 | {
1033 | "cell_type": "code",
1034 | "source": [
1035 | "cohort_df = cohort_df[cohort_df[\"Modality\"] == \"CT\"].reset_index(drop = True)\n",
1036 | "\n",
1037 | "# randomly select one Series from the cohort\n",
1038 | "sid = random.choice(cohort_df[\"SeriesInstanceUID\"].values)\n",
1039 | "series_df = cohort_df[cohort_df[\"SeriesInstanceUID\"] == sid].reset_index(drop = True)"
1040 | ],
1041 | "metadata": {
1042 | "id": "cXFSN4q6-4cM"
1043 | },
1044 | "execution_count": 9,
1045 | "outputs": []
1046 | },
1047 | {
1048 | "cell_type": "code",
1049 | "source": [
1050 | "def download_series(download_path, series_df):\n",
1051 | "\n",
1052 | " \"\"\"\n",
1053 | " Download raw DICOM data and run dicomsort to standardise the input format.\n",
1054 | " Arguments:\n",
1055 | " download_path : required - path to the folder where the raw data will be downloaded.\n",
1056 | " patient_df : required - Pandas dataframe storing all the information required\n",
1057 | " to pull data from the IDC buckets.\n",
1058 | " \"\"\"\n",
1059 | "\n",
1060 | " s5cmd_gs_file_path = \"data/gcs_url_s5cmd.txt\"\n",
1061 | "\n",
1062 | " sid = series_df[\"PatientID\"].values[0]\n",
1063 | " download_path = os.path.join(download_path, sid)\n",
1064 | "\n",
1065 | " gcsurl_temp = \"cp \" + series_df[\"gcs_url\"].str.replace(\"gs://\",\"s3://\") + \" \" + download_path\n",
1066 | " gcsurl_temp.to_csv(s5cmd_gs_file_path, header=False, index=False)\n",
1067 | "\n",
1068 | " if not os.path.exists(download_path): os.mkdir(download_path)\n",
1069 | "\n",
1070 | " start_time = time.time()\n",
1071 | " print(\"Copying files from IDC buckets to %s...\"%(download_path))\n",
1072 | "\n",
1073 | " !s5cmd --no-sign-request --endpoint-url https://storage.googleapis.com run data/gcs_url_s5cmd.txt\n",
1074 | "\n",
1075 | " elapsed = time.time() - start_time\n",
1076 | " print(\"Done in %g seconds.\"%elapsed)\n",
1077 | "\n",
1078 | " return download_path"
1079 | ],
1080 | "metadata": {
1081 | "id": "aLkhmT5n_kOd"
1082 | },
1083 | "execution_count": 10,
1084 | "outputs": []
1085 | },
1086 | {
1087 | "cell_type": "code",
1088 | "source": [
1089 | "%%capture\n",
1090 | "\n",
1091 | "!mkdir data\n",
1092 | "\n",
1093 | "# cross-load data\n",
1094 | "download_path = download_series(download_path = \"data\", series_df = series_df)"
1095 | ],
1096 | "metadata": {
1097 | "id": "OKUQcWoh_rFe"
1098 | },
1099 | "execution_count": 11,
1100 | "outputs": []
1101 | },
1102 | {
1103 | "cell_type": "code",
1104 | "source": [
1105 | "ct_nrrd_path = \"data/image.nrrd\"\n",
1106 | "\n",
1107 | "convert_args_ct = {\"input\" : download_path,\n",
1108 | " \"output-img\" : ct_nrrd_path}"
1109 | ],
1110 | "metadata": {
1111 | "id": "2XhkdOUbAcRU"
1112 | },
1113 | "execution_count": 12,
1114 | "outputs": []
1115 | },
1116 | {
1117 | "cell_type": "code",
1118 | "source": [
1119 | "pypla.convert(verbose = True, **convert_args_ct)"
1120 | ],
1121 | "metadata": {
1122 | "colab": {
1123 | "base_uri": "https://localhost:8080/"
1124 | },
1125 | "id": "4GIWaHUuA6e1",
1126 | "outputId": "c4fe2781-c4ed-4c40-a6f9-feb64b132aa5"
1127 | },
1128 | "execution_count": 13,
1129 | "outputs": [
1130 | {
1131 | "output_type": "stream",
1132 | "name": "stdout",
1133 | "text": [
1134 | "\n",
1135 | "Running 'plastimatch convert' with the specified arguments:\n",
1136 | " --input data/LUNG1-327\n",
1137 | " --output-img data/image.nrrd\n",
1138 | "... Done.\n"
1139 | ]
1140 | }
1141 | ]
1142 | },
1143 | {
1144 | "cell_type": "code",
1145 | "source": [
1146 | "!ls -lh data"
1147 | ],
1148 | "metadata": {
1149 | "colab": {
1150 | "base_uri": "https://localhost:8080/"
1151 | },
1152 | "id": "Lh7ZT8YuBL1G",
1153 | "outputId": "32a52ddd-dd0d-4f4c-a610-880151e3b99a"
1154 | },
1155 | "execution_count": 14,
1156 | "outputs": [
1157 | {
1158 | "output_type": "stream",
1159 | "name": "stdout",
1160 | "text": [
1161 | "total 34M\n",
1162 | "-rw-r--r-- 1 root root 15K Sep 27 19:47 gcs_url_s5cmd.txt\n",
1163 | "-rw-r--r-- 1 root root 34M Sep 27 19:48 image.nrrd\n",
1164 | "drwxr-xr-x 2 root root 12K Sep 27 19:47 LUNG1-327\n"
1165 | ]
1166 | }
1167 | ]
1168 | }
1169 | ]
1170 | }
--------------------------------------------------------------------------------
/pyplastimatch/__init__.py:
--------------------------------------------------------------------------------
1 | from .utils import *
2 | from .pyplastimatch import *
3 | __version__ = "0.1"
4 |
--------------------------------------------------------------------------------
/pyplastimatch/pyplastimatch.py:
--------------------------------------------------------------------------------
1 | """
2 | ----------------------------------------
3 | PyPlastimatch
4 |
5 | dummy python plastimatch wrapper
6 | ----------------------------------------
7 |
8 | ----------------------------------------
9 | Author: Dennis Bontempi
10 | Email: dbontempi@bwh.harvard.edu
11 | ----------------------------------------
12 |
13 | """
14 |
15 | import os
16 | import json
17 | import subprocess
18 | from typing import Dict
19 |
20 | ## ----------------------------------------
21 |
22 | # FIXME: like this, every command is basically the same function with a line changed
23 | # define classes/something more fancy (for parsing etc.)?
24 | # Otherwise we may very well just have a function called "run_plastimatch_command"
25 | # and pass it also the command we want to run (convert, resample, etc.)
26 |
27 | def convert(verbose = True, path_to_log_file = None, return_bash_command = False, **kwargs):
28 | """
29 | Convert DICOM series to any supported file format.
30 |
31 | For additional details, see:
32 | https://plastimatch.org/plastimatch.html#plastimatch-convert
33 |
34 | Args:
35 | (GENERAL)
36 | path_to_log_file: path to file where stdout and stderr from the processing should be logged
37 | return_bash_command: return the executed command together with the exit status
38 |
39 | **kwargs: all the arguments parsable by 'plastimatch convert'
40 | Special Cases:
41 | - metadata (list): If provided as a list, each item is passed as a separate `--metadata` argument.
42 | """
43 |
44 | bash_command = list()
45 | bash_command += ["plastimatch", "convert"]
46 |
47 | for key, val in kwargs.items():
48 | if key == "metadata" and isinstance(val, list):
49 | for meta in val:
50 | bash_command += ["--metadata", str(meta)]
51 | else:
52 | bash_command += [f"--{key}", str(val)]
53 |
54 | if verbose:
55 | print("\nRunning 'plastimatch convert' with the specified arguments:")
56 | for arg in bash_command[2:]:
57 | print(f" {arg}")
58 |
59 | try:
60 | if path_to_log_file:
61 | with open(path_to_log_file, "a") as log_file:
62 | bash_exit_status = subprocess.run(bash_command,
63 | stdout = log_file, stderr = log_file,
64 | check = True)
65 | else:
66 | # if no log file is specified, output to the default stdout and stderr
67 | bash_exit_status = subprocess.run(bash_command, capture_output = True, check = True)
68 |
69 | if verbose: print("... Done.")
70 |
71 | except Exception as e:
72 | # if the process exits with a non-zero exit code, a CalledProcessError exception will be raised
73 | # attributes of that exception hold the arguments, the exit code, and stdout and stderr if they were captured
74 | # For details, see: https://docs.python.org/3/library/subprocess.html#subprocess.run
75 |
76 | # FIXME: return exception?
77 | print(e)
78 |
79 | if return_bash_command:
80 | return bash_command
81 |
82 | ## ----------------------------------------
83 |
84 | def resample(verbose = True, path_to_log_file = None, return_bash_command = False, **kwargs):
85 | """
86 | Resample any volume of a supported format.
87 |
88 | For additional details, see:
89 | https://plastimatch.org/plastimatch.html#plastimatch-resample
90 |
91 | Args:
92 | (GENERAL)
93 | path_to_log_file: path to file where stdout and stderr from the processing should be logged
94 | return_bash_command: return the executed command together with the exit status
95 |
96 | **kwargs: all the arguments parsable by 'plastimatch resample'
97 |
98 | """
99 |
100 | bash_command = list()
101 | bash_command += ["plastimatch", "resample"]
102 |
103 | for key, val in kwargs.items():
104 | bash_command += ["--%s"%(key), val]
105 |
106 | if verbose:
107 | print("\nRunning 'plastimatch resample' with the specified arguments:")
108 | for key, val in kwargs.items():
109 | print(" --%s"%(key), val)
110 |
111 | try:
112 | if path_to_log_file:
113 | with open(path_to_log_file, "a") as log_file:
114 | bash_exit_status = subprocess.run(bash_command,
115 | stdout = log_file, stderr = log_file,
116 | check = True)
117 | else:
118 | # if no log file is specified, output to the default stdout and stderr
119 | bash_exit_status = subprocess.run(bash_command, capture_output = True, check = True)
120 |
121 | if verbose: print("... Done.")
122 |
123 | except Exception as e:
124 | # if the process exits with a non-zero exit code, a CalledProcessError exception will be raised
125 | # attributes of that exception hold the arguments, the exit code, and stdout and stderr if they were captured
126 | # For details, see: https://docs.python.org/3/library/subprocess.html#subprocess.run
127 |
128 | # FIXME: return exception?
129 | print(e)
130 |
131 | if return_bash_command:
132 | return bash_command
133 |
134 | ## ----------------------------------------
135 |
136 | def dice(path_to_reference_img, path_to_test_img, verbose = True):
137 | """
138 | Compute Dice coefficient for binary label images.
139 |
140 | For additional details, see:
141 | https://plastimatch.org/plastimatch.html#plastimatch-dice
142 |
143 | Args:
144 | path_to_reference_img:
145 | path_to_test_img:
146 |
147 | Returns:
148 | dice_summary_dict:
149 |
150 | """
151 |
152 | bash_command = list()
153 | bash_command += ["plastimatch", "dice", "--dice"]
154 | bash_command += [path_to_reference_img, path_to_test_img]
155 |
156 | if verbose: print("\nComputing DC between the two images with 'plastimatch dice --dice'")
157 |
158 | try:
159 | dice_summary = subprocess.run(bash_command, capture_output = True, check = True)
160 | if verbose: print("... Done.")
161 | except Exception as e:
162 | # if the process exits with a non-zero exit code, a CalledProcessError exception will be raised
163 | # attributes of that exception hold the arguments, the exit code, and stdout and stderr if they were captured
164 | # For details, see: https://docs.python.org/3/library/subprocess.html#subprocess.run
165 |
166 | # FIXME: return exception?
167 | print(e)
168 |
169 | dice_summary = dice_summary.stdout.decode().splitlines()
170 |
171 | dice_summary_dict = dict()
172 | dice_summary_dict["com"] = dict()
173 |
174 | dice_summary_dict["com"]["ref"] = [float(dice_summary[1].split("\t")[1]),
175 | float(dice_summary[1].split("\t")[2]),
176 | float(dice_summary[1].split("\t")[3])]
177 |
178 | dice_summary_dict["com"]["cmp"] = [float(dice_summary[2].split("\t")[1]),
179 | float(dice_summary[2].split("\t")[2]),
180 | float(dice_summary[2].split("\t")[3])]
181 |
182 | dice_summary_dict["dc"] = float(dice_summary[7].split(":")[1])
183 |
184 | return dice_summary_dict
185 |
186 | ## ----------------------------------------
187 |
188 |
189 | def hd(path_to_reference_img, path_to_test_img, verbose = True):
190 | """
191 | Compute Hausdorff Distance for binary label images.
192 |
193 | For additional details, see:
194 | https://plastimatch.org/plastimatch.html#plastimatch-dice
195 |
196 | Args:
197 | path_to_reference_img:
198 | path_to_test_img:
199 | return_bash_command: return the executed command together with the exit status
200 |
201 | """
202 |
203 | bash_command = list()
204 | bash_command += ["plastimatch", "dice", "--hausdorff"]
205 | bash_command += [path_to_reference_img, path_to_test_img]
206 |
207 | if verbose: print("\nComputing the HD between the two images with 'plastimatch dice --hausdorff'")
208 |
209 | try:
210 | hausdorff_summary = subprocess.run(bash_command, capture_output = True, check = True)
211 | if verbose: print("... Done.")
212 | except Exception as e:
213 | # if the process exits with a non-zero exit code, a CalledProcessError exception will be raised
214 | # attributes of that exception hold the arguments, the exit code, and stdout and stderr if they were captured
215 | # For details, see: https://docs.python.org/3/library/subprocess.html#subprocess.run
216 |
217 | # FIXME: return exception?
218 | print(e)
219 |
220 | hausdorff_summary = hausdorff_summary.stdout.decode().splitlines()
221 |
222 | hausdorff_summary_dict = dict()
223 |
224 | hausdorff_summary_dict["hd"] = float(hausdorff_summary[0].split("=")[-1])
225 | hausdorff_summary_dict["hd95"] = float(hausdorff_summary[3].split("=")[-1])
226 |
227 | hausdorff_summary_dict["hd_boundaries"] = float(hausdorff_summary[4].split("=")[-1])
228 | hausdorff_summary_dict["hd95_boundaries"] = float(hausdorff_summary[-1].split("=")[-1])
229 |
230 | return hausdorff_summary_dict
231 |
232 | ## ----------------------------------------
233 |
234 | def compare(path_to_reference_img, path_to_test_img, verbose = True) -> Dict[str, float]:
235 | """
236 | The compare command compares two files by subtracting one file from the other, and reporting statistics of the difference image.
237 | The two input files must have the same geometry (origin, dimensions, and voxel spacing). The command line usage is given as follows:
238 |
239 | For additional details, see:
240 | https://plastimatch.org/plastimatch.html#plastimatch-compare
241 |
242 | Args:
243 | path_to_reference_img:
244 | path_to_test_img:
245 |
246 | Returns:
247 | dictionary:
248 | MIN Minimum value of difference image
249 | AVE Average value of difference image
250 | MAX Maximum value of difference image
251 | MAE Mean average value of difference image
252 | MSE Mean squared difference between images
253 | DIF Number of pixels with different intensities
254 | NUM Total number of voxels in the difference image
255 |
256 | """
257 |
258 | # print
259 | if verbose:
260 | print("\n Comparing two images with 'plastimatch compare'")
261 |
262 | # build command
263 | bash_command = []
264 | bash_command += ["plastimatch", "compare"]
265 | bash_command += [path_to_reference_img, path_to_test_img]
266 |
267 | # run command
268 | dice_summary = subprocess.run(bash_command, capture_output = True, check = True)
269 |
270 | # print
271 | if verbose:
272 | print("... Done.")
273 |
274 | # combine output
275 | comparison_raw = dice_summary.stdout.decode('utf-8')
276 |
277 | # flaten output
278 | comparison_flat = " ".join(comparison_raw.splitlines()).split()
279 | assert len(comparison_flat) == 14, "Unfamiliar output."
280 |
281 | # parse output into dictionary
282 | comparison_dict = {}
283 | for i in range(0, len(comparison_flat), 2):
284 | comparison_dict[comparison_flat[i]] = float(comparison_flat[i+1])
285 |
286 | # return dictionary
287 | return comparison_dict
288 |
289 | ## ----------------------------------------
290 |
--------------------------------------------------------------------------------
/pyplastimatch/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from .data import *
2 | from .eval import *
3 | from .install import install_precompiled_binaries
4 | #from .widgets import *
5 |
6 |
--------------------------------------------------------------------------------
/pyplastimatch/utils/data.py:
--------------------------------------------------------------------------------
1 | """
2 | ----------------------------------------
3 | PyPlastimatch
4 |
5 | Data utility functions
6 | ----------------------------------------
7 |
8 | ----------------------------------------
9 | Author: Dennis Bontempi
10 | Email: dbontempi@bwh.harvard.edu
11 | ----------------------------------------
12 |
13 | """
14 |
15 |
16 | import os
17 | import numpy as np
18 | import SimpleITK as sitk
19 |
20 |
21 | def save_binary_segmask(path_to_header_file, path_to_output, pred_binary_segmask):
22 |
23 | """
24 | blabla
25 |
26 | Args:
27 | path_to_header_file: path to the NRRD file to be read with SITK in order to copy the
28 | header information from it
29 | path_to_output: location where to save the binary segmask (in one of the ITK supported formats)
30 | pred_binary_segmask: numpy array storing the binary segmask to save
31 | """
32 |
33 | sitk_copy_header = sitk.ReadImage(path_to_header_file)
34 |
35 | sitk_pred_binary = sitk.GetImageFromArray(pred_binary_segmask)
36 | sitk_pred_binary.CopyInformation(sitk_copy_header)
37 | sitk.WriteImage(sitk_pred_binary, path_to_output)
38 |
--------------------------------------------------------------------------------
/pyplastimatch/utils/eval.py:
--------------------------------------------------------------------------------
1 | """
2 | ----------------------------------------
3 | PyPlastimatch
4 |
5 | Eval utility functions
6 | ----------------------------------------
7 |
8 | ----------------------------------------
9 | Author: Dennis Bontempi
10 | Email: dbontempi@bwh.harvard.edu
11 | ----------------------------------------
12 |
13 | """
14 |
15 | import os
16 | import json
17 | import numpy as np
18 | import pandas as pd
19 |
20 | def dc_dict_to_df(dc_dict, structure_name):
21 |
22 | """
23 | Format the Dice Coefficient results dictionary to a more human-readable Dataframe.
24 |
25 | Args:
26 | dc_dict: dictionary containing the results information.
27 | The dictionary should be formatted like the following (eval script output):
28 |
29 | {'LUNG1-002': {'heart': {'com': {'ref': [35.0662, -47.6561, -34.145],
30 | 'cmp': [35.0477, -49.1853, -34.787]},
31 | 'dc': 0.939273},
32 | 'esophagus': {'com': {'ref': [10.0202, -1.10146, 29.832],
33 | 'cmp': [7.15864, 2.45604, 34.21]},
34 | 'dc': 0.745591}},
35 | ...
36 | }
37 |
38 | structure_name: name of the structure the Dataframe should be about (e.g., "heart")
39 |
40 | """
41 |
42 | df_dict = dict()
43 |
44 | for pat in dc_dict.keys():
45 | pat_dict = dc_dict[pat][structure_name]
46 |
47 | tmp = dict()
48 |
49 | try:
50 | tmp["com_ref_x0"], tmp["com_ref_x1"], tmp["com_ref_x2"] = pat_dict["com"]["ref"]
51 | tmp["com_cmp_x0"], tmp["com_cmp_x1"], tmp["com_cmp_x2"] = pat_dict["com"]["cmp"]
52 | tmp["dc"] = pat_dict["dc"]
53 | except:
54 | tmp["com_ref_x0"], tmp["com_ref_x1"], tmp["com_ref_x2"] = np.full([3], np.nan).tolist()
55 | tmp["com_cmp_x0"], tmp["com_cmp_x1"], tmp["com_cmp_x2"] = np.full([3], np.nan).tolist()
56 | tmp["dc"] = np.nan
57 |
58 | df_dict[pat] = tmp
59 |
60 | return pd.DataFrame.from_dict(df_dict, orient = "index")
61 |
62 | ## ----------------------------------------
63 |
64 | def hd_dict_to_df(hd_dict, structure_name):
65 |
66 | """
67 | Format the Hausdorff Distance results dictionary to a more human-readable Dataframe.
68 |
69 |
70 | Args:
71 | hd_dict: dictionary containing the results information.
72 | The dictionary should be formatted like the following (eval script output):
73 |
74 | {'LUNG1-002': {'heart': {'hd': 8.999999,
75 | 'hd95': 1.5,
76 | 'hd_boundaries': 8.999999,
77 | 'hd95_boundaries': 7.373553},
78 | 'esophagus': {'hd': 31.477112,
79 | 'hd95': 8.945594,
80 | 'hd_boundaries': 31.477112,
81 | 'hd95_boundaries': 12.433664}},
82 | ...
83 | }
84 |
85 | structure_name: name of the structure the Dataframe should be about (e.g., "heart")
86 |
87 | """
88 |
89 | df_dict = dict()
90 |
91 | for pat in hd_dict.keys():
92 | pat_dict = hd_dict[pat][structure_name]
93 |
94 | tmp = dict()
95 |
96 | for key in pat_dict.keys():
97 | try:
98 | tmp[key] = pat_dict[key]
99 | except:
100 | tmp[key] = np.nan
101 |
102 | df_dict[pat] = tmp
103 |
104 | return pd.DataFrame.from_dict(df_dict, orient = "index")
105 |
106 | ## ----------------------------------------
107 |
108 |
--------------------------------------------------------------------------------
/pyplastimatch/utils/install.py:
--------------------------------------------------------------------------------
1 | """
2 | ----------------------------------------
3 | PyPlastimatch
4 |
5 | Setup utility function
6 | ----------------------------------------
7 |
8 | ----------------------------------------
9 | Author: Dennis Bontempi
10 | Email: dbontempi@bwh.harvard.edu
11 | ----------------------------------------
12 |
13 | """
14 |
15 | import os
16 | import sys
17 | import shutil
18 |
19 | import json
20 | import requests
21 | import urllib.request
22 |
23 | import subprocess
24 |
25 | import tempfile
26 |
27 | def get_distro_info():
28 | """
29 | Get the distribution info of the current system from "/etc/os-release"
30 | formatted in a dictionary fashion.
31 | """
32 |
33 | # check if the file exists
34 | if not os.path.exists("/etc/os-release"):
35 | raise FileNotFoundError("The file '/etc/os-release' does not exist.")
36 |
37 | # read the file
38 | with open("/etc/os-release", "r") as f:
39 | lines = f.readlines()
40 |
41 | # parse the file
42 | distro_info_dict = dict()
43 |
44 | for line in lines:
45 | key, val = line.replace("\n", "").split("=")
46 |
47 | # make the key lowercase
48 | key = key.lower()
49 |
50 | # if "val" is a string that starts end ends with quotes, remove them
51 | if val[0] == '"':
52 | val = val[1:-1]
53 | distro_info_dict[key] = val
54 |
55 | return distro_info_dict
56 |
57 | ## --------------------------------
58 |
59 | def move_binaries(binaries_path: str, verbose: bool) -> None:
60 |
61 | # copy the binaries to the right folder
62 | print("\nInstalling binaries...", end="")
63 | shutil.copy(binaries_path, "/usr/local/bin/plastimatch")
64 |
65 | # make the file executable
66 | subprocess.run(["chmod", "+x", "/usr/local/bin/plastimatch"])
67 | print(" Done.")
68 |
69 | # install dependencies
70 | print("Installing dependencies...", end="")
71 | sys.stdout.flush()
72 | install_dependencies(verbose=verbose)
73 | print(" Done.")
74 |
75 | ## --------------------------------
76 |
77 | def install_dependencies(verbose: bool) -> None:
78 |
79 | apt_update = ["apt-get", "update"]
80 |
81 | if verbose:
82 | subprocess.run(apt_update, capture_output=verbose, check=True)
83 | else:
84 | subprocess.run(
85 | apt_update,
86 | stdout=subprocess.DEVNULL,
87 | stderr=subprocess.STDOUT
88 | )
89 |
90 |
91 | dependency_list = ["libinsighttoolkit4-dev", "libdcmtk16", "libdlib19", "libfftw3-dev"]
92 | apt_install = ["apt-get", "install", "-y"] + dependency_list
93 |
94 | if verbose:
95 | subprocess.run(apt_install, capture_output=verbose, check=True)
96 | else:
97 | subprocess.run(
98 | apt_install,
99 | stdout=subprocess.DEVNULL,
100 | stderr=subprocess.STDOUT
101 | )
102 |
103 | ## --------------------------------
104 |
105 | def install_precompiled_binaries(verbose: bool=False) -> None:
106 | """
107 | Download the plastimatch binaries compiled for the specified distribution (if found).
108 |
109 | The information regarding the distribution is automatically parsed from "/etc/os-release"
110 | as a dictionary by the get_distro_info() function.
111 |
112 | Returns:
113 | str: path to the downloaded binaries, and info on the download status
114 | """
115 |
116 | distro_info_dict = get_distro_info()
117 |
118 | # get the distribution info
119 | distro_name = distro_info_dict["name"]
120 | distro_version_id = distro_info_dict["version_id"]
121 |
122 | print("PyPlastimatch Plastimatch installation utility.")
123 | print("NOTE: this utility is not meant to be replace the normal install of Plastimatch via apt.")
124 | print("Rather, it is meant to be used in case a Plastimatch binary is not available for a specific distribution.")
125 | print("\nSystem distribution: %s %s"%(distro_name, distro_version_id))
126 |
127 | releases_url = "https://api.github.com/repos/AIM-Harvard/pyplastimatch/releases"
128 | releases_dict = requests.get(releases_url).json()
129 |
130 | # request temp dir for the pull of the github release
131 | # (the directory will be deleted at upon exit from the function)
132 | temp_dir = tempfile.TemporaryDirectory()
133 |
134 | # get the latest release
135 | for release in releases_dict:
136 | release["tag_name"] == "latest"
137 | latest_release_dict = release
138 |
139 | # get the list of files in the release
140 | assets_list = latest_release_dict["assets"]
141 |
142 | # to check if the distribution is supported, check the release_meta.json file in the release
143 | meta_asset_name = "release_meta.json"
144 |
145 | # look for the release_meta.json file
146 | for asset in assets_list:
147 | if asset["name"] == meta_asset_name:
148 | meta_asset = asset
149 | break
150 |
151 | # download the file
152 | meta_browser_download_url = meta_asset["browser_download_url"]
153 | path_to_meta_json = os.path.join(temp_dir.name, meta_asset_name)
154 |
155 | print("\nDownloading meta JSON in the temp directory %s..."%path_to_meta_json, end="")
156 | print(" Done.")
157 |
158 | urllib.request.urlretrieve(meta_browser_download_url, path_to_meta_json)
159 |
160 | with open(path_to_meta_json, "r") as f:
161 | build_release_dict = json.load(f)
162 |
163 | # check if the distribution is supported
164 | supported = False
165 |
166 | for supported_distro_key in build_release_dict.keys():
167 | supported_distro = build_release_dict[supported_distro_key]
168 | if distro_name == supported_distro["name"] and \
169 | distro_version_id == supported_distro["version_id"]:
170 |
171 | supported = True
172 | break
173 |
174 | if supported:
175 | print("Matching distribution found in the latest PyPlastimatch release.")
176 | else:
177 | print("You system does not have a compiled binary in the latest PyPlastimatch release.")
178 |
179 | # the asset name is always going to be formatted as "plastimatch-$OS_${MAJOR_VERSION}_${MINOR_VERSION}"
180 | distro_asset_name = "plastimatch-%s_%s_%s"%(
181 | distro_name.lower(),
182 | distro_version_id.split(".")[0],
183 | distro_version_id.split(".")[1]
184 | )
185 |
186 | for asset in assets_list:
187 | if asset["name"] == distro_asset_name:
188 | distro_asset = asset
189 | break
190 |
191 | # download the asset
192 | browser_download_url = distro_asset["browser_download_url"]
193 | path_to_binaries = os.path.join(temp_dir.name, distro_asset_name)
194 |
195 | print("\nDownloading binary in the temp directory %s..."%path_to_binaries, end="")
196 | print(" Done.")
197 |
198 | urllib.request.urlretrieve(browser_download_url, path_to_binaries)
199 |
200 | move_binaries(path_to_binaries, verbose=verbose)
201 |
202 | temp_dir.cleanup()
--------------------------------------------------------------------------------
/pyplastimatch/utils/widgets.py:
--------------------------------------------------------------------------------
1 | """
2 | ----------------------------------------
3 | PyPlastimatch
4 |
5 | Viz tools for notebook testing
6 | ----------------------------------------
7 |
8 | ----------------------------------------
9 | Author: Dennis Bontempi
10 | Email: dbontempi@bwh.harvard.edu
11 | ----------------------------------------
12 |
13 | """
14 |
15 | import numpy as np
16 | import ipywidgets as ipyw
17 | import matplotlib.pyplot as plt
18 |
19 | from matplotlib.colors import ListedColormap
20 | from matplotlib.patches import Patch
21 |
22 | class AxialSliceFigure:
23 | """
24 | Base class for the interactive slice-wise visualisation widgets
25 |
26 | Init of all the figure parameters that are always set.
27 |
28 | @Params:
29 | ct_cmap - required : colormap for the CT viz.
30 | figsize - required : size of the figure (in inches).
31 | dpi - required : DPI of the figure.
32 | min_hu - required : minimum HU to be visualised
33 | max_hu - required : maximum HU to be visualised
34 | """
35 |
36 | def __init__(self, ct_cmap, figsize, dpi, min_hu, max_hu):
37 |
38 | self.ct_cmap = ct_cmap
39 | self.figsize = figsize
40 | self.dpi = dpi
41 |
42 | self.min_hu = min_hu
43 | self.max_hu = max_hu
44 |
45 |
46 | def views(self):
47 | """
48 | method description goes here
49 |
50 | """
51 |
52 | ipyw.interact(self.plot_slice,
53 | axial_idx = ipyw.IntSlider(min = 0, max = self.max_axial,
54 | step = 1, continuous_update = True,
55 | description = 'Axial slice:'))
56 |
57 | ## ----------------------------------------
58 | ## ----------------------------------------
59 |
60 | class AxialSliceComparison(AxialSliceFigure):
61 | """
62 | class description goes here
63 |
64 | """
65 |
66 | def __init__(self, ct_volume_left, ct_volume_right,
67 | title_left = "", title_right = "",
68 | ct_cmap = "gray", figsize = (12, 12), dpi = 100,
69 | min_hu = -1024, max_hu = 3072):
70 |
71 | super().__init__(ct_cmap, figsize, dpi, min_hu, max_hu)
72 |
73 | # the only constraint should be on the number of axial slice of the volumes to visualise
74 | assert ct_volume_left.shape[0] == ct_volume_right.shape[0]
75 |
76 | self.ct_volume_left = ct_volume_left
77 | self.ct_volume_right = ct_volume_right
78 | self.max_axial = self.ct_volume_left.shape[0] - 1
79 |
80 | self.title_left = title_left
81 | self.title_right = title_right
82 |
83 | ipyw.interact(self.views)
84 |
85 |
86 | def plot_slice(self, axial_idx):
87 | """
88 | method description goes here
89 |
90 | """
91 |
92 | fig, (ax_left, ax_right) = plt.subplots(1, 2, figsize = self.figsize, dpi = self.dpi)
93 |
94 | # plot CT axial slices
95 | ax_left.set_title(self.title_left)
96 | ax_left.imshow(self.ct_volume_left[axial_idx, :, :], cmap = self.ct_cmap,
97 | vmin = self.min_hu, vmax = self.max_hu)
98 |
99 | ax_right.set_title(self.title_right)
100 | ax_right.imshow(self.ct_volume_right[axial_idx, :, :], cmap = self.ct_cmap,
101 | vmin = self.min_hu, vmax = self.max_hu)
102 |
103 | plt.subplots_adjust(hspace = 0.2)
104 | plt.show()
105 |
106 |
107 | ## ----------------------------------------
108 | ## ----------------------------------------
109 |
110 | class AxialSliceSegmaskComparison(AxialSliceFigure):
111 | """
112 | class description goes here
113 |
114 | """
115 |
116 | def __init__(self, ct_volume, segmask_ai_dict, segmask_manual_dict,
117 | segmask_cmap_dict, segmask_alpha = 0.6, title_left = "", title_right = "",
118 | ct_cmap = "gray", figsize = (12, 12), dpi = 100,
119 | min_hu = -1024, max_hu = 3072):
120 |
121 | super().__init__(ct_cmap, figsize, dpi, min_hu, max_hu)
122 |
123 | self.ct_volume = ct_volume
124 | self.max_axial = self.ct_volume.shape[0] - 1
125 |
126 | self.segmask_ai_dict = segmask_ai_dict
127 | self.segmask_manual_dict = segmask_manual_dict
128 |
129 | self.segmask_cmap_dict = segmask_cmap_dict
130 | self.segmask_alpha = segmask_alpha
131 |
132 | self.title_left = title_left
133 | self.title_right = title_right
134 |
135 | ipyw.interact(self.views)
136 |
137 |
138 | def plot_slice(self, axial_idx):
139 | """
140 | method description goes here
141 |
142 | """
143 |
144 | fig, (ax_ai, ax_manual) = plt.subplots(1, 2, figsize = self.figsize, dpi = self.dpi)
145 |
146 | # plot CT axial slices
147 | ax_ai.set_title(self.title_left)
148 | ax_ai.imshow(self.ct_volume[axial_idx, :, :], cmap = self.ct_cmap,
149 | vmin = self.min_hu, vmax = self.max_hu)
150 |
151 | ax_manual.set_title(self.title_right)
152 | ax_manual.imshow(self.ct_volume[axial_idx, :, :], cmap = self.ct_cmap,
153 | vmin = self.min_hu, vmax = self.max_hu)
154 |
155 | # check the lenght of cmaps is enough to colour all the structures
156 | assert len(self.segmask_cmap_dict) == np.max([len(self.segmask_ai_dict),
157 | len(self.segmask_manual_dict)])
158 |
159 | # plot overlaying masks
160 | for key in self.segmask_ai_dict:
161 | segmask_ai = self.segmask_ai_dict[key]
162 | segmask_cmap = self.segmask_cmap_dict[key]
163 | ax_ai.imshow(segmask_ai[axial_idx, :, :], label = key,
164 | cmap = segmask_cmap, alpha = self.segmask_alpha)
165 |
166 | for key in self.segmask_manual_dict:
167 | segmask_manual = self.segmask_manual_dict[key]
168 | segmask_cmap = self.segmask_cmap_dict[key]
169 | ax_manual.imshow(segmask_manual[axial_idx, :, :], label = key,
170 | cmap = segmask_cmap, alpha = self.segmask_alpha)
171 |
172 | plt.subplots_adjust(hspace = 0.2)
173 | plt.show()
174 |
175 | ## ----------------------------------------
176 | ## ----------------------------------------
177 |
178 | class AxialSliceSegmaskViz(AxialSliceFigure):
179 | """
180 | class description goes here
181 |
182 | """
183 |
184 | def __init__(self, ct_volume, segmask_dict, segmask_cmap_dict,
185 | segmask_alpha = 0.4,
186 | random_cmap = False, # FIXME - legend in the figure is wrong
187 | fig_title = "",
188 | ct_cmap = "gray", figsize = (6, 6), dpi = 100,
189 | min_hu = -1024, max_hu = 3072):
190 |
191 | """
192 | constructor description goes here
193 |
194 | """
195 |
196 | super().__init__(ct_cmap, figsize, dpi, min_hu, max_hu)
197 |
198 | self.ct_volume = ct_volume
199 | self.max_axial = self.ct_volume.shape[0] - 1
200 |
201 | self.segmask_dict = segmask_dict
202 |
203 | self.fig_title = fig_title
204 |
205 | if random_cmap:
206 | self.segmask_cmap_dict = self.get_random_cmap_dict(segmask_dict.keys())
207 | else:
208 | self.segmask_cmap_dict = segmask_cmap_dict
209 |
210 | self.segmask_alpha = segmask_alpha
211 |
212 | ipyw.interact(self.views)
213 |
214 |
215 | def get_random_cmap_dict(self, dict_keys):
216 | """
217 | method description goes here
218 |
219 | """
220 |
221 | segmask_cmap_dict = dict()
222 |
223 | for idx, key in enumerate(dict_keys):
224 | # dummy random colormap creation
225 | vals = np.linspace(0, 1, 256)
226 | np.random.shuffle(vals)
227 |
228 | # color map to sample from
229 | my_cmap = plt.cm.jet(vals)
230 | my_cmap[:, -1] = np.linspace(0, 1, 256)
231 |
232 | segmask_cmap_dict[key] = ListedColormap(my_cmap)
233 |
234 | return segmask_cmap_dict
235 |
236 |
237 | def plot_slice(self, axial_idx):
238 | """
239 | method description goes here
240 |
241 | """
242 |
243 | fig, ax = plt.subplots(1, 1, figsize = self.figsize, dpi = self.dpi)
244 |
245 | try:
246 | len(legend_elements)
247 | except:
248 | legend_elements = list()
249 |
250 | # plot CT axial slices
251 | ax.set_title(self.fig_title)
252 | ax.imshow(self.ct_volume[axial_idx, :, :], cmap = self.ct_cmap,
253 | vmin = self.min_hu, vmax = self.max_hu)
254 |
255 | # check the lenght of cmaps is enough to colour all the structures
256 | assert len(self.segmask_cmap_dict) == len(self.segmask_dict)
257 |
258 | # plot overlaying masks
259 | for key in self.segmask_dict:
260 | segmask = self.segmask_dict[key]
261 | segmask_cmap = self.segmask_cmap_dict[key]
262 |
263 | ax.imshow(segmask[axial_idx, :, :], label = key,
264 | cmap = segmask_cmap, alpha = self.segmask_alpha)
265 |
266 | if not len(legend_elements) == len(self.segmask_dict):
267 | legend_elements.append(Patch(facecolor = segmask_cmap(0.7),
268 | edgecolor = 'k',
269 | label = key))
270 |
271 | plt.subplots_adjust(hspace = 0.2)
272 | plt.show()
273 |
274 | ax.legend(handles = legend_elements,
275 | loc = 'upper left',
276 | bbox_to_anchor = (1, 1))
277 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=61.0"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "pyplastimatch"
7 | version = "0.4.6"
8 | authors = [
9 | { name="Dennis Bontempi", email="dbontempi@bwh.harvard.edu" },
10 | { name="Leonard Nuernberg", email="lnuernberg@bwh.harvard.edu" },
11 | ]
12 | maintainers = [
13 | { name="Dennis Bontempi", email="dbontempi@bwh.harvard.edu" },
14 | { name="Leonard Nuernberg", email="lnuernberg@bwh.harvard.edu" },
15 | { name="Andrey Fedorov", email="andrey.fedorov@gmail.com" },
16 | ]
17 | description = "Basic Python wrapper for Plastimatch"
18 | readme = "README.md"
19 | license = { file="LICENSE" }
20 | requires-python = ">=3.7"
21 | classifiers = [
22 | "Programming Language :: Python :: 3",
23 | "Operating System :: POSIX :: Linux",
24 | ]
25 | dynamic = [
26 | "dependencies"
27 | ]
28 |
29 | [project.urls]
30 | "Homepage" = "https://github.com/ImagingDataCommons/pyplastimatch"
31 | "Bug Tracker" = "https://github.com/ImagingDataCommons/pyplastimatch/issues"
32 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | itk
2 | matplotlib
3 | numpy
4 | pandas
5 | pydicom
6 | requests
7 | scikit-image
8 | SimpleITK
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name="pyplastimatch",
5 | version="0.4.6",
6 | description="Basic Python wrapper for Plastimatch",
7 | url="https://github.com/ImagingDataCommons/pyplastimatch",
8 | author="Dennis Bontempi",
9 | license=" BSD-3-Clause license",
10 | packages=[
11 | "pyplastimatch",
12 | "pyplastimatch.utils"
13 | ],
14 | install_requires=[
15 | "itk",
16 | "matplotlib",
17 | "numpy",
18 | "pandas",
19 | "pydicom",
20 | "requests",
21 | "scikit-image",
22 | "SimpleITK"
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------