{ "cells": [ { "cell_type": "markdown", "id": "c9a2425d-49a8-4060-8af9-a078a7ca7475", "metadata": {}, "source": [ "# Getting started with PySkinDose\n", "Welcome! This is a Jupyter Notebook based getting-started tutorial for PySkinDose, which contains tools and scripts for calculating estimates of peak skin dose and to create dose maps for fluoroscopic exams from RDSR data\n", "\n", "The tutorial will be updated when more functionality is added or modified.\n", "\n", "The latest version of the tutorial is available on GitHub at [https://github.com/rvbCMTS/PySkinDose/blob/master/docs/source/getting_started/getting_started.ipynb](https://github.com/rvbCMTS/PySkinDose/blob/master/docs/source/getting_started/getting_started.ipynb).\n", "\n", "PyPI: [https://pypi.org/project/pyskindose/](https://pypi.org/project/pyskindose/)\n", "\n", "Code repository: [https://github.com/rvbCMTS/PySkinDose](https://github.com/rvbCMTS/PySkinDose)\n", "\n", "Documentation: [https://pyskindose.readthedocs.io/en/latest/](https://pyskindose.readthedocs.io/en/latest/)\n" ] }, { "cell_type": "markdown", "id": "65c80f08-d40f-41fe-8c8d-ebe6070dd3ec", "metadata": {}, "source": [ "## PART I: Settings " ] }, { "cell_type": "markdown", "id": "70d7cd1d", "metadata": {}, "source": [ "This tutorial generates several interactive plots directly within the notebook. If you prefer to view them in a separate browser tab, simply uncomment the following two lines and set `settings.plot.notebook_mode` to False when running PySkinDose.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "2c4f3f56", "metadata": {}, "outputs": [], "source": [ "# import plotly.io as pio\n", "# pio.renderers.default = \"browser\"" ] }, { "cell_type": "markdown", "id": "85e68b59-b761-4ed1-bc2c-9ff2463d03f3", "metadata": {}, "source": [ "We need to start our session with adding the class PyskindoseSettings for parsing user defined settings:" ] }, { "cell_type": "code", "execution_count": null, "id": "b61afc58-ec9f-48e2-9231-9a5a908e6a5c", "metadata": {}, "outputs": [], "source": [ "from pyskindose import (\n", " PyskindoseSettings,\n", " load_settings_example_json,\n", " print_available_human_phantoms,\n", " get_path_to_example_rdsr_files,\n", " print_example_rdsr_files,\n", ")\n", "from pyskindose.main import main" ] }, { "cell_type": "markdown", "id": "2de6a01c-a52e-4558-9bcd-58b3bb05126d", "metadata": {}, "source": [ "Once completed, we need to initialize all the user defined settings. Lets load a template of pre-loaded settings (located in `settings_example.json`), then we can change each of the individual settings prior to calculating skin dose estimate." ] }, { "cell_type": "code", "execution_count": null, "id": "83490313-f4be-4ec0-8f8f-06d03a669226", "metadata": {}, "outputs": [], "source": [ "# Parse the settings to a setting class:\n", "settings_json = load_settings_example_json()\n", "settings = PyskindoseSettings(settings=settings_json)" ] }, { "cell_type": "markdown", "id": "8547c150-7c47-4173-90ea-ea2ad9964270", "metadata": {}, "source": [ "Now, all the settings are populated in `settings`. We can print all user defined setting by typing `settings.print_parameters()`:" ] }, { "cell_type": "code", "execution_count": null, "id": "c96c974c-cb21-464c-8ce1-ec6790024939", "metadata": {}, "outputs": [], "source": [ "settings.print_parameters()" ] }, { "cell_type": "markdown", "id": "07a55d3e-f007-4bff-82e1-165ecacfdb2d", "metadata": {}, "source": [ "As seen in the above output, `settings` includes the sections __general__, __phantom__, __plot__ and __normalisation__. \n", "\n", "You can access each of the settings in __general__ by simply typing `settings.mode`, `settings.k_tab_val` etc. For the other sections, include the section name, e.g. `settings.phantom.patient_orientation`.\n", "\n", "You can read more about each setting by adressing the corresponding docstring with the `__doc__` attribute. E.g: `settings.__doc__` returns detailed descriptions of the setting in __general__, i.e., \"mode\", \"k_tab_val\", \"estimate_k_tab\", and \"rdsr_filename\". \n", "\n", "Uncomment any of the following lines for more information regarding each of the subsections in `settings`.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "8f44c480-0a73-4921-96c6-b0a4c8f3c465", "metadata": {}, "outputs": [], "source": [ "# print(settings.__doc__) # uncomment this line to read about settings.general\n", "# print(settings.phantom.__doc__) # uncomment this line to read about settings.phantom\n", "# print(settings.phantom.patient_offset.__doc__) # uncomment this line to read about settings.phantom.patient_offset\n", "# print(settings.phantom.dimension.__doc__) # uncomment this line to read about settings.phantom.dimension\n", "# print(settings.plot.__doc__) # uncomment this line to read about settings.plot\n", "# print(settings.normalization_settings.__doc__) # uncomment this line to read about settings.normalisation_settings" ] }, { "cell_type": "markdown", "id": "fec17d93-48b5-4d54-9d14-09a415ca024f", "metadata": {}, "source": [ "## PART II: Setup of the skin dose calculation geometry" ] }, { "cell_type": "markdown", "id": "4665e721-9935-4b3a-a4df-f1c0b9c6de23", "metadata": {}, "source": [ "Now, lets have a look on the geometry in which the skin dose is calculated. PySkinDose has a built in mode for plotting the setup (i.e. the phantom and its position upon the patient support table). This is displayed by running the cell below with `settings.mode` = \"plot_setup\".\n", "\n", "The position of the phantom can be modified by changing the translation parameters in `settings.phantom.patient_offset`. \n" ] }, { "cell_type": "code", "execution_count": null, "id": "2290d2af-b61b-4827-832b-aab10465aa35", "metadata": {}, "outputs": [], "source": [ "settings = PyskindoseSettings(settings=load_settings_example_json())\n", "settings.mode = \"plot_setup\"\n", "settings.phantom.model = \"cylinder\"\n", "\n", "# Uncomment any of the following lines to translate the position of the phantom:\n", "# settings.phantom.patient_offset.d_lat = -20\n", "# settings.phantom.patient_offset.d_lon = +10\n", "# settings.phantom.patient_offset.d_ver = -10\n", "\n", "main(settings=settings)" ] }, { "cell_type": "markdown", "id": "ee930e3c-5ef4-4a85-8a49-11d54afcd2d1", "metadata": {}, "source": [ "In the above output, you will see the phantom and its position upon the support table. The X-ray source, beam, and image receptor is also added in standard position (AP1=AP2=0)\n", "\n", "\n", "Select your phantom by setting `settings.phantom.model` to either \"plane\", \"cylinder\", or \"human\". Cylindrical phantoms are great for general purposes but for skin dose map visualisation based patient data a human phantom is the best option. If you select \"human\", you need to specify what human phantom to use. Our current default phantom is named \"hudfrid\" which is an adult male phantom slightly optimized for skin dose estimations. \n", "\n", "Several more advanced phantoms (e.g. for neurointerventions) are in development. If you select any of the mathematical phantoms (plane or cylinder), then you can tweak settings such as length and radius in `settings.phantom.dimension`. You can also change the dimension of the patient support table and pad." ] }, { "cell_type": "code", "execution_count": null, "id": "1ed64f1d-4d57-4c05-8491-35b43754eaec", "metadata": {}, "outputs": [], "source": [ "settings = PyskindoseSettings(settings=load_settings_example_json())\n", "settings.mode = \"plot_setup\"\n", "\n", "# Select an elliptical cylinder with length 150 cm and a \n", "# radius of[20, 10] cm\n", "# settings.phantom.model = \"cylinder\"\n", "# settings.phantom.dimension.cylinder_length = 150\n", "# settings.phantom.dimension.cylinder_radii_a = 20\n", "# settings.phantom.dimension.cylinder_radii_b = 10\n", "\n", "# Select a planar phantom with length 120 cm and width 40 cm\n", "# settings.phantom.model = \"plane\"\n", "# settings.phantom.dimension.plane_length = 120\n", "# settings.phantom.dimension.plane_width = 40\n", "\n", "# Select a human phantom\n", "settings.phantom.model = \"human\"\n", "settings.phantom.human_mesh = \"hudfrid\"\n", "\n", "main(settings=settings)" ] }, { "cell_type": "markdown", "id": "d80ce60b", "metadata": {}, "source": [ "Use `print_available_human_phantoms()` to show currently available human phantoms. To change human phantom, rerun the previous cell after replacing `hudfrid` with any of the options presented in `print_available_human_phantoms()`\n" ] }, { "cell_type": "code", "execution_count": null, "id": "e851494b", "metadata": {}, "outputs": [], "source": [ "print_available_human_phantoms()" ] }, { "cell_type": "markdown", "id": "833ffbd5", "metadata": {}, "source": [ "Once you are satisfied with the selection of patient phantom, and the patient position on the support table, we can move on to examine the contents of an RDSR file prior to calculating the skin dose estimate." ] }, { "cell_type": "markdown", "id": "4fc40fd0", "metadata": {}, "source": [ "## PART III: Load and examine an RDSR file" ] }, { "cell_type": "markdown", "id": "a670f6df", "metadata": {}, "source": [ "PySkinDose contains the following example RDSR files: " ] }, { "cell_type": "code", "execution_count": null, "id": "34bb18cd", "metadata": {}, "outputs": [], "source": [ "print_example_rdsr_files()" ] }, { "cell_type": "markdown", "id": "0b02b619", "metadata": {}, "source": [ "A useful tool during analysis of individual procedures is to visualize the orientation and size of the x-ray beam in every irradiation event one by one. In the example below the `settings.mode` is changed to `plot_procedure`. With this, you can use the slider in the plot to scroll through each irradiation event in the study. \n", "\n", "Please be aware that the mode `plot_procedure` can be quite computationally intensive when the patient phantom is included in the interactive plot. To reduce this load, you can disable the patient phantom by adjusting the `settings.plot.max_events_for_patient_inclusion` to a value smaller than the number of irradiation events in the RDSR file.\n", "\n", "Now, lets load the siemens axiom artis example procedure and visualize the procedure with `plot_procedure`" ] }, { "cell_type": "code", "execution_count": null, "id": "85920ff8-0729-40ce-b066-5ea63930a423", "metadata": {}, "outputs": [], "source": [ "settings = PyskindoseSettings(settings=load_settings_example_json())\n", "settings.mode = \"plot_procedure\"\n", "settings.phantom.model = \"cylinder\"\n", "\n", "rdsr_data_dir = get_path_to_example_rdsr_files()\n", "\n", "# You can set the maximum number of irradiation events for including the\n", "# phantom in the plot. Here, we set it to 0 to reduce memory use.\n", "settings.plot.max_events_for_patient_inclusion = 0\n", "\n", "# Change this path to use your own RDSR file\n", "# N.B: If your are using a windows OS, you need to set the path as r raw string,\n", "# for example: \n", "# selected_rdsr_filepath = Path(r'c:\\rdsr_files\\file_name.dcm')\n", "selected_rdsr_filepath = rdsr_data_dir / \"siemens_axiom_example_procedure.dcm\"\n", "\n", "main(settings=settings, file_path=selected_rdsr_filepath)" ] }, { "cell_type": "markdown", "id": "d2fb2e78", "metadata": {}, "source": [ "To use your own RDSR file, just change the `file_path` to the location of your RDSR file." ] }, { "cell_type": "markdown", "id": "d8580806db62171b", "metadata": { "collapsed": false }, "source": [ "## PART IV: Calculate and plot a dosemap" ] }, { "cell_type": "markdown", "id": "5ec95719", "metadata": {}, "source": [ "At this point, you should be satisfied with your choice of patient phantom, the phantom position on the support table and also understand the contents in the selected RDSR-file (from the `settings.plot.plot_dosemap` function).\n", "\n", "Now we can finally address the purpose of PySkinDose, which is to calculate the skin dose estimate and visualize the result in a dosemap plot. \n", "\n", "A plot will illustrate the skin dose estimation map based on information in the RDSR-file together with available correction factors for the specific X-ray device. The plot is interactive so the phantom can rotate and if you hover the mouse over the phantom an estimate of skin dose will appear." ] }, { "cell_type": "code", "execution_count": null, "id": "5528235c", "metadata": {}, "outputs": [], "source": [ "settings = PyskindoseSettings(settings=load_settings_example_json())\n", "settings.mode = \"calculate_dose\"\n", "settings.output_format = 'html' # set output format to html\n", "settings.plot.plot_dosemap = True # enable dosemap plot\n", "settings.phantom.model = \"human\"\n", "settings.phantom.human_mesh = \"hudfrid\"\n", "\n", "settings.phantom.patient_offset.d_lat = -35\n", " \n", "main(settings=settings, file_path=rdsr_data_dir / \"siemens_axiom_example_procedure.dcm\")\n" ] }, { "cell_type": "markdown", "id": "43a5b3f9", "metadata": {}, "source": [ "If you want to examine, analyze or save the output of the calculated skin dose estimation, you need to change output mode to either __dict__ or __json__:" ] }, { "cell_type": "code", "execution_count": null, "id": "bc103caa", "metadata": {}, "outputs": [], "source": [ "settings = PyskindoseSettings(settings=load_settings_example_json())\n", "settings.mode = \"calculate_dose\"\n", "settings.output_format = 'dict'\n", "\n", "rdsr_data_dir = get_path_to_example_rdsr_files()\n", "output = main(settings=settings, file_path=rdsr_data_dir / \"siemens_axiom_example_procedure.dcm\")\n", "\n", "print(f'estimated psd {output[\"psd\"].round(1)} mGy')" ] }, { "cell_type": "markdown", "id": "594b2616", "metadata": {}, "source": [ "The output (in this case a dictionary) contains the following parameters from the\n", "conducted skin dose calculation.\n", "- **table**\n", " - parameters regarding the patient support table\n", "- **pad**\n", " - parameters regarding the patient support pad\n", "- **patient**\n", " - parameters regarding the phantom\n", "- **dose_map**\n", " - skin patch index and estimated skin dose value for each patch on the phantom\n", "- **psd**\n", " - Estimated peak skin dose, i.e., max(dose_map)\n", "- **air_kerma**\n", " - Air kerma, i.e., psd but without the correction factors\n", "- **events**\n", " - parameters on translation, rotation for each irradiation event\n", "- **corrections**\n", " - The correction factor used to convert air kerma to skin dose. This includes\n", " - backscatter correction\n", " - medium correction\n", " - table correction\n", " - inverse square law correction\n" ] }, { "cell_type": "markdown", "id": "a1647009cb57c086", "metadata": { "collapsed": false }, "source": [ "## Adding/Altering correction factors\n", "\n", "The fine-tuning and optimization of correction factors are, and will always be, a work in progress. Currently, our priority is to implement compliance across additional X-ray devices. One key correction that can be easily adjusted is the estimated attenuation for the patient support table and pad. This can be configured by setting `k_tab_val` to a value between 0 and 1, with 1 indicating that the X-ray beam is not attenuated at all by the table and pad.\n", "\n", "Lets try to lower the table and pad correction factor (that is, X-ray beam transmission) and observe the difference in the skin dose map. " ] }, { "cell_type": "code", "execution_count": null, "id": "eef10eea", "metadata": {}, "outputs": [], "source": [ "settings = PyskindoseSettings(settings=load_settings_example_json())\n", "settings.mode = \"calculate_dose\"\n", "settings.output_format = 'html' # set output format to html\n", "settings.plot.plot_dosemap = True # enable dosemap plot\n", "settings.phantom.model = \"human\"\n", "settings.phantom.human_mesh = \"hudfrid\"\n", "\n", "settings.phantom.patient_offset.d_lat = -35\n", "settings.k_tab_val = 0.5\n", "main(settings=settings, file_path=rdsr_data_dir / \"siemens_axiom_example_procedure.dcm\")\n" ] }, { "cell_type": "markdown", "id": "381a15f6", "metadata": {}, "source": [ "In the above output, we note that the skin dose estimates on the back of the phantom is decreased due to increase attenuation in the patient support table and pad. However, the skin dose estimates on the side of the phantom remain the same as before since the X-rays never pass the table and pad.\n" ] }, { "cell_type": "markdown", "id": "d304981aa175afa", "metadata": { "collapsed": false }, "source": [ "## Further development and contributions" ] }, { "cell_type": "markdown", "id": "31b6b893", "metadata": {}, "source": [ "Contributions and collaborations for further development of PySkinDose is greatly appreciated. Please visit the link below for suggested topics.\n", "[https://pyskindose.readthedocs.io/en/latest/user/contribute/](https://pyskindose.readthedocs.io/en/latest/user/contribute.html)\n", "\n", "If you encounter issues using PySkinDose please describe them as detailed as possible at the project GIT linked below\n", "[https://github.com/rvbCMTS/PySkinDose/issues/](https://github.com/rvbCMTS/PySkinDose/issues/)" ] } ], "metadata": { "kernelspec": { "display_name": "pyskindose", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.18" } }, "nbformat": 4, "nbformat_minor": 5 }