├── .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: [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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 | "\"Open" 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 | --------------------------------------------------------------------------------