diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..0c635c5 Binary files /dev/null and b/.DS_Store differ diff --git a/.github/workflows/deploy_gh_page.yml b/.github/workflows/deploy_gh_page.yml index fcee419..fe3c5cc 100644 --- a/.github/workflows/deploy_gh_page.yml +++ b/.github/workflows/deploy_gh_page.yml @@ -4,6 +4,7 @@ on : push : branches : - main + - update_examples permissions: id-token: write @@ -23,6 +24,12 @@ jobs : python -m pip install --upgrade pip pip install sphinx sphinx-gallery sphinx-book-theme pip install -r requirements.txt + git clone https://gitlab.com/openmcsquare/opentps.git + pip install ./opentps + pip install --upgrade pip setuptools wheel + pip install tigre[cpu] # CPU-only install if available + + - name: Build the docs run: | make html @@ -40,4 +47,4 @@ jobs : uses: actions/deploy-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - artifact_name: github-pages \ No newline at end of file + artifact_name: github-pages diff --git a/.gitignore b/.gitignore index 3825826..4250d5b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,21 @@ _build auto_examples/** !auto_examples/**/*.ipynb !auto_examples/**/ +examples/**/output/ .idea + +auto_community/** +!auto_community/**/*.ipynb +!auto_community/**/ + + +community/** +!community/**/*.py +!community/**/*.rst +!community/**/ + +examples/** +!examples/**/*.py +!examples/**/*.rst +!examples/**/ diff --git a/_static/OpenTPS_logo_dark_big.png b/_static/OpenTPS_logo_dark_big.png new file mode 100644 index 0000000..9405943 Binary files /dev/null and b/_static/OpenTPS_logo_dark_big.png differ diff --git a/_templates/my_header.html b/_templates/my_header.html index db48c31..d874661 100644 --- a/_templates/my_header.html +++ b/_templates/my_header.html @@ -64,7 +64,7 @@ var fileName = pathParts.pop().replace('.html', '.ipynb'); // Check if the forelast word is 'auto_examples' - if (foreforelastWord === 'auto_examples'&& fileName !== 'index.ipynb') { + if ((foreforelastWord === 'auto_examples'||foreforelastWord === 'auto_community')&& fileName !== 'index.ipynb') { // Show the button var colabButton = document.getElementById('colab-button'); colabButton.style.display = 'inline-block'; diff --git a/auto_community/CommunityExample/SimpleDoseComputationAndOptOnCT.ipynb b/auto_community/CommunityExample/SimpleDoseComputationAndOptOnCT.ipynb new file mode 100644 index 0000000..c9547ee --- /dev/null +++ b/auto_community/CommunityExample/SimpleDoseComputationAndOptOnCT.ipynb @@ -0,0 +1,356 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Simple dose computation and optimization on a real CT image\nauthor: Eliot Peeters\n\nIn this example we are going to see how to : \n\n- Import real dicom images and RT struct \n- Create a plan \n- Compute beamlets \n- Optimize a plan with beamlets \n- Save a plan and beamlets \n- Compute DVH histograms \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport os\nfrom matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.plan import ProtonPlanDesign\nfrom opentps.core.data import DVH\nfrom opentps.core.io import mcsquareIO\nfrom opentps.core.io.scannerReader import readScanner\nfrom opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig\nfrom opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator\nfrom opentps.core.io.dataLoader import readData\nfrom opentps.core.data.plan import ObjectivesList\nfrom opentps.core.data.plan import FidObjective\nfrom opentps.core.io.serializedObjectIO import saveBeamlets, saveRTPlan, loadBeamlets, loadRTPlan" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the next cell we configure the CT scan model used for the dose calculation and the bdl model. The ones used in this example are the default configuration of openTPS wich may lead to some imprecision.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ctCalibration = readScanner(DoseCalculationConfig().scannerFolder)\nbdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data importation\nThe dataset used in this example comes from the [Proknow website](https://proknowsystems.com/planning/studies/5a0f6aa074403fbcc665424c1b13eaf2/instructions), 2018 TROG Plan Study: SRS Brain. The readData functions automatically import the subfolders and detects the type of data (CT or RT_struct).\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ctImagePath = \"Path_to_\\ProKnows_2018_TROG_Plan_Study_SRS_Brain\" #The folder is initially named 'data'\ndata = readData(ctImagePath)\n\nrt_struct = data[0]\nct = data[1]\n\nrt_struct.print_ROINames()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the purpose of the demonstration we are going to use only 3 different ROI. Note that it is important to specify the CT origin,gridSize and spacing to the getBinaryMask function in order to have a correct binary mask.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "target_name = \"GTV4-20Gy\"\ntarget = rt_struct.getContourByName(target_name).getBinaryMask(origin=ct.origin,gridSize=ct.gridSize,spacing=ct.spacing)\n\nOAR_brain = rt_struct.getContourByName(\"Brain\").getBinaryMask(origin=ct.origin,gridSize=ct.gridSize,spacing=ct.spacing)\nOAR_brainstem = rt_struct.getContourByName(\"Brainstem\").getBinaryMask(origin=ct.origin,gridSize=ct.gridSize,spacing=ct.spacing)\nOAR_optic_chiasm = rt_struct.getContourByName(\"Optic Chiasm\").getBinaryMask(origin=ct.origin,gridSize=ct.gridSize,spacing=ct.spacing)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For further plots we can extract the indexes of the centerOfMass of the tumor.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "COM_coord = target.centerOfMass\nCOM_index = target.getVoxelIndexFromPosition(COM_coord)\nZ_COORD = COM_index[2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MCsquare configuration\nWe now initialize a MCsquareDoseCalculator and provide the beamModel and ctCalibration imported above.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "mc2 = MCsquareDoseCalculator()\nmc2.beamModel = bdl\nmc2.ctCalibration = ctCalibration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plan design\n\n In the next section we create a planDesign object with 3 beams (of no medical relevance, we just use them for demonstration). There are multiple parameters which can affect computation time :\n * targetMargin : a higher margin will increase the time used to dilate the mask\n * spotSpacing : a lower spot spacing will result in more beamlets therefore longer beamlets calculation time\n * layerSpacing : a lower layer spacing will result in more beamlets therefore longer beamlets calculation time\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Design plan\nbeamNames = [\"Beam1\",\"Beam2\",\"Beam3\"]\ngantryAngles = [0.,45.,315.]\ncouchAngles = [0.,0.,0.]\n\n# Generate new plan\nplanDesign = ProtonPlanDesign()\nplanDesign.ct = ct\nplanDesign.gantryAngles = gantryAngles\nplanDesign.targetMask = target\nplanDesign.beamNames = beamNames\nplanDesign.couchAngles = couchAngles\nplanDesign.calibration = ctCalibration\nplanDesign.spotSpacing = 5.0\nplanDesign.layerSpacing = 5.0\nplanDesign.targetMargin = 5.0\n\nplan = planDesign.buildPlan() # Spot placement\nplan.PlanName = \"NewPlan\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Beamlets computation and initial dose computation\nIn the next section we compute the beamlets (this is the most computer-intensive part). We have set the numbers of protons to 5e4.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "mc2.nbPrimaries = 5e4\nbeamlets = mc2.computeBeamlets(ct, plan)\nplan.planDesign.beamlets = beamlets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After the beamlets computation we can save the plan and the beamlets to reuse them in the future.\nWARNING : the saveRTPlan function automatically remove the beamlets from the memory, if you want to save the beamlets, you have to call the saveBeamlets function before. Those files can be heavy !\nAfterward you can load the plan and the beamlets via the loadRTPlan and loadBeamlets functions.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#Output path\noutput_path = 'Output'\nif not os.path.exists(output_path):\n os.makedirs(output_path)\n\n# Save the plan and the beamlets\nsaveBeamlets(beamlets, os.path.join(output_path, \"SimpleRealDoseComputationOptimization_beamlets.blm\"))\nsaveRTPlan(plan, os.path.join(output_path,\"SimpleRealDoseComputationOptimization_plan.tps\"))\nplan = loadRTPlan(os.path.join(output_path,\"SimpleRealDoseComputationOptimization_plan.tps\"))\nplan.planDesign.beamlets = loadBeamlets(os.path.join(output_path,\"SimpleRealDoseComputationOptimization_beamlets.blm\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that in the next cell we have augmented the number of protons for the dose computation (computeDose) to have a more accurate dose. This dose is computed with all the weights of the beamlets set to 1.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "mc2.nbPrimaries = 1e7\ndose_before_opti = mc2.computeDose(ct,plan)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plan optimization\nWe will now optimize the plan with and without OAR to compare the differences. We first create an ObjectivesList and then add objectives via the addFidObjective which can be either DMIN, DMAX or DMEAN. Note that you can also create other objectives and implement them via the addExoticObjective fucntion.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan.planDesign.objectives = ObjectivesList() #create a new objective set\nplan.planDesign.objectives.setTarget(target.name, 20.0) #setting a target of 20 Gy for the target\nplan.planDesign.objectives.fidObjList = []\nplan.planDesign.objectives.addFidObjective(target, FidObjective.Metrics.DMAX, 19.5, 1.0)\nplan.planDesign.objectives.addFidObjective(target, FidObjective.Metrics.DMIN, 20.5, 1.0)\nplan.planDesign.defineTargetMaskAndPrescription()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will use the Scipy-LBFGS as solver for this example but other are also implemented such as :\n- Scipy-BFGS\n- Scipy-LBFGS\n- Gradient\n- BFGS\n- LBFGS\n- FISTA\n- LP\nFeel also free to specify a maxit to the IMPTPlanOptimizer object to speed up the program.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.processing.planOptimization.planOptimization import IMPTPlanOptimizer\n\nsolver = IMPTPlanOptimizer(method='Scipy-LBFGS',plan=plan)\nw, doseImage, ps = solver.optimize()\nplan.spotMUs = np.square(w).astype(np.float32)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now recompute the dose.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "doseImage_opti = mc2.computeDose(ct,plan)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We here reload the plan in order to reset all weights and the filtering (after optimization, spots that are bellow the solver.thresholdSpotRemoval and corresponding weights are removed).\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan = loadRTPlan(os.path.join(output_path,\"SimpleRealDoseComputationOptimization_plan.tps\"))\nplan.planDesign.beamlets = loadBeamlets(os.path.join(output_path,\"SimpleRealDoseComputationOptimization_beamlets.blm\"))\nplan.planDesign.objectives = ObjectivesList() #create a new objective set\nplan.planDesign.objectives.setTarget(target.name, 20.0) #setting a target of 20 Gy for the target\nplan.planDesign.objectives.fidObjList = []\nplan.planDesign.objectives.addFidObjective(target, FidObjective.Metrics.DMAX, 20.5, 1.0)\nplan.planDesign.objectives.addFidObjective(target, FidObjective.Metrics.DMIN, 19.5, 1.0)\nplan.planDesign.objectives.addFidObjective(OAR_brain, FidObjective.Metrics.DMAX, 8.0, 1.0)\nplan.planDesign.objectives.addFidObjective(OAR_brainstem, FidObjective.Metrics.DMAX, 5.0, 1.0)\nplan.planDesign.objectives.addFidObjective(OAR_optic_chiasm, FidObjective.Metrics.DMAX, 2.0, 1.0)\nplan.planDesign.defineTargetMaskAndPrescription()\n\nsolver = IMPTPlanOptimizer(method='Scipy-LBFGS',plan=plan)\ndoseImage, ps = solver.optimize()\n\ndoseImage_opti_OAR = mc2.computeDose(ct,plan)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## DVH histograms\nWe can create simple DVH plots with the DVH objects. Take a look at the class properties to find the D95, \u2026\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "target_DVH = DVH(target,doseImage_opti_OAR)\ntarget_DVH_No_OAR = DVH(target,doseImage_opti)\nbrain_DVH = DVH(OAR_brain,doseImage_opti_OAR)\nbrainstem_DVH = DVH(OAR_brainstem,doseImage_opti_OAR)\noptic_chiasm_DVH = DVH(OAR_optic_chiasm,doseImage_opti_OAR)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Final plots\nNow that the different optimizations are done, we can display the results.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from skimage.transform import resize\nimage_ct_axial = ct.imageArray[:,:,Z_COORD].transpose(1,0)\nimage_target_axial = target.imageArray[:,:,Z_COORD].transpose(1,0)\nimage_brain_axial = OAR_brain.imageArray[:,:,Z_COORD].transpose(1,0)\nimage_brainstem_axial = OAR_brainstem.imageArray[:,:,Z_COORD].transpose(1,0)\nimage_optic_chiasm_axial = OAR_optic_chiasm.imageArray[:,:,Z_COORD].transpose(1,0)\nimage_dose_before_opti_axial = dose_before_opti.imageArray[:,:,Z_COORD].transpose(1,0)\nimage_dose_opti_axial = doseImage_opti.imageArray[:,:,Z_COORD].transpose(1,0)\nimage_dose_opti_OAR_axial = doseImage_opti_OAR.imageArray[:,:,Z_COORD].transpose(1,0)\n\nimage_ct_sagital = resize(np.rot90(ct.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1]))\nimage_target_sagital = resize(np.rot90(target.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1]))\nimage_brain_sagital = resize(np.rot90(OAR_brain.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1]))\nimage_brainstem_sagital = resize(np.rot90(OAR_brainstem.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1]))\nimage_optic_chiasm_sagital = resize(np.rot90(OAR_optic_chiasm.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1]))\nimage_dose_before_opti_sagital = resize(np.rot90(dose_before_opti.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1]))\nimage_dose_opti_sagital = resize(np.rot90(doseImage_opti.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1]))\nimage_dose_opti_OAR_sagital = resize(np.rot90(doseImage_opti_OAR.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1]))\n\nmin_dose_val = 0.1\nimage_dose_before_opti_axial[image_dose_before_opti_axial <= min_dose_val] = np.nan\nimage_dose_opti_axial[image_dose_opti_axial <= min_dose_val] = np.nan\nimage_dose_opti_OAR_axial[image_dose_opti_OAR_axial <= min_dose_val] = np.nan\nimage_dose_before_opti_sagital[image_dose_before_opti_sagital <= min_dose_val] = np.nan\nimage_dose_opti_sagital[image_dose_opti_sagital <= min_dose_val] = np.nan\nimage_dose_opti_OAR_sagital[image_dose_opti_OAR_sagital <= min_dose_val] = np.nan\n\nvmin=0\nvmax=22\n\nfig, ax = plt.subplots(3,3,figsize=(12,12))\n\nax[0,0].imshow(image_ct_axial,cmap=\"gray\")\nax[0,0].contour(image_target_axial,colors=\"blue\")\nax[0,0].contour(image_brain_axial,colors=\"red\")\nax[0,0].contour(image_brainstem_axial,colors=\"green\")\nax[0,0].contour(image_optic_chiasm_axial,colors=\"orange\")\nax[0,0].axis(\"off\")\nax[0,0].set_title(\"Base CT - Axial vue\")\n\nax[0,1].imshow(image_ct_sagital,cmap=\"gray\")\nax[0,1].contour(image_target_sagital,colors=\"blue\")\nax[0,1].contour(image_brain_sagital,colors=\"red\")\nax[0,1].contour(image_brainstem_sagital,colors=\"green\")\nax[0,1].contour(image_optic_chiasm_sagital,colors=\"orange\")\nax[0,1].axis(\"off\")\nax[0,1].set_title(\"Base CT - Sagital vue\")\n\nax[0,2].plot(target_DVH.histogram[0],target_DVH.histogram[1],label=target_DVH.name + \" with OAR\",color=\"blue\")\nax[0,2].plot(brain_DVH.histogram[0],brain_DVH.histogram[1],label=brain_DVH.name, color=\"red\")\nax[0,2].plot(brainstem_DVH.histogram[0],brainstem_DVH.histogram[1],label=brainstem_DVH.name, color=\"green\")\nax[0,2].plot(optic_chiasm_DVH.histogram[0],optic_chiasm_DVH.histogram[1],label=optic_chiasm_DVH.name, color=\"orange\")\nax[0,2].plot(target_DVH_No_OAR.histogram[0],target_DVH_No_OAR.histogram[1],label=target_DVH.name + \" wihtout OAR\",color=\"blue\",linestyle=\"dashed\")\nax[0,2].set_xlim(0,25)\nax[0,2].grid(True)\nax[0,2].legend()\nax[0,2].set_title(\"DVH after optimization\")\n\nax[1,0].imshow(image_ct_axial,cmap=\"gray\")\ndose_bar_ref = ax[1,0].imshow(image_dose_before_opti_axial,cmap=\"jet\",alpha=.5,vmin=vmin,vmax=vmax)\nax[1,0].contour(image_target_axial,colors=\"black\")\nax[1,0].set_title(\"Dose before optimization\")\nax[1,0].axis(\"off\")\n\nax[1,1].imshow(image_ct_axial,cmap=\"gray\")\nax[1,1].imshow(image_dose_opti_axial,cmap=\"jet\",alpha=.5,vmin=vmin,vmax=vmax)\nax[1,1].contour(image_target_axial,colors=\"black\")\nax[1,1].set_title(\"Dose after optimization without OAR\")\nax[1,1].axis(\"off\")\n\nax[1,2].imshow(image_ct_axial,cmap=\"gray\")\nax[1,2].imshow(image_dose_opti_OAR_axial,cmap=\"jet\",alpha=.5,vmin=vmin,vmax=vmax)\nax[1,2].contour(image_target_axial,colors=\"black\")\nax[1,2].set_title(\"Dose after optimization with OAR\")\nax[1,2].axis(\"off\")\n\nax[2,0].imshow(image_ct_sagital,cmap=\"gray\")\nax[2,0].imshow(image_dose_before_opti_sagital,cmap=\"jet\",alpha=.5,vmin=vmin,vmax=vmax)\nax[2,0].contour(image_target_sagital,colors=\"black\")\nax[2,0].set_title(\"Dose before optimization\")\nax[2,0].axis(\"off\")\n\nax[2,1].imshow(image_ct_sagital,cmap=\"gray\")\nax[2,1].imshow(image_dose_opti_sagital,cmap=\"jet\",alpha=.5,vmin=vmin,vmax=vmax)\nax[2,1].contour(image_target_sagital,colors=\"black\")\nax[2,1].set_title(\"Dose after optimization without OAR\")\nax[2,1].axis(\"off\")\n\nax[2,2].imshow(image_ct_sagital,cmap=\"gray\")\nax[2,2].imshow(image_dose_opti_OAR_sagital,cmap=\"jet\",alpha=.5,vmin=vmin,vmax=vmax)\nax[2,2].contour(image_target_sagital,colors=\"black\")\nax[2,2].set_title(\"Dose after optimization with OAR\")\nax[2,2].axis(\"off\")\ncb_ax = fig.add_axes([0.1, 0.08, 0.8, 0.02])\nfig.colorbar(dose_bar_ref,cax=cb_ax,location=\"bottom\")\nplt.savefig(os.path.join(output_path, \"SimpleRealDoseComputationOptimization_output.png\"))\nplt.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_community/Template/run_template.ipynb b/auto_community/Template/run_template.ipynb new file mode 100644 index 0000000..5e47a29 --- /dev/null +++ b/auto_community/Template/run_template.ipynb @@ -0,0 +1,104 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Template Example\n\nThis is a minimal example showing how to contribute to the\nCommunity Gallery. It just generates some random data and plots it.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 1. Generate random data\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "x = np.linspace(0, 10, 100)\ny = np.sin(x) + 0.2 * np.random.randn(100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 2. Plot the results\nTo display your plot in the Sphinx gallery, make sure plt.show() is the last line of your code cell.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plt.figure()\nplt.plot(x, y, label=\"noisy sine\")\nplt.legend()\nplt.title(\"Random sine example\")\nplt.xlabel(\"x\")\nplt.ylabel(\"y\")\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/DoseComputation/run_SimpleDoseCalculation.ipynb b/auto_examples/DoseComputation/run_SimpleDoseCalculation.ipynb new file mode 100644 index 0000000..033e84c --- /dev/null +++ b/auto_examples/DoseComputation/run_SimpleDoseCalculation.ipynb @@ -0,0 +1,227 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Simple dose computation\nauthor: OpenTPS team\n\nIn this example we are going to create a generic CT and use the MCsquare dose calculator to compute the dose image\n\nrunning time: ~ 7 minute\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport os\nfrom matplotlib import pyplot as plt\nimport math" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images import CTImage\nfrom opentps.core.data.images import ROIMask\nfrom opentps.core.data.plan import ProtonPlanDesign\nfrom opentps.core.data import DVH\nfrom opentps.core.data import Patient\nfrom opentps.core.io import mcsquareIO\nfrom opentps.core.io.scannerReader import readScanner\nfrom opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig\nfrom opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator\nfrom opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D,resampleImage3D" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generic CT creation\nwe will first create a generic CT of a box fill with water and air\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ctCalibration = readScanner(DoseCalculationConfig().scannerFolder)\nbdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile)\n\npatient = Patient()\npatient.name = 'Patient'\n\nctSize = 150\n\nct = CTImage()\nct.name = 'CT'\nct.patient = patient\n\nhuAir = -1024.\nhuWater = ctCalibration.convertRSP2HU(1.)\ndata = huAir * np.ones((ctSize, ctSize, ctSize))\ndata[:, 50:, :] = huWater\nct.imageArray = data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Region of interest\nwe will now create a region of interest wich is a small 3D box of size 20*20*20\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "roi = ROIMask()\nroi.patient = patient\nroi.name = 'TV'\nroi.color = (255, 0, 0) # red\ndata = np.zeros((ctSize, ctSize, ctSize)).astype(bool)\ndata[65:85, 65:85, 65:85] = True\nroi.imageArray = data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configuration of MCsquare\nto configure Mcsquare we need to calibrate it with the CT calibration obtained above\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "mc2 = MCsquareDoseCalculator()\nmc2.beamModel = bdl\nmc2.ctCalibration = ctCalibration\nmc2.nbPrimaries = 1e7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plan Creation\nwe will now create a plan and create one beam\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Design plan\nbeamNames = [\"Beam1\",\"Beam2\",\"Beam3\"]\ngantryAngles = [0.,90.,270.]\ncouchAngles = [0.,0.,0.]\n\n# Generate new plan\nplanDesign = ProtonPlanDesign()\nplanDesign.ct = ct\nplanDesign.targetMask = roi\nplanDesign.gantryAngles = gantryAngles\nplanDesign.beamNames = beamNames\nplanDesign.couchAngles = couchAngles\nplanDesign.calibration = ctCalibration\nplanDesign.spotSpacing = 5.0\nplanDesign.layerSpacing = 5.0\nplanDesign.targetMargin = 5.0\n\nplan = planDesign.buildPlan() # Spot placement\nplan.PlanName = \"NewPlan\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Center of mass\nHere we look at the part of the 3D CT image where \"stuff is happening\" by getting the CoM. We use the function resampleImage3DOnImage3D to the same array size for both images.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "roi = resampleImage3DOnImage3D(roi, ct)\nCOM_coord = roi.centerOfMass\nCOM_index = roi.getVoxelIndexFromPosition(COM_coord)\nZ_coord = COM_index[2]\n\nimg_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0)\ncontourTargetMask = roi.getBinaryContourMask()\nimg_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0)\n\n#Output path \noutput_path = 'Output'\nif not os.path.exists(output_path):\n os.makedirs(output_path)\n\nimage = plt.imshow(img_ct,cmap='Blues')\nplt.colorbar(image)\nplt.contour(img_mask,colors=\"red\")\nplt.title(\"Created CT with ROI\")\nplt.text(5,40,\"Air\",color= 'black')\nplt.text(5,100,\"Water\",color = 'white')\nplt.text(71,77,\"TV\",color ='red')\nplt.savefig(os.path.join(output_path, 'SimpleCT.png'),format = 'png')\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dose Computation\nWe now use the MCsquare dose calculator to compute the dose of the created plan\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "doseImage = mc2.computeDose(ct, plan)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "img_dose = resampleImage3DOnImage3D(doseImage, ct)\nimg_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0)\nscoringSpacing = [2, 2, 2]\nscoringGridSize = [int(math.floor(i / j * k)) for i, j, k in zip([150,150,150], scoringSpacing, [1,1,1])]\nroiResampled = resampleImage3D(roi, origin=ct.origin, gridSize=scoringGridSize, spacing=scoringSpacing)\ntarget_DVH = DVH(roiResampled, doseImage)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(1, 2, figsize=(12, 5))\nax[0].imshow(img_ct, cmap='gray')\nax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV\ndose = ax[0].imshow(img_dose, cmap='jet', alpha=.2)\ncbar = plt.colorbar(dose, ax=ax[0])\ncbar.set_label('Dose(Gy)')\nax[1].plot(target_DVH.histogram[0], target_DVH.histogram[1], label=target_DVH.name)\nax[1].set_xlabel(\"Dose (Gy)\")\nax[1].set_ylabel(\"Volume (%)\")\nplt.grid(True)\nplt.legend()\nplt.savefig(os.path.join(output_path, 'SimpleDose.png'), format = 'png')\nplt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print('D95 = ' + str(target_DVH.D95) + ' Gy')\nprint('D5 = ' + str(target_DVH.D5) + ' Gy')\nprint('D5 - D95 = {} Gy'.format(target_DVH.D5 - target_DVH.D95))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/DoseComputation/run_UseOfRangeShifterForProtonPlan.ipynb b/auto_examples/DoseComputation/run_UseOfRangeShifterForProtonPlan.ipynb new file mode 100644 index 0000000..06ed26f --- /dev/null +++ b/auto_examples/DoseComputation/run_UseOfRangeShifterForProtonPlan.ipynb @@ -0,0 +1,374 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Use of Range Shifter for Proton Plan\nauthor: OpenTPS team\n\nIn this example, we will show how to create a plan from scratch and use range shifters.\n\nrunning time: ~ 10 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import os\nimport logging\nimport sys\n\nfrom matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D\nsys.path.append('..')\nimport numpy as np\nfrom pathlib import Path\nfrom opentps.core.data.plan._planProtonBeam import PlanProtonBeam\nfrom opentps.core.data.plan._planProtonLayer import PlanProtonLayer\nfrom opentps.core.data.plan._protonPlan import ProtonPlan\nfrom opentps.core.data.plan._rtPlan import RTPlan\nfrom opentps.core.io.scannerReader import readScanner\nfrom opentps.core.io.serializedObjectIO import loadRTPlan, saveRTPlan\nfrom opentps.core.io import mcsquareIO\nfrom opentps.core.data._dvh import DVH\nfrom opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig\nfrom opentps.core.io.dataLoader import readData\nfrom opentps.core.io.mhdIO import exportImageMHD\nfrom opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator\nfrom opentps.core.data.images import CTImage, DoseImage\nfrom opentps.core.data.images import ROIMask\nfrom opentps.core.io.dicomIO import writeDicomCT, writeRTPlan, writeRTDose, readDicomDose, writeRTStruct\nfrom opentps.core.io.mcsquareIO import RangeShifter\nfrom opentps.core.data.CTCalibrations.MCsquareCalibration._mcsquareMolecule import MCsquareMolecule\nfrom opentps.core.data._rtStruct import RTStruct\nfrom opentps.core.data import Patient\n\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Exemple_RangeShifter')\nif not os.path.exists(output_path):\n os.makedirs(output_path) \n print(f\"Directory '{output_path}' created.\")\nelse:\n print(f\"Directory '{output_path}' already exists.\")\n \nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Choosing default scanner\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "doseCalculator = MCsquareDoseCalculator()\ndoseCalculator.ctCalibration = readScanner(DoseCalculationConfig().scannerFolder)\n# Or a specific one if you do have\n# MCSquarePath = os.path.join(openTPS_path, 'core', 'processing', 'doseCalculation', 'MCsquare')\n# scannerPath = os.path.join(MCSquarePath, 'Scanners', 'UCL_Toshiba')\n# doseCalculator.ctCalibration = MCsquareCTCalibration(fromFiles=(os.path.join(scannerPath, 'HU_Density_Conversion.txt'),\n# os.path.join(scannerPath, 'HU_Material_Conversion.txt'),\n# os.path.join(MCSquarePath, 'Materials')))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Path of your BDL if you do have one\nOtherwise the default bdl 'BDL_default_DN_RangeShifter' is used\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "bdl_path = \"Path/to/your/BDL\"\nif 'bdl_path' in locals() and os.path.isfile(bdl_path):\n DoseCalculationConfig().bdlFile = bdl_path\n\n# chossing default BDL\ndoseCalculator.beamModel = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get different range shifters\n From bdl file : Be sure the material you use is in core/processing/doseCalculation/protons/MCsquare/Materials and you have the good MCsquare material ID in the bdl\n If you want to add a new material, you can add the folder with the necessary material properties in MCsquare/Materials\n The MCsquare material ID (RS_material) of the new material to add in the bdl will be print in the terminal and is in MCsquare/Materials/list.dat\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "rs_1 = doseCalculator.beamModel.rangeShifters[0]\n# From scratch : \nrs_2 = RangeShifter(material='Lexan', density=1.217, WET=40.3)\nrs_2.ID = 'RS_Lexan_66'\nrs_2.type = 'binary'\nprint('Range shifter 1:', rs_1)\nprint('Range shifter 2:', rs_2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure dose calculation\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "doseCalculator.nbPrimaries = 1e7 # number of primary particles, 1e4 is enough for a quick test, otherwise 1e7 is recommended (It can take several minutes to compute).\n\npatient = Patient()\npatient.name = 'TestPatient'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define CT and Target\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ctSize = 200\nct = CTImage()\nct.name = 'TestPhantom'\nct.patient = patient\n\ntarget = ROIMask()\ntarget.name = 'TV'\ntarget.spacing = ct.spacing\ntarget.color = (255, 0, 0) # red\ntargetArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool)\nradius = 20\nx0, y0, z0 = (100, 100, 100)\nx, y, z = np.mgrid[0:ctSize:1, 0:ctSize:1, 0:ctSize:1]\nr = np.sqrt((x - x0) ** 2 + (y - y0) ** 2 + (z - z0) ** 2)\ntargetArray[r < radius] = True\ntarget.imageArray = targetArray\n\nhuAir = -1024.\nhuWater = doseCalculator.ctCalibration.convertRSP2HU(1.)\nctArray = huAir * np.ones((ctSize, ctSize, ctSize))\nctArray[1:ctSize - 1, 1:ctSize - 1, 1:ctSize - 1] = huWater\nctArray[targetArray >= 0.5] = 50\nct.imageArray = ctArray\n\nbody = ROIMask()\nbody.name = 'Body'\nbody.spacing = ct.spacing\nbody.color = (0, 0, 255)\nbodyArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool)\nbodyArray[1:ctSize - 1, 1:ctSize - 1, 1:ctSize - 1] = True\nbody.imageArray = bodyArray" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create plan from scratch\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan = ProtonPlan()\nplan.appendBeam(PlanProtonBeam())\nplan.appendBeam(PlanProtonBeam())\nplan.appendBeam(PlanProtonBeam())\nplan.beams[0].gantryAngle = 0.\nplan.beams[1].gantryAngle = 0.\nplan.beams[2].gantryAngle = 90.\nplan.beams[0].appendLayer(PlanProtonLayer(120)) # Nominal energy of the layer \nplan.beams[1].appendLayer(PlanProtonLayer(120))\nplan.beams[2].appendLayer(PlanProtonLayer(120))\nplan[0].layers[0].appendSpot([50, 60], [100, 100], [300, 300]) # Two spots to the target from beam 0 (0. gantryAngle)\nplan[1].layers[0].appendSpot([90, 100], [100, 100], [300, 300]) # Two spots to the target from beam 1 (0. gantryAngle)\nplan[2].layers[0].appendSpot([100, 110], [100, 110], [300, 300]) # Two spots placed outside the target from beam 2 (90. gantryAngle)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use 2 different range shifters for the two beams\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan.rangeShifter = [rs_1, rs_2]\nplan.beams[0].rangeShifter = [rs_1]\nplan.beams[1].rangeShifter = None\nplan.beams[2].rangeShifter = [rs_2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Range Shifter beam 0 parameters\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan[0].layers[0].rangeShifterSettings.isocenterToRangeShifterDistance = 0 # [mm]\nplan[0].layers[0].rangeShifterSettings.rangeShifterSetting = 'IN'\nplan[0].layers[0].rangeShifterSettings.rangeShifterWaterEquivalentThickness = None # [mm] None means get thickness from BDL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Range Shifter beam 2 parameters\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan[2].layers[0].rangeShifterSettings.isocenterToRangeShifterDistance = 200 # [mm]\nplan[2].layers[0].rangeShifterSettings.rangeShifterSetting = 'IN'\nplan[2].layers[0].rangeShifterSettings.rangeShifterWaterEquivalentThickness = 15 # [mm] None means get thickness from BDL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Save plan in OpenTPS format (serialized)\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "saveRTPlan(plan, os.path.join(output_path, 'dummy_plan.tps'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load plan in OpenTPS format (serialized)\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan2 = loadRTPlan(os.path.join(output_path, 'dummy_plan.tps'))\nprint(plan2[0].layers[0].spotWeights)\nprint(plan[0].layers[0].spotWeights) # plan2 is the same as plan" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Save plan in Dicom format\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dicomPath = os.path.join(output_path)\nwriteRTPlan(plan, dicomPath)\nif not os.path.exists(os.path.join(dicomPath, 'CT')):\n os.mkdir(os.path.join(dicomPath, 'CT'))\nwriteDicomCT(ct, os.path.join(dicomPath, 'CT'))\nprint('Dicom files saved in', dicomPath)\n\n# For contour, they must be RTStruct\ncontour = target.getROIContour()\nstruct = RTStruct()\nstruct.appendContour(contour)\nwriteRTStruct(struct, os.path.join(output_path, 'CT'))\n\n# load plan in Dicom format\ndataList = readData(dicomPath, maxDepth=2)\nctDicom = [d for d in dataList if isinstance(d, CTImage)][0]\nplanDicom = [d for d in dataList if isinstance(d, RTPlan)][0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generic example: box of water with spherical target\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Load CT & contours\n# ct = [d for d in dataList if isinstance(d, CTImage)][0]\n# struct = [d for d in dataList if isinstance(d, RTStruct)][0]\n# target = struct.getContourByName('TV')\n# body = struct.getContourByName('Body')\n\n# Compute the dose\ndoseImage = doseCalculator.computeDose(ct, plan) # You can choose the plan you want to use, results will be the same\n# doseImage = importImageMHD(output_path) # If you want to import a dose image from a MHD file\n# doseImageDicom = [d for d in dataList if isinstance(d, DoseImage)][0] # If you want to import a dose image from a Dicom file\n\n# Export dose (MHD)\n# exportImageMHD(os.path.join(output_path,'DoseImage'), doseImage)\n\n# Export dose (Dicom)\nwriteRTDose(doseImage, dicomPath)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot dose\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "target = resampleImage3DOnImage3D(target, ct)\nCOM_coord = target.centerOfMass\nCOM_index = target.getVoxelIndexFromPosition(COM_coord)\nZ_coord = COM_index[2]\n\nimg_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0)\ncontourTargetMask = target.getBinaryContourMask()\nimg_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0)\nimg_dose = resampleImage3DOnImage3D(doseImage, ct)\nimg_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Display dose\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plt.imshow(img_ct, cmap='gray')\nplt.contour(img_mask, colors='red') # PTV\ndose = plt.imshow(img_dose, cmap='jet', alpha=.6)\ncolorbar = plt.colorbar(dose)\ncolorbar.set_label('Dose [Gy]', fontsize=12)\nplt.show()\nplt.savefig(os.path.join(output_path, 'Dose_protonWithRangeShifters.png'))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/DoseComputation/run_photonPlanCreationAndDoseCalculation.ipynb b/auto_examples/DoseComputation/run_photonPlanCreationAndDoseCalculation.ipynb new file mode 100644 index 0000000..e824fce --- /dev/null +++ b/auto_examples/DoseComputation/run_photonPlanCreationAndDoseCalculation.ipynb @@ -0,0 +1,216 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Photon Plan Creation and Dose Calculation\nauthor: OpenTPS team\n\nThis example demonstrates how to create a photon plan and perform dose calculation using OpenTPS.\n\nrunning time: ~ 10 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import os\nfrom opentps.core.data.images._ctImage import CTImage\nfrom opentps.core.io.scannerReader import readScanner\nfrom opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig\nfrom opentps.core.processing.doseCalculation.photons.cccDoseCalculator import CCCDoseCalculator\nfrom opentps.core.io.sitkIO import exportImageSitk\nimport numpy as np\nfrom opentps.core.data.images import ROIMask\nimport logging\nfrom opentps.core.data.plan._photonPlan import PhotonPlan\nfrom opentps.core.data.plan._planPhotonBeam import PlanPhotonBeam\nfrom opentps.core.data.plan._planPhotonSegment import PlanPhotonSegment\nfrom opentps.core.io.serializedObjectIO import loadRTPlan, saveRTPlan\nfrom pathlib import Path\nfrom opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D\nfrom opentps.core.data._dvh import DVH\nimport matplotlib.pyplot as plt\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def getMLCCoordinates(Ymin, Ymax, step):\n first_column = np.arange(Ymin, Ymax, step)\n second_column = np.arange(Ymin + step, Ymax + step, step)\n Xl = np.zeros(len(first_column))\n Xr = np.zeros(len(first_column))\n # Xl[15:25] = np.random.rand(10) * -5\n # Xr[15:25] = np.random.rand(10) * 5\n Xl[15:25] = np.ones(10) * -5\n Xr[15:25] = np.ones(10) * 5\n return np.column_stack((first_column, second_column, Xl, Xr))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def initializeSegment(beamSegment,Ymin, Ymax, step):\n beamSegment.Xmlc_mm = getMLCCoordinates(Ymin, Ymax, step)\n beamSegment.x_jaw_mm = [-50, 50]\n beamSegment.y_jaw_mm = [-200, 200]\n beamSegment.mu = 5000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output_Example','PhotonDoseCalculation')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\n\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create plan from scratch\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan = PhotonPlan()\nplan.appendBeam(PlanPhotonBeam())\nplan.appendBeam(PlanPhotonBeam())\nplan.beams[0].appendBeamSegment(PlanPhotonSegment())\nplan.beams[1].appendBeamSegment(PlanPhotonSegment())\n\nYmax = 200\nYmin = -200\nstep = 10\ninitializeSegment(plan.beams[0].beamSegments[0], Ymin, Ymax, step)\ninitializeSegment(plan.beams[1].beamSegments[0], Ymin, Ymax, step)\nplan.beams[1].beamSegments[0].gantryAngle_degree = 90.\n\n# Save plan\nsaveRTPlan(plan,os.path.join(output_path,'dummy_plan.tps'))\n\n# Load plan\nplan2 = loadRTPlan(os.path.join(output_path,'dummy_plan.tps'))\nprint(plan2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dose computation from plan\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ccc = CCCDoseCalculator(batchSize= 30)\nccc.ctCalibration = readScanner(DoseCalculationConfig().scannerFolder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create CT and contours\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ctSize = 20\nct = CTImage()\nct.name = 'CT'\nct.origin = -ctSize/2 * ct.spacing\n\ntarget = ROIMask()\ntarget.name = 'TV'\ntarget.origin = -ctSize/2 * ct.spacing\ntarget.spacing = ct.spacing\ntarget.color = (255, 0, 0) # red\ntargetArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool)\nradius = 2.5\nx0, y0, z0 = (10, 10, 10)\nx, y, z = np.mgrid[0:ctSize:1, 0:ctSize:1, 0:ctSize:1]\nr = np.sqrt((x - x0) ** 2 + (y - y0) ** 2 + (z - z0) ** 2)\ntargetArray[r < radius] = True\ntarget.imageArray = targetArray\n\nctArray = np.zeros((ctSize, ctSize, ctSize))\nctArray[1:ctSize - 1, 1:ctSize - 1, 1:ctSize - 1] = 1\nctArray[targetArray>=0.5] = 10\nct.imageArray = ctArray\n\nbody = ROIMask()\nbody.name = 'Body'\nbody.spacing = ct.spacing\nbody.origin = -ctSize/2 * ct.spacing\nbody.color = (0, 0, 255)\nbodyArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool)\nbodyArray[1:ctSize- 1, 1:ctSize - 1, 1:ctSize - 1] = True\nbody.imageArray = bodyArray\n\ndoseImage = ccc.computeDose(ct, plan)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## DVH\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dvh = DVH(target, doseImage)\nprint(\"D95\",dvh._D95)\nprint(\"D5\",dvh._D5)\nprint(\"Dmax\",dvh._Dmax)\nprint(\"Dmin\",dvh._Dmin)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot dose\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "target = resampleImage3DOnImage3D(target, ct)\nCOM_coord = target.centerOfMass\nCOM_index = target.getVoxelIndexFromPosition(COM_coord)\nZ_coord = COM_index[2]\n\nimg_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0)\ncontourTargetMask = target.getBinaryContourMask()\nimg_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0)\nimg_dose = resampleImage3DOnImage3D(doseImage, ct)\nimg_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Display dose\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(1, 2, figsize=(10, 5))\nax[0].axes.get_xaxis().set_visible(False)\nax[0].axes.get_yaxis().set_visible(False)\nax[0].imshow(img_ct, cmap='gray')\nax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV\ndose = ax[0].imshow(img_dose, cmap='jet', alpha=.2)\nplt.colorbar(dose, ax=ax[0])\nax[1].plot(dvh.histogram[0], dvh.histogram[1], label=dvh.name)\nax[1].set_xlabel(\"Dose (Gy)\")\nax[1].set_ylabel(\"Volume (%)\")\nax[1].grid(True)\nax[1].legend()\nplt.savefig(os.path.join(output_path, 'dose.png')) \nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/DoseComputation/run_protonPlanCreationAndDoseCalculation.ipynb b/auto_examples/DoseComputation/run_protonPlanCreationAndDoseCalculation.ipynb new file mode 100644 index 0000000..fa66b16 --- /dev/null +++ b/auto_examples/DoseComputation/run_protonPlanCreationAndDoseCalculation.ipynb @@ -0,0 +1,241 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Proton Plan Creation and Dose Calculation\nauthor: OpenTPS team\n\nThis example demonstrates how to create a proton plan and perform dose calculation using OpenTPS.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import os\nimport logging\nimport sys" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n\nfrom opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D\nfrom opentps.core.processing.planOptimization.tools import evaluateClinical\nsys.path.append('..')\nimport numpy as np\n\nfrom opentps.core.data.plan import ProtonPlan, RTPlan\nfrom opentps.core.io.scannerReader import readScanner\nfrom opentps.core.io.serializedObjectIO import loadRTPlan, saveRTPlan\nfrom opentps.core.io.dicomIO import readDicomDose, readDicomPlan\nfrom opentps.core.io.dataLoader import readData\nfrom opentps.core.data.CTCalibrations.MCsquareCalibration._mcsquareCTCalibration import MCsquareCTCalibration\nfrom opentps.core.io import mcsquareIO\nfrom opentps.core.data._dvh import DVH\nfrom opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig\nfrom opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator\nfrom opentps.core.io.mhdIO import exportImageMHD\nfrom opentps.core.data.plan import PlanProtonBeam\nfrom opentps.core.data.plan import PlanProtonLayer\nfrom opentps.core.data.images import CTImage, DoseImage\nfrom opentps.core.data import RTStruct\nfrom opentps.core.data import Patient\nfrom opentps.core.data.images import ROIMask\nfrom pathlib import Path\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.getcwd()\n\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create plan from scratch and save it\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan = ProtonPlan()\nplan.appendBeam(PlanProtonBeam())\nplan.appendBeam(PlanProtonBeam())\nplan.beams[1].gantryAngle = 120.\nplan.beams[0].appendLayer(PlanProtonLayer(100))\nplan.beams[0].appendLayer(PlanProtonLayer(90))\nplan.beams[1].appendLayer(PlanProtonLayer(80))\nplan[0].layers[0].appendSpot([-1,0,1], [1,2,3], [0.1,0.2,0.3])\nplan[0].layers[1].appendSpot([0,1], [2,3], [0.2,0.3])\nplan[1].layers[0].appendSpot(1, 1, 0.5)\n\n# Save plan\nsaveRTPlan(plan,os.path.join(output_path,'dummy_plan.tps'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load plan in OpenTPS format (serialized)\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan2 = loadRTPlan(os.path.join(output_path,'dummy_plan.tps'))\nprint(plan2[0].layers[1].spotWeights)\nprint(plan[0].layers[1].spotWeights)\n\n# Load DICOM plan\n#dicomPath = os.path.join(Path(os.getcwd()).parent.absolute(),'opentps','testData','Phantom')\n#print(dicomPath)\n#dataList = readData(dicomPath, maxDepth=1)\n#plan3 = [d for d in dataList if isinstance(d, RTPlan)][0]\n# or provide path to RTPlan and read it\n# plan_path = os.path.join(Path(os.getcwd()).parent.absolute(),'opentps/testData/Phantom/Plan_SmallWaterPhantom_cropped_resampled_optimized.dcm')\n# plan3 = readDicomPlan(plan_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dose computation from plan\n Choosing default Scanner and BDL\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "doseCalculator = MCsquareDoseCalculator()\ndoseCalculator.ctCalibration = readScanner(DoseCalculationConfig().scannerFolder)\ndoseCalculator.beamModel = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile)\ndoseCalculator.nbPrimaries = 1e7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generic example: box of water with spherical target\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Load CT & contours\n# ct = [d for d in dataList if isinstance(d, CTImage)][0]\n# struct = [d for d in dataList if isinstance(d, RTStruct)][0]\n# target = struct.getContourByName('TV')\n# body = struct.getContourByName('Body') \n\n# or create CT and contours\nctSize = 20\nct = CTImage()\nct.name = 'CT'\n\ntarget = ROIMask()\ntarget.name = 'TV'\ntarget.spacing = ct.spacing\ntarget.color = (255, 0, 0) # red\ntargetArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool)\nradius = 2.5\nx0, y0, z0 = (10, 10, 10)\nx, y, z = np.mgrid[0:ctSize:1, 0:ctSize:1, 0:ctSize:1]\nr = np.sqrt((x - x0) ** 2 + (y - y0) ** 2 + (z - z0) ** 2)\ntargetArray[r < radius] = True\ntarget.imageArray = targetArray\n\nhuAir = -1024.\nhuWater = doseCalculator.ctCalibration.convertRSP2HU(1.)\nctArray = huAir * np.ones((ctSize, ctSize, ctSize))\nctArray[1:ctSize - 1, 1:ctSize - 1, 1:ctSize - 1] = huWater\nctArray[targetArray>=0.5] = 50\nct.imageArray = ctArray\n\nbody = ROIMask()\nbody.name = 'Body'\nbody.spacing = ct.spacing\nbody.color = (0, 0, 255)\nbodyArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool)\nbodyArray[1:ctSize- 1, 1:ctSize - 1, 1:ctSize - 1] = True\nbody.imageArray = bodyArray" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# MCsquare simulation\ndoseImage = doseCalculator.computeDose(ct, plan)\n# or Load dicom dose\n#doseImage = [d for d in dataList if isinstance(d, DoseImage)][0]\n# or\n#dcm_dose_file = os.path.join(output_path, \"Dose_SmallWaterPhantom_resampled_optimized.dcm\")\n#doseImage = readDicomDose(dcm_dose_file)\n\n# Export dose\n#output_path = os.getcwd()\n#exportImageMHD(output_path, doseImage)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## DVH\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dvh = DVH(target, doseImage)\nprint(\"D95\",dvh._D95)\nprint(\"D5\",dvh._D5)\nprint(\"Dmax\",dvh._Dmax)\nprint(\"Dmin\",dvh._Dmin)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot dose\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "target = resampleImage3DOnImage3D(target, ct)\nCOM_coord = target.centerOfMass\nCOM_index = target.getVoxelIndexFromPosition(COM_coord)\nZ_coord = COM_index[2]\n\nimg_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0)\ncontourTargetMask = target.getBinaryContourMask()\nimg_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0)\nimg_dose = resampleImage3DOnImage3D(doseImage, ct)\nimg_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Display dose\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(1, 2, figsize=(10, 5))\nax[0].axes.get_xaxis().set_visible(False)\nax[0].axes.get_yaxis().set_visible(False)\nax[0].imshow(img_ct, cmap='gray')\nax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV\ndose = ax[0].imshow(img_dose, cmap='jet', alpha=.2)\nplt.colorbar(dose, ax=ax[0])\nax[1].plot(dvh.histogram[0], dvh.histogram[1], label=dvh.name)\nax[1].set_xlabel(\"Dose (Gy)\")\nax[1].set_ylabel(\"Volume (%)\")\nax[1].grid(True)\nax[1].legend()\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/DoseDeliverySimulation/PlanDeliverySimulation.ipynb b/auto_examples/DoseDeliverySimulation/PlanDeliverySimulation.ipynb new file mode 100644 index 0000000..d136d87 --- /dev/null +++ b/auto_examples/DoseDeliverySimulation/PlanDeliverySimulation.ipynb @@ -0,0 +1,223 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Plan Delivery Simulation\nauthor: OpenTPS team\n\nThis example will present the basis of plan delivery simulation with openTPS core.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.io.dataLoader import readData\nfrom opentps.core.io.dicomIO import readDicomPlan, readDicomStruct\nfrom opentps.core.data.images._ctImage import CTImage\nfrom opentps.core.data.images._deformation3D import Deformation3D\nfrom opentps.core.data._rtStruct import RTStruct\nfrom opentps.core.processing.planDeliverySimulation.planDeliverySimulation import *\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'planDeliverySimulation')\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simulation on 4DCT\n Load plan\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan_path = \"./testData/RP1.2.840.10008.5.1.4.1.1.481.8.dcm\" # Generate a plan and save it with writeRTPlan from dicomIO\nplan = readDicomPlan(plan_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load 4DCT\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dataPath = \"path/to/4DCT_folder\" # Use a 4DCT with structures availables (are going to be use below)\ndataList = readData(dataPath, 1)\nCT4D = [data for data in dataList if type(data) is CTImage]\nCT4D = Dynamic3DSequence(CT4D)\n\n# If already have a 3D model, load it and pass it to PlanDeliverySimulation:\n# model3D = pickle.load('path/to_model3D')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create plan delivery object\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "PDS = PlanDeliverySimulation(plan, CT4D)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# 4D Dose simulation\nPDS.simulate4DDose()\n\n# 4D dynamic simulation\nPDS.simulate4DDynamicDose()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simulate fractionation scenarios\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "number_of_fractions=5 # number of fractions of the plan\nnumber_of_starting_phases=3 # number of simulations (from a different starting phase)\nnumber_of_fractionation_scenarios=7 # how many scenarios we select where each scenario is a random combination with replacement\nPDS.simulate4DDynamicDoseScenarios(number_of_fractions=number_of_fractions, number_of_starting_phases=number_of_starting_phases, number_of_fractionation_scenarios=number_of_fractionation_scenarios)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plot DVH with bands for a single fraction\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "midP_struct_path = 'path/to/dicom_struct'\nmidP_struct = readDicomStruct(midP_struct_path)\ndvh_bands = PDS.computeDVHBand4DDD(midP_struct.contours, singleFraction=True)\n\n# Display DVH + DVH-bands\nfig, ax = plt.subplots(1, 1, figsize=(5, 5))\nfor dvh_band in dvh_bands:\n phigh = ax.plot(dvh_band._dose, dvh_band._volumeHigh, alpha=0)\n plow = ax.plot(dvh_band._dose, dvh_band._volumeLow, alpha=0)\n pNominal = ax.plot(dvh_band._nominalDVH._dose, dvh_band._nominalDVH._volume, label=dvh_band._roiName)\n pfill = ax.fill_between(dvh_band._dose, dvh_band._volumeHigh, dvh_band._volumeLow, alpha=0.2)\nax.set_xlabel(\"Dose (Gy)\")\nax.set_ylabel(\"Volume (%)\")\nplt.grid(True)\nplt.legend()\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plot DVH with band for the accumulation of 5 fractions\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dvh_bands = PDS.computeDVHBand4DDD(midP_struct.contours, singleFraction=False)\n\n# Display DVH + DVH-bands\nfig, ax = plt.subplots(1, 1, figsize=(5, 5))\nfor dvh_band in dvh_bands:\n phigh = ax.plot(dvh_band._dose, dvh_band._volumeHigh, alpha=0)\n plow = ax.plot(dvh_band._dose, dvh_band._volumeLow, alpha=0)\n pNominal = ax.plot(dvh_band._nominalDVH._dose, dvh_band._nominalDVH._volume, label=dvh_band._roiName)\n pfill = ax.fill_between(dvh_band._dose, dvh_band._volumeHigh, dvh_band._volumeLow, alpha=0.2)\nax.set_xlabel(\"Dose (Gy)\")\nax.set_ylabel(\"Volume (%)\")\nplt.grid(True)\nplt.legend()\nplt.show()\nplt.savefig(os.path.join(output_path, 'Dose_4DCT.png'))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/DoseDeliverySimulation/run_PBSDeliveryTimings.ipynb b/auto_examples/DoseDeliverySimulation/run_PBSDeliveryTimings.ipynb new file mode 100644 index 0000000..5219fd5 --- /dev/null +++ b/auto_examples/DoseDeliverySimulation/run_PBSDeliveryTimings.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# PBS Delivery Timings \nauthor: OpenTPS team\n\nThis example will present the basis of PBS delivery timings with openTPS core.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nnp.random.seed(42)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.plan._planProtonBeam import PlanProtonBeam\nfrom opentps.core.data.plan._planProtonLayer import PlanProtonLayer\nfrom opentps.core.data.plan._protonPlan import ProtonPlan\nfrom opentps.core.processing.planDeliverySimulation.scanAlgoBeamDeliveryTimings import ScanAlgoBeamDeliveryTimings\nfrom opentps.core.processing.planDeliverySimulation.simpleBeamDeliveryTimings import SimpleBeamDeliveryTimings\nfrom opentps.core.io.dicomIO import readDicomPlan" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create random plan\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plan = ProtonPlan()\nplan.appendBeam(PlanProtonBeam())\nenergies = np.array([130, 140, 150, 160, 170])\nfor m in energies:\n layer = PlanProtonLayer(m)\n x = 10*np.random.random(5) - 5\n y = 10*np.random.random(5) - 5\n mu = 5*np.random.random(5)\n\n layer.appendSpot(x, y, mu)\n plan.beams[0].appendLayer(layer)\n\n\nbdt = SimpleBeamDeliveryTimings(plan)\nplan_with_timings = bdt.getPBSTimings(sort_spots=\"true\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Print plan\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(plan_with_timings._beams[0]._layers[0].__dict__)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/Segmentation/run_Segmentation.ipynb b/auto_examples/Segmentation/run_Segmentation.ipynb new file mode 100644 index 0000000..f5be4b7 --- /dev/null +++ b/auto_examples/Segmentation/run_Segmentation.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Segmentation\n*Author* : OpenTPS team\n\nThis example will present the basis of segmentation with openTPS core.\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport matplotlib.pyplot as plt\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images import CTImage\nfrom opentps.core.processing.segmentation.segmentation3D import applyThreshold\nfrom opentps.core.processing.segmentation.segmentationCT import SegmentationCT\nfrom opentps.core.examples.syntheticData import *\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = 'Output'\nif not os.path.exists(output_path):\n os.makedirs(output_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Genrerate synthetic CT image and segment it\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# GENERATE SYNTHETIC CT IMAGE\nct = createSynthetic3DCT()\n\n# APPLY THRESHOLD SEGMENTATION\nmask = applyThreshold(ct, -750)\n\n# APPLY CT BODY SEGMENTATION\nseg = SegmentationCT(ct)\nbody = seg.segmentBody()\nbones = seg.segmentBones()\nlungs = seg.segmentLungs()\n\n# CHECK RESULTS\nassert (body.imageArray[50,100,80] == True) & (body.imageArray[0,0,0] == False), f\"Wrong body segmentation\"\nassert (bones.imageArray[85,100,50] == True) & (bones.imageArray[85,110,50] == False), f\"Wrong bones segmentation\"\nassert (lungs.imageArray[120,100,35] == True) & (lungs.imageArray[50,100,35] == False), f\"Wrong lungs segmentation\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## DISPLAY RESULTS\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(2, 5)\nfig.tight_layout()\ny_slice = 100\nz_slice = 35 #round(ct.imageArray.shape[2] / 2) - 1\nax[0,0].imshow(ct.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,0].title.set_text('CT')\nax[0,1].imshow(mask.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1)\nax[0,1].title.set_text('Threshold')\nax[0,2].imshow(body.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1)\nax[0,2].title.set_text('Body')\nax[0,3].imshow(bones.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1)\nax[0,3].title.set_text('Bones')\nax[0,4].imshow(lungs.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1)\nax[0,4].title.set_text('Lungs')\n\nax[1,0].imshow(ct.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,0].title.set_text('CT')\nax[1,1].imshow(mask.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1)\nax[1,1].title.set_text('Threshold')\nax[1,2].imshow(body.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1)\nax[1,2].title.set_text('Body')\nax[1,3].imshow(bones.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1)\nax[1,3].title.set_text('Bones')\nax[1,4].imshow(lungs.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1)\nax[1,4].title.set_text('Lungs')\n\nplt.savefig(os.path.join(output_path, 'Example_Segmentation.png'))\n\nprint('Segmentation example completed')\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/dynamicData/exampleInterFractionChanges.ipynb b/auto_examples/dynamicData/exampleInterFractionChanges.ipynb new file mode 100644 index 0000000..39df236 --- /dev/null +++ b/auto_examples/dynamicData/exampleInterFractionChanges.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Inter Fraction Changes \nauthor: OpenTPS team\n\nThis example shows how to apply inter-fraction changes to a dynamic 3D model, including baseline shift, translation, rotation, and shrinkage of the target organ.\n\nrunning time: ~ 10 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import copy\nimport matplotlib.pyplot as plt\nimport time\nimport os\nimport sys\nimport cProfile as profile\nimport logging\npr = profile.Profile()\npr.enable()\n\ncurrentWorkingDir = os.getcwd()\nprint('currentWorkingDir :', currentWorkingDir)\n# while not os.path.isfile(currentWorkingDir + '/main.py'): currentWorkingDir = os.path.dirname(currentWorkingDir)\nsys.path.append(os.path.dirname(currentWorkingDir))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.io.serializedObjectIO import loadDataStructure\nfrom opentps.core.processing.imageProcessing.syntheticDeformation import applyBaselineShift, shrinkOrgan\nfrom opentps.core.processing.imageProcessing.resampler3D import crop3DDataAroundBox\nfrom opentps.core.processing.segmentation.segmentation3D import getBoxAroundROI\nfrom opentps.core.processing.deformableDataAugmentationToolBox.modelManipFunctions import *\nfrom opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData, applyTransform3D\nfrom opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D\nfrom opentps.core.examples.syntheticData import createSynthetic4DCT\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'exampleInterFractionChanges')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## paths selection\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "organ = 'lung'\nstudyFolder = 'FDGorFAZA_study/'\npatientFolder = 'Patient_4'\npatientComplement = '/1/FDG1'\nbasePath = '/DATA2/public/'\ndataPath = basePath + organ + '/' + studyFolder + patientFolder + patientComplement + '/dynModAndROIs_bodyCropped.p'\n\n#dataPath = './ImageData/lung/Patient_4/1/FDG1/dynModAndROIs_bodyCropped.p'\n\n# ctList, roiList = createSynthetic4DCT(returnTumorMask=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## parameters selection\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "bodyContourToUse = 'Body'\ntargetContourToUse = 'GTV T'\nlungContourToUse = 'R lung'\n\ncontourToAddShift = targetContourToUse\n\ncroppingContoursUsedXYZ = [targetContourToUse, bodyContourToUse, targetContourToUse]\nmarginInMM = [50, 0, 100]\n\n# interfraction changes parameters\nbaselineShift = [-5, 0, 10]\n# baselineShift = [0, 0, 0]\ntranslation = [-5, 3, 10]\n# translation = [0, 0, 0]\nrotation = [0, 5, 0]\n# rotation = [0, 0, 0]\nshrinkSize = [8, 5, 2]\n# shrinkSize = [0, 0, 0]\n\n# GPU used\ntryGPU = True\nusedGPU = 0\n\ntry:\n import cupy\n cupy.cuda.Device(usedGPU).use()\nexcept:\n print('Module Cupy not found or selected GPU not available')\n\n# data loading\npatient = loadDataStructure(dataPath)[0]\ndynMod = patient.getPatientDataOfType(\"Dynamic3DModel\")[0]\nrtStruct = patient.getPatientDataOfType(\"RTStruct\")[0]\n\nprint('Available ROIs')\nrtStruct.print_ROINames()\n\ngtvContour = rtStruct.getContourByName(targetContourToUse)\nGTVMask = gtvContour.getBinaryMask(origin=dynMod.midp.origin, gridSize=dynMod.midp.gridSize, spacing=dynMod.midp.spacing)\ngtvBox = getBoxAroundROI(GTVMask)\nGTVCenterOfMass = gtvContour.getCenterOfMass(dynMod.midp.origin, dynMod.midp.gridSize, dynMod.midp.spacing)\nGTVCenterOfMassInVoxels = getVoxelIndexFromPosition(GTVCenterOfMass, dynMod.midp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "get the body contour to adjust the crop in the direction of the DRR projection\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "bodyContour = rtStruct.getContourByName(bodyContourToUse)\nbodyMask = bodyContour.getBinaryMask(origin=dynMod.midp.origin, gridSize=dynMod.midp.gridSize, spacing=dynMod.midp.spacing)\nbodyBox = getBoxAroundROI(bodyMask)\nprint('Body Box from contour', bodyBox)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the cropping box\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "croppingBox = [[], [], []]\nfor i in range(3):\n if croppingContoursUsedXYZ[i] == bodyContourToUse:\n croppingBox[i] = bodyBox[i]\n elif croppingContoursUsedXYZ[i] == targetContourToUse:\n croppingBox[i] = gtvBox[i]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "crop the model data using the box\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "crop3DDataAroundBox(dynMod, croppingBox, marginInMM=marginInMM)\nGTVCenterOfMass = gtvContour.getCenterOfMass(dynMod.midp.origin, dynMod.midp.gridSize, dynMod.midp.spacing)\nGTVCenterOfMassInVoxels = getVoxelIndexFromPosition(GTVCenterOfMass, dynMod.midp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "get the mask in cropped version (the dynMod.midp is now cropped so its origin and gridSize has changed)\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "GTVMask = gtvContour.getBinaryMask(origin=dynMod.midp.origin, gridSize=dynMod.midp.gridSize,\n spacing=dynMod.midp.spacing)\ndynModCopy = copy.deepcopy(dynMod)\nGTVMaskCopy = copy.deepcopy(GTVMask)\n\nstartTime = time.time()\n\nprint('-' * 50)\nif contourToAddShift == targetContourToUse:\n print('Apply baseline shift of', baselineShift, 'to', contourToAddShift)\n dynMod, GTVMask = applyBaselineShift(dynMod, GTVMask, baselineShift, tryGPU=tryGPU)\nelse:\n print('Not implemented in this script --> must use the get contour by name function')\n\nprint('-' * 50)\ntranslateData(dynMod, translationInMM=translation, tryGPU=tryGPU)\ntranslateData(GTVMask, translationInMM=translation, tryGPU=tryGPU)\n\nprint('-'*50)\nrotateData(dynMod, rotAnglesInDeg=rotation)\nrotateData(GTVMask, rotAnglesInDeg=rotation)\n\nprint('-' * 50)\nshrinkedDynMod, shrinkedOrganMask = shrinkOrgan(dynMod, GTVMask, shrinkSize=shrinkSize, tryGPU=tryGPU)\nshrinkedDynMod.name = 'MidP_ShrinkedGTV'\n\nresampleImage3DOnImage3D(shrinkedDynMod.midp, dynModCopy.midp, inPlace=True, tryGPU=tryGPU)\nresampleImage3DOnImage3D(shrinkedOrganMask, GTVMaskCopy, inPlace=True, tryGPU=tryGPU)\n\nprint('-' * 50)\n\nstopTime = time.time()\nprint('time:', stopTime-startTime)\n\npatient.appendPatientData(shrinkedDynMod)\npatient.appendPatientData(shrinkedOrganMask)\n\nfig, ax = plt.subplots(1, 4)\nfig.suptitle('Example of baseline shift, translate, rotate and shrink')\nax[0].imshow(np.rot90(dynModCopy.midp.imageArray[:, GTVCenterOfMassInVoxels[1], :]), cmap='gray')\n# ax[0].imshow(GTVMaskCopy.imageArray[:, GTVCenterOfMassInVoxels[1], :], alpha=0.5, cmap='Reds')\nax[0].set_title('Initial image')\nax[1].imshow(np.rot90(shrinkedDynMod.midp.imageArray[:, GTVCenterOfMassInVoxels[1], :]), cmap='gray')\n# ax[1].imshow(shrinkedOrganMask.imageArray[:, GTVCenterOfMassInVoxels[1], :], alpha=0.5, cmap='Reds')\nax[1].set_title('After inter fraction changes')\nax[2].imshow(np.rot90(dynModCopy.midp.imageArray[:, GTVCenterOfMassInVoxels[1], :] - shrinkedDynMod.midp.imageArray[:, GTVCenterOfMassInVoxels[1], :]), cmap='gray')\nax[2].set_title('Image difference')\nax[3].imshow(np.rot90(GTVMaskCopy.imageArray[:, GTVCenterOfMassInVoxels[1], :] ^ shrinkedOrganMask.imageArray[:, GTVCenterOfMassInVoxels[1], :]), cmap='gray')\nax[3].set_title('Mask difference')\nplt.savefig(os.path.join(output_path, 'exampleInterFractionChanges.png'))\n\npr.disable()\n# pr.print_stats(sort='time')\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/dynamicData/run_exampleApplyBaselineShiftToModel.ipynb b/auto_examples/dynamicData/run_exampleApplyBaselineShiftToModel.ipynb new file mode 100644 index 0000000..ba73ad6 --- /dev/null +++ b/auto_examples/dynamicData/run_exampleApplyBaselineShiftToModel.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Applying Baseline Shift to a Model\nauthor: OpenTPS team\n\nThis example demonstrates how to apply a baseline shift to a model and visualize the results.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport matplotlib.pyplot as plt\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images import CTImage\nfrom opentps.core.data.images import ROIMask\nfrom opentps.core.processing.imageProcessing.syntheticDeformation import applyBaselineShift\nfrom opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel\nfrom opentps.core.data.dynamicData._dynamic3DSequence import Dynamic3DSequence\nfrom opentps.core.examples.syntheticData import *\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'ExampleApplyBaselineShiftToModel')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate synthetic 4DCT, mask, and MidP\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# GENERATE SYNTHETIC 4DCT\nCT4D = createSynthetic4DCT()\n\n# GENERATE MASK\nmask = np.full(CT4D.dyn3DImageList[0].gridSize, 0)\nmask[38:52, 87:103, 39:54] = 1\nroi = ROIMask(imageArray=mask, origin=[0, 0, 0], spacing=[1, 1, 1.5])\n\n# GENERATE MIDP\nModel = Dynamic3DModel()\nModel.computeMidPositionImage(CT4D, 0, tryGPU=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Apply baseline shift\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ModelShifted, maskShifted = applyBaselineShift(Model, roi, [5, 0, 10])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Regenerate 4D sequences from models\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "CT4DRegen = Dynamic3DSequence()\nfor i in range(len(CT4D.dyn3DImageList)):\n CT4DRegen.dyn3DImageList.append(Model.generate3DImage(i / len(CT4D.dyn3DImageList), amplitude=1))\nCT4DShifted = Dynamic3DSequence()\nfor i in range(len(CT4D.dyn3DImageList)):\n CT4DShifted.dyn3DImageList.append(ModelShifted.generate3DImage(i/len(CT4D.dyn3DImageList), amplitude=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Display results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(3, 7)\nfig.tight_layout()\ny_slice = 95\nax[1, 0].imshow(Model.midp.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1, 0].title.set_text('MidP')\nax[2, 0].imshow(ModelShifted.midp.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[2, 0].title.set_text('MidP shifted')\n\naverage = CT4D.dyn3DImageList[0].copy()\nfor i in range(len(CT4D.dyn3DImageList)-1):\n average._imageArray += CT4D.dyn3DImageList[i+1]._imageArray\naverage._imageArray = average.imageArray/len(CT4D.dyn3DImageList)\naverageRegen = CT4DRegen.dyn3DImageList[0].copy()\nfor i in range(len(CT4DRegen.dyn3DImageList) - 1):\n averageRegen._imageArray += CT4DRegen.dyn3DImageList[i + 1]._imageArray\naverageRegen._imageArray = averageRegen.imageArray / len(CT4DRegen.dyn3DImageList)\naverageShifted = CT4DShifted.dyn3DImageList[0].copy()\nfor i in range(len(CT4DShifted.dyn3DImageList) - 1):\n averageShifted._imageArray += CT4DShifted.dyn3DImageList[i + 1]._imageArray\naverageShifted._imageArray = averageShifted.imageArray / len(CT4DShifted.dyn3DImageList)\n\nax[0, 1].imshow(average.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0, 1].title.set_text('Average')\nax[1, 1].imshow(averageRegen.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1, 1].title.set_text('Gen average')\nax[2, 1].imshow(averageShifted.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[2, 1].title.set_text('Gen average shifted')\n\naverageRegen._imageArray -= average._imageArray\naverageShifted._imageArray -= average._imageArray\naverage._imageArray -= average._imageArray\nax[0, 0].imshow(average.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0, 0].title.set_text('-')\nax[0, 2].imshow(average.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0, 2].title.set_text('-')\nax[1, 2].imshow(averageRegen.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1, 2].title.set_text('Gen average diff')\nax[2, 2].imshow(averageShifted.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[2, 2].title.set_text('Gen average shifted diff')\n\nax[0, 3].imshow(CT4D.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0, 3].title.set_text('Phase 0')\nax[1, 3].imshow(CT4DRegen.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1, 3].title.set_text('Gen phase 0')\nax[2, 3].imshow(CT4DShifted.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[2, 3].title.set_text('Gen phase 0 shifted')\n\nax[0, 4].imshow(CT4D.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0, 4].title.set_text('Phase 1')\nax[1, 4].imshow(CT4DRegen.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1, 4].title.set_text('Gen phase 1')\nax[2, 4].imshow(CT4DShifted.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[2, 4].title.set_text('Gen phase 1 shifted')\n\nax[0, 5].imshow(CT4D.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0, 5].title.set_text('Phase 2')\nax[1, 5].imshow(CT4DRegen.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1, 5].title.set_text('Gen phase 2')\nax[2, 5].imshow(CT4DShifted.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[2, 5].title.set_text('Gen phase 2 shifted')\n\nax[0, 6].imshow(CT4D.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0, 6].title.set_text('Phase 3')\nax[1, 6].imshow(CT4DRegen.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1, 6].title.set_text('Gen phase 3')\nax[2, 6].imshow(CT4DShifted.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[2, 6].title.set_text('Gen phase 3 shifted')\n\nplt.savefig(os.path.join(output_path, 'BaselinesSHift.png')) \nprint('done')\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/dynamicData/run_exampleDeformableBreathigDataAugmentation.ipynb b/auto_examples/dynamicData/run_exampleDeformableBreathigDataAugmentation.ipynb new file mode 100644 index 0000000..728cc1c --- /dev/null +++ b/auto_examples/dynamicData/run_exampleDeformableBreathigDataAugmentation.ipynb @@ -0,0 +1,151 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Deformable Breathing Data Augmentation\nauthor: OpenTPS team\n\nThis example shows how to create a synthetic 4DCT, generate a mid-position CT, and create a dynamic sequence from breathing signals and the mid-position CT. The example also demonstrates how to visualize the generated dynamic sequence.\n\nrunning time: ~ 7 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import os\nimport sys\ncurrentWorkingDir = os.getcwd()\nsys.path.append(currentWorkingDir)\nimport numpy as np\nfrom pathlib import Path\nimport math\nimport logging\nimport matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from matplotlib.animation import FuncAnimation\nfrom opentps.core.data.dynamicData._breathingSignals import SyntheticBreathingSignal\nfrom opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel\nfrom opentps.core.processing.deformableDataAugmentationToolBox.generateDynamicSequencesFromModel import generateDynSeqFromBreathingSignalsAndModel\nfrom opentps.core.processing.imageProcessing.imageTransform3D import getVoxelIndexFromPosition\nfrom opentps.core.processing.imageProcessing.resampler3D import resample\nfrom opentps.core.examples.syntheticData import*\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'ExampleDeformableBreathingDataAugmentation')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate synthetic 4DCT\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "CT4D = createSynthetic4DCT(numberOfPhases=10)\n # CT4D = resample(CT4D, gridSize=(80, 50, 50))\n\nplt.figure()\nfig = plt.gcf()\ndef updateAnim(imageIndex):\n\n fig.clear()\n plt.imshow(np.rot90(CT4D.dyn3DImageList[imageIndex].imageArray[:, 95, :]))\n\nanim = FuncAnimation(fig, updateAnim, frames=len(CT4D.dyn3DImageList), interval=300)\nanim.save(os.path.join(output_path, 'anim.gif'))\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate MidP\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dynMod = Dynamic3DModel()\ndynMod.computeMidPositionImage(CT4D, 0, tryGPU=True)\n\nprint(dynMod.midp.origin, dynMod.midp.spacing, dynMod.midp.gridSize)\nprint('Resample model image')\ndynMod = resample(dynMod, gridSize=(80, 50, 50))\nprint('after resampling', dynMod.midp.origin, dynMod.midp.spacing, dynMod.midp.gridSize)\n\n# option 3\nfor field in dynMod.deformationList:\n print('Resample model field')\n field.resample(spacing=dynMod.midp.spacing, gridSize=dynMod.midp.gridSize, origin=dynMod.midp.origin)\n print('after resampling', field.origin, field.spacing, field.gridSize)\n\nsimulationTime = 10\namplitude = 10\n\nnewSignal = SyntheticBreathingSignal(amplitude=amplitude,\n breathingPeriod=4,\n meanNoise=0,\n varianceNoise=0,\n samplingPeriod=0.2,\n simulationTime=simulationTime,\n coeffMin=0,\n coeffMax=0,\n meanEvent=0/30,\n meanEventApnea=0)\n\nnewSignal.generate1DBreathingSignal()\nlinearIncrease = np.linspace(0.8, 10, newSignal.breathingSignal.shape[0])\n\nnewSignal.breathingSignal = newSignal.breathingSignal * linearIncrease\n\nnewSignal2 = SyntheticBreathingSignal()\nnewSignal2.breathingSignal = -newSignal.breathingSignal\n\nsignalList = [newSignal.breathingSignal, newSignal2.breathingSignal]\n\npointRLung = np.array([50, 100, 50])\npointLLung = np.array([120, 100, 50])\n\n## get points in voxels --> for the plot, not necessary for the process example\npointRLungInVoxel = getVoxelIndexFromPosition(pointRLung, dynMod.midp)\npointLLungInVoxel = getVoxelIndexFromPosition(pointLLung, dynMod.midp)\n\npointList = [pointRLung, pointLLung]\npointVoxelList = [pointRLungInVoxel, pointLLungInVoxel]\n\n## to show signals and ROIs\nprop_cycle = plt.rcParams['axes.prop_cycle']\ncolors = prop_cycle.by_key()['color']\nplt.figure(figsize=(12, 6))\nsignalAx = plt.subplot(2, 1, 2)\nfor pointIndex, point in enumerate(pointList):\n ax = plt.subplot(2, 2 * len(pointList), 2 * pointIndex + 1)\n ax.set_title('Slice Y:' + str(pointVoxelList[pointIndex][1]))\n ax.imshow(np.rot90(dynMod.midp.imageArray[:, pointVoxelList[pointIndex][1], :]))\n ax.scatter([pointVoxelList[pointIndex][0]], [dynMod.midp.imageArray.shape[2] - pointVoxelList[pointIndex][2]], c=colors[pointIndex], marker=\"x\", s=100)\n ax2 = plt.subplot(2, 2 * len(pointList), 2 * pointIndex + 2)\n ax2.set_title('Slice Z:' + str(pointVoxelList[pointIndex][2]))\n ax2.imshow(np.rot90(dynMod.midp.imageArray[:, :, pointVoxelList[pointIndex][2]], 3))\n ax2.scatter([pointVoxelList[pointIndex][0]], [pointVoxelList[pointIndex][1]], c=colors[pointIndex], marker=\"x\", s=100)\n signalAx.plot(newSignal.timestamps / 1000, signalList[pointIndex], c=colors[pointIndex])\n\nsignalAx.set_xlabel('Time (s)')\nsignalAx.set_ylabel('Deformation amplitude in Z direction (mm)')\nplt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dynSeq = generateDynSeqFromBreathingSignalsAndModel(dynMod, signalList, pointList, dimensionUsed='Z', outputType=np.int16)\ndynSeq.breathingPeriod = newSignal.breathingPeriod\ndynSeq.timingsList = newSignal.timestamps\n\nprint('/'*80, '\\n', '/'*80)\n\nplt.figure()\nfig = plt.gcf()\ndef updateAnim(imageIndex):\n\n fig.clear()\n plt.imshow(np.rot90(dynSeq.dyn3DImageList[imageIndex].imageArray[:, 29, :]))\n\nanim = FuncAnimation(fig, updateAnim, frames=len(dynSeq.dyn3DImageList), interval=300)\nanim.save(os.path.join(output_path, 'anim3.gif'))\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/dynamicData/run_exampleDeformationFromWeightMaps.ipynb b/auto_examples/dynamicData/run_exampleDeformationFromWeightMaps.ipynb new file mode 100644 index 0000000..1868e81 --- /dev/null +++ b/auto_examples/dynamicData/run_exampleDeformationFromWeightMaps.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Deformation from Weight Maps\nauthor: OpenTPS team\n\nThis example demonstrates how to apply a deformation to a model using weight maps and visualize the results.\n\nrunning time: ~ 7 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport matplotlib.pyplot as plt\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.processing.imageProcessing import resampler3D\nfrom opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel\nfrom opentps.core.examples.syntheticData import createSynthetic4DCT\nfrom opentps.core.processing.deformableDataAugmentationToolBox.weightMaps import generateDeformationFromTrackers, generateDeformationFromTrackersAndWeightMaps\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'ExampleDeformationFromWeightMaps')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate synthetic 4DCT and MidP\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# GENERATE SYNTHETIC 4D INPUT SEQUENCE\nCT4D = createSynthetic4DCT(numberOfPhases=10)\n\n# CREATE TRACKER POSITIONS\ntrackers = [[30, 75, 40],\n [70, 75, 40],\n [100, 75, 40],\n [140, 75, 40]]\n\n# GENERATE MIDP\nModel4D = Dynamic3DModel()\nModel4D.computeMidPositionImage(CT4D, 0, tryGPU=True)\n\n# GENERATE ADDITIONAL PHASES\ndf1, wm = generateDeformationFromTrackers(Model4D, [0, 0, 2/4, 2/4], [1, 1, 1, 1], trackers)\nim1 = df1.deformImage(Model4D.midp, fillValue='closest')\ndf2, wm = generateDeformationFromTrackers(Model4D, [0.5/4, 0.5/4, 1.5/4, 1.5/4], [1, 1, 1, 1], trackers)\nim2 = df2.deformImage(Model4D.midp, fillValue='closest')\ndf3 = generateDeformationFromTrackersAndWeightMaps(Model4D, [0, 0, 2/4, 2/4], [2, 2, 2, 2], wm)\nim3 = df3.deformImage(Model4D.midp, fillValue='closest')\n\n# RESAMPLE WEIGHT MAPS TO IMAGE RESOLUTION\nfor i in range(len(trackers)):\n resampler3D.resampleImage3DOnImage3D(wm[i], Model4D.midp, inPlace=True, fillValue=-1024.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Display results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(2, 5)\nax[0,0].imshow(Model4D.midp.imageArray[:, 50, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\ns0 = wm[0].imageArray[:, 50, :].T[::-1, ::1]\ns1 = wm[1].imageArray[:, 50, :].T[::-1, ::1]\ns2 = wm[2].imageArray[:, 50, :].T[::-1, ::1]\ns3 = wm[3].imageArray[:, 50, :].T[::-1, ::1]\nax[0,1].imshow(s0, cmap='Reds', origin='upper', vmin=0, vmax=1)\nax[0,2].imshow(s1, cmap='Reds', origin='upper', vmin=0, vmax=1)\nax[0,3].imshow(s2, cmap='Blues', origin='upper', vmin=0, vmax=1)\nax[0,4].imshow(s3, cmap='Blues', origin='upper', vmin=0, vmax=1)\nax[0,0].plot(trackers[0][0],100-trackers[0][2],'ro')\nax[0,0].plot(trackers[1][0],100-trackers[1][2],'ro')\nax[0,0].plot(trackers[2][0],100-trackers[2][2],'bo')\nax[0,0].plot(trackers[3][0],100-trackers[3][2],'bo')\nax[0,1].plot(trackers[0][0],100-trackers[0][2],'ro')\nax[0,2].plot(trackers[1][0],100-trackers[1][2],'ro')\nax[0,3].plot(trackers[2][0],100-trackers[2][2],'bo')\nax[0,4].plot(trackers[3][0],100-trackers[3][2],'bo')\n\nax[1,0].imshow(Model4D.midp.imageArray[:, :, 50].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\ns0 = wm[0].imageArray[:, :, 50].T[::-1, ::1]\ns1 = wm[1].imageArray[:, :, 50].T[::-1, ::1]\ns2 = wm[2].imageArray[:, :, 50].T[::-1, ::1]\ns3 = wm[3].imageArray[:, :, 50].T[::-1, ::1]\nax[1,1].imshow(s0, cmap='Reds', origin='upper', vmin=0, vmax=1)\nax[1,2].imshow(s1, cmap='Reds', origin='upper', vmin=0, vmax=1)\nax[1,3].imshow(s2, cmap='Blues', origin='upper', vmin=0, vmax=1)\nax[1,4].imshow(s3, cmap='Blues', origin='upper', vmin=0, vmax=1)\nax[1,0].plot(trackers[0][0],trackers[0][1],'ro')\nax[1,0].plot(trackers[1][0],trackers[1][1],'ro')\nax[1,0].plot(trackers[2][0],trackers[2][1],'bo')\nax[1,0].plot(trackers[3][0],trackers[3][1],'bo')\nax[1,1].plot(trackers[0][0],trackers[0][1],'ro')\nax[1,2].plot(trackers[1][0],trackers[1][1],'ro')\nax[1,3].plot(trackers[2][0],trackers[2][1],'bo')\nax[1,4].plot(trackers[3][0],trackers[3][1],'bo')\nax[0,0].title.set_text('MidP and trackers')\nax[0,1].title.set_text('Tracker 1')\nax[0,2].title.set_text('Tracker 2')\nax[0,3].title.set_text('Tracker 3')\nax[0,4].title.set_text('Tracker 4')\n\nfig, ax = plt.subplots(2, 4)\nfig.tight_layout()\ny_slice = round(Model4D.midp.imageArray.shape[1]/2)-1\nax[0,0].imshow(CT4D.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,0].title.set_text('Phase 0')\nax[0,1].imshow(CT4D.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,1].title.set_text('Phase 1')\nax[0,2].imshow(CT4D.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,2].title.set_text('Phase 2')\nax[0,3].imshow(CT4D.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,3].title.set_text('Phase 3')\nax[1,0].imshow(Model4D.midp.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,0].imshow(wm[0].imageArray[:, y_slice, :].T[::-1, ::1] + wm[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='Reds', origin='upper', vmin=0, vmax=1, alpha=0.3)\nax[1,0].imshow(wm[2].imageArray[:, y_slice, :].T[::-1, ::1] + wm[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='Blues', origin='upper', vmin=0, vmax=1, alpha=0.3)\nax[1, 0].plot(trackers[0][0],100-trackers[0][2], 'ro')\nax[1, 0].plot(trackers[1][0],100-trackers[1][2], 'ro')\nax[1, 0].plot(trackers[2][0],100-trackers[2][2], 'bo')\nax[1, 0].plot(trackers[3][0],100-trackers[3][2], 'bo')\nax[1,0].title.set_text('MidP and weight maps')\nax[1,1].imshow(im1.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,1].title.set_text('phases [0,2] - amplitude 1')\nax[1,2].imshow(im2.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,2].title.set_text('phases [0.5,1.5] - amplitude 1')\nax[1,3].imshow(im3.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,3].title.set_text('phases [0,2] - amplitude 2')\n\nplt.savefig(os.path.join(output_path, 'DeformationFromWeightMaps.png'))\nprint('done')\nprint(' ')\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/dynamicData/run_exampleMidP.ipynb b/auto_examples/dynamicData/run_exampleMidP.ipynb new file mode 100644 index 0000000..5a4a849 --- /dev/null +++ b/auto_examples/dynamicData/run_exampleMidP.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Midp\nauthor: OpenTPS team\n\nThis example shows how to create a mid-position CT from a 4DCT and visualize it.\n\nrunning time: ~ 6 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport matplotlib.pyplot as plt\nimport time\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel\nfrom opentps.core.data.dynamicData._dynamic3DSequence import Dynamic3DSequence\nfrom opentps.core.data.images import CTImage\nfrom opentps.core.examples.syntheticData import *\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'ExampleMidP')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate synthetic 4DCT And MidP\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# GENERATE SYNTHETIC 4D INPUT SEQUENCE\nCT4D = createSynthetic4DCT()\n\n# GENERATE MIDP\nModel4D = Dynamic3DModel()\nstartTime = time.time()\nModel4D.computeMidPositionImage(CT4D, 0, tryGPU=True)\nstopTime = time.time()\nprint('midP computed in ', np.round(stopTime - startTime, 2), 'seconds')\n\n# GENERATE ADDITIONAL PHASES\nim1 = Model4D.generate3DImage(0.5/4, amplitude=1, tryGPU=False)\nim2 = Model4D.generate3DImage(2/4, amplitude=2.0, tryGPU=False)\nim3 = Model4D.generate3DImage(2/4, amplitude=0.5, tryGPU=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Display results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(2, 4)\nfig.tight_layout()\ny_slice = 95\nax[0,0].imshow(CT4D.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,0].title.set_text('Phase 0')\nax[0,1].imshow(CT4D.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,1].title.set_text('Phase 1')\nax[0,2].imshow(CT4D.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,2].title.set_text('Phase 2')\nax[0,3].imshow(CT4D.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,3].title.set_text('Phase 3')\nax[1,0].imshow(Model4D.midp.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,0].title.set_text('MidP image')\nax[1,1].imshow(im1.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,1].title.set_text('phase 0.5 - amplitude 1')\nax[1,2].imshow(im2.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,2].title.set_text('phase 2 - amplitude 2')\nax[1,3].imshow(im3.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,3].title.set_text('phase 2 - amplitude 0.5')\n\nplt.savefig(os.path.join(output_path, 'ExampleMidp.png')) \nprint('MidP example completed')\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/examples1/DoseOptimizationRealPatient.ipynb b/auto_examples/examples1/DoseOptimizationRealPatient.ipynb deleted file mode 100644 index e29f3e9..0000000 --- a/auto_examples/examples1/DoseOptimizationRealPatient.ipynb +++ /dev/null @@ -1,50 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n# OpenTPS CORE : Real case optimization\n*Author* : Eliot Peeters\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## I am writing stuff\n\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import numpy as np\nimport os\nfrom matplotlib import pyplot as plt\n\n#Import the needed opentps.core packages\n\nfrom opentps.core.data.plan import PlanDesign\nfrom opentps.core.data import DVH\nfrom opentps.core.io import mcsquareIO\nfrom opentps.core.io.scannerReader import readScanner\nfrom opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig\nfrom opentps.core.processing.doseCalculation.mcsquareDoseCalculator import MCsquareDoseCalculator\nfrom opentps.core.io.dataLoader import readData\nfrom opentps.core.data.plan import ObjectivesList\nfrom opentps.core.data.plan import FidObjective\nfrom opentps.core.io.serializedObjectIO import saveBeamlets, saveRTPlan, loadBeamlets, loadRTPlan\n\n\n\n# In the next cell we configure the CT scan model used for the dose calculation and the bdl model. The ones used in this example are the default configuration of openTPS which may lead to some imprecision.\n\nctCalibration = readScanner(DoseCalculationConfig().scannerFolder)\nbdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.11.10" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/auto_examples/examples2/plot_testExample.ipynb b/auto_examples/examples2/plot_testExample.ipynb deleted file mode 100644 index 3032107..0000000 --- a/auto_examples/examples2/plot_testExample.ipynb +++ /dev/null @@ -1,61 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n# Example Title\n\nThis is an example script that demonstrates XYZ.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\nimport numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is a test cell.\n\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "x = np.linspace(0, 2 * np.pi, 100)\ny = np.sin(x)\n\nplt.plot(x, y)\nplt.xlabel(r\"$x$\")\nplt.ylabel(r\"$\\sin(x)$\")\n# To avoid matplotlib text output\nplt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.11.10" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/auto_examples/examples2/testExample.ipynb b/auto_examples/examples2/testExample.ipynb deleted file mode 100644 index 892de1a..0000000 --- a/auto_examples/examples2/testExample.ipynb +++ /dev/null @@ -1,43 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n# Example Title\n\nThis is an example script that demonstrates XYZ.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\nimport numpy as np\n\nx = np.linspace(0, 2 * np.pi, 100)\ny = np.sin(x)\n\nplt.plot(x, y)\nplt.xlabel(r\"$x$\")\nplt.ylabel(r\"$\\sin(x)$\")\n# To avoid matplotlib text output\nplt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.11.10" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/auto_examples/examples2/test_output.ipynb b/auto_examples/examples2/test_output.ipynb deleted file mode 100644 index a014a8b..0000000 --- a/auto_examples/examples2/test_output.ipynb +++ /dev/null @@ -1,43 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n# Example Title\n\nIf it prints, it works.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "print(1+1)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.11.10" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/auto_examples/imageProcessing/cupyVSsitkTransforms.ipynb b/auto_examples/imageProcessing/cupyVSsitkTransforms.ipynb new file mode 100644 index 0000000..9227a22 --- /dev/null +++ b/auto_examples/imageProcessing/cupyVSsitkTransforms.ipynb @@ -0,0 +1,194 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Cupy vs SitkTransforms\nauthor: OpenTPS team\n\nThis example shows how to use the OpenTPS transforms with cupy and compare the results with the SimpleITK transforms.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import copy\n\nimport matplotlib.pyplot as plt\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images import VectorField3D\nfrom opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel\nfrom opentps.core.data._transform3D import Transform3D\nfrom opentps.core.examples.showStuff import showModelWithAnimatedFields\nfrom opentps.core.examples.syntheticData import *\nfrom opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D\nfrom opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData\nfrom opentps.core.processing.imageProcessing.resampler3D import resample\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'CupyVsSitk')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate synthetic input immages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "imgSize = [40, 40, 40]\nimgSpacing = [1, 1, 2]\nobjectBorder = [[21, 33], [int(imgSize[1]/4), 3*int(imgSize[1]/4)], [21, 34]]\n\ntranslation = np.array([-10.22, 0, -14.56])\nrotation = np.array([0, 30, 0])\nrotCenter = 'imgCenter'\noutputBox = 'same'\n# interpOrder = 1\n\nshowImage = True\nshowField = True\nshowMask = True\n\nfixed = CTImage()\nfixed.spacing = np.array(imgSpacing)\nfixed.imageArray = np.full(imgSize, -1000)\nfixed.imageArray[objectBorder[0][0]: objectBorder[0][1],\n objectBorder[1][0]: objectBorder[1][1],\n objectBorder[2][0]: objectBorder[2][1]] = 100.0\n\ny_slice = int(imgSize[1]/2)\npointList = [[objectBorder[0][0], y_slice, objectBorder[2][1]-1],\n [objectBorder[0][1]-1, y_slice, objectBorder[2][0]],\n [objectBorder[0][0]+1, y_slice, objectBorder[2][0]+1],\n [objectBorder[0][0], y_slice, objectBorder[2][0]]]\n\nfieldFixed = VectorField3D()\nfieldFixed.imageArray = np.zeros((imgSize[0], imgSize[1], imgSize[2], 3))\nfieldFixed.spacing = np.array(imgSpacing)\nvectorList = [np.array([4, 6, 8]), np.array([0, 6, 8]), np.array([14, 6, 6]), np.array([6, 0, 0])]\nfor pointIdx in range(len(pointList)):\n fieldFixed.imageArray[pointList[pointIdx][0], pointList[pointIdx][1], pointList[pointIdx][2]] = vectorList[\n pointIdx]\n\nmaskFixed = ROIMask()\nmaskFixed.spacing = np.array(imgSpacing)\nmaskFixed.imageArray = np.zeros(imgSize).astype(bool)\nmaskFixed.imageArray[objectBorder[0][0]: objectBorder[0][1],\n objectBorder[1][0]: objectBorder[1][1],\n objectBorder[2][0]: objectBorder[2][1]] = True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "this function is just to see the results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def showImagesAndFieldAndMask(fixed, movingCupy1, movingCupy3, movingSitk, fieldFixed, fieldMovingCupy1, fieldMovingCupy3, fieldMovingSitk,\n maskFixed, maskMovingCupy1, maskMovingCupy3, maskMovingSitk, y_slice, figTitle, showImage=True,\n showField=True, showMask=True, ):\n\n compXFixed = fieldFixed.imageArray[:, y_slice, :, 0]\n compZFixed = fieldFixed.imageArray[:, y_slice, :, 2]\n compXMovingCupy1 = fieldMovingCupy1.imageArray[:, y_slice, :, 0]\n compZMovingCupy1 = fieldMovingCupy1.imageArray[:, y_slice, :, 2]\n compXMovingCupy3 = fieldMovingCupy3.imageArray[:, y_slice, :, 0]\n compZMovingCupy3 = fieldMovingCupy3.imageArray[:, y_slice, :, 2]\n compXMovingSITK = fieldMovingSitk.imageArray[:, y_slice, :, 0]\n compZMovingSITK = fieldMovingSitk.imageArray[:, y_slice, :, 2]\n\n fig, ax = plt.subplots(3, 4)\n fig.suptitle(figTitle)\n\n if showImage:\n ax[0, 0].imshow(fixed.imageArray[:, y_slice, :])\n ax[0, 1].imshow(movingCupy1.imageArray[:, y_slice, :])\n ax[0, 2].imshow(movingCupy3.imageArray[:, y_slice, :])\n ax[0, 3].imshow(movingSitk.imageArray[:, y_slice, :])\n ax[1, 0].imshow(movingCupy1.imageArray[:, y_slice, :] - movingSitk.imageArray[:, y_slice, :])\n ax[1, 0].set_xlabel('Img diff cupy1-sitk')\n ax[1, 1].imshow(movingCupy1.imageArray[:, y_slice, :] - movingCupy3.imageArray[:, y_slice, :])\n ax[1, 1].set_xlabel('Img diff cupy1-cupy3')\n if showField:\n ax[0, 0].quiver(compZFixed, compXFixed, alpha=0.5, color='red', angles='xy', scale_units='xy', scale=2, width=.010)\n ax[0, 1].quiver(compZMovingCupy1, compXMovingCupy1, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010)\n ax[0, 2].quiver(compZMovingCupy3, compXMovingCupy3, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010)\n ax[0, 3].quiver(compZMovingSITK, compXMovingSITK, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010)\n ax[1, 2].quiver(compZMovingCupy1 - compZMovingSITK, compXMovingCupy1 - compXMovingSITK, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010)\n ax[1, 2].set_xlabel('Field diff cupy1-sitk')\n ax[1, 3].quiver(compZMovingCupy1 - compZMovingCupy3, compXMovingCupy1 - compXMovingCupy3, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010)\n ax[1, 3].set_xlabel('Field diff cupy1-cupy3')\n if showMask:\n ax[0, 0].imshow(maskFixed.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds')\n ax[0, 1].imshow(maskMovingCupy1.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds')\n ax[0, 2].imshow(maskMovingCupy3.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds')\n ax[0, 3].imshow(maskMovingSitk.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds')\n ax[2, 0].imshow(maskMovingCupy1.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :] ^ maskMovingSitk.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds')\n ax[2, 0].set_xlabel('Mask diff cupy1-sitk')\n ax[2, 1].imshow(maskMovingCupy1.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice,:] ^ maskMovingCupy3.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds')\n ax[2, 1].set_xlabel('Mask diff cupy1-cupy3')\n\n ax[0, 0].set_title('Fixed')\n ax[0, 0].set_xlabel(f\"{fixed.origin}\\n{fixed.spacing}\\n{fixed.gridSize}\")\n ax[0, 1].set_title('Moving Cupy1')\n ax[0, 1].set_xlabel(f\"{movingCupy1.origin}\\n{movingCupy1.spacing}\\n{movingCupy1.gridSize}\")\n ax[0, 2].set_title('Moving Cupy3')\n ax[0, 2].set_xlabel(f\"{movingCupy3.origin}\\n{movingCupy3.spacing}\\n{movingCupy3.gridSize}\")\n ax[0, 3].set_title('Moving SITK')\n ax[0, 3].set_xlabel(f\"{movingSitk.origin}\\n{movingSitk.spacing}\\n{movingSitk.gridSize}\")\n\n plt.savefig(os.path.join(output_path, 'cupy_VS_sitk_Transforms.png'))\n plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test using a Transform3D\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print('-' * 40)\n\nmovingCupy1 = copy.deepcopy(fixed)\nmovingCupy3 = copy.deepcopy(fixed)\nmovingSitk = copy.deepcopy(fixed)\nfieldMovingCupy1 = copy.deepcopy(fieldFixed)\nfieldMovingCupy3 = copy.deepcopy(fieldFixed)\nfieldMovingSitk = copy.deepcopy(fieldFixed)\nmaskMovingCupy1 = copy.deepcopy(maskFixed)\nmaskMovingCupy3 = copy.deepcopy(maskFixed)\nmaskMovingSitk = copy.deepcopy(maskFixed)\n\n## Create a transform 3D\nprint('Create a transform 3D')\ntransform3D = Transform3D()\ntransform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation)\ntransform3D.setCenter(rotCenter)\nprint('Translation', transform3D.getTranslation())\nprint('Rotation', transform3D.getRotationAngles(inDegrees=True))\n\nprint('Moving with transform3D')\nmovingCupy1 = transform3D.deformData(movingCupy1, outputBox=outputBox, fillValue=-1000, tryGPU=True, interpOrder=1)\nfieldMovingCupy1 = transform3D.deformData(fieldMovingCupy1, outputBox=outputBox, tryGPU=True, interpOrder=1)\nmaskMovingCupy1 = transform3D.deformData(maskMovingCupy1, outputBox=outputBox, tryGPU=True, interpOrder=1)\n\nmovingCupy3 = transform3D.deformData(movingCupy3, outputBox=outputBox, fillValue=-1000, tryGPU=True, interpOrder=3)\nfieldMovingCupy3 = transform3D.deformData(fieldMovingCupy3, outputBox=outputBox, tryGPU=True, interpOrder=3)\nmaskMovingCupy3 = transform3D.deformData(maskMovingCupy3, outputBox=outputBox, tryGPU=True, interpOrder=3)\n\nmovingSitk = transform3D.deformData(movingSitk, outputBox=outputBox, fillValue=-1000)\nfieldMovingSitk = transform3D.deformData(fieldMovingSitk, outputBox=outputBox)\nmaskMovingSitk = transform3D.deformData(maskMovingSitk, outputBox=outputBox)\n\nshowImagesAndFieldAndMask(fixed, movingCupy1, movingCupy3, movingSitk, fieldFixed, fieldMovingCupy1,\n fieldMovingCupy3, fieldMovingSitk,\n maskFixed, maskMovingCupy1, maskMovingCupy3, maskMovingSitk, y_slice,\n figTitle='Test using a Transform3D', showField=showField, showImage=showImage,\n showMask=showMask)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test using translateData\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print('-' * 40)\n\nmovingCupy1 = copy.deepcopy(fixed)\nmovingCupy3 = copy.deepcopy(fixed)\nmovingSitk = copy.deepcopy(fixed)\nfieldMovingCupy1 = copy.deepcopy(fieldFixed)\nfieldMovingCupy3 = copy.deepcopy(fieldFixed)\nfieldMovingSitk = copy.deepcopy(fieldFixed)\nmaskMovingCupy1 = copy.deepcopy(maskFixed)\nmaskMovingCupy3 = copy.deepcopy(maskFixed)\nmaskMovingSitk = copy.deepcopy(maskFixed)\n\nprint('Moving with translateData')\ntranslateData(movingCupy1, translationInMM=translation, outputBox=outputBox, fillValue=-1000, tryGPU=True, interpOrder=1)\ntranslateData(fieldMovingCupy1, translationInMM=translation, outputBox=outputBox, tryGPU=True, interpOrder=1)\ntranslateData(maskMovingCupy1, translationInMM=translation, outputBox=outputBox, tryGPU=True, interpOrder=1)\n\ntranslateData(movingCupy3, translationInMM=translation, outputBox=outputBox, fillValue=-1000, tryGPU=True, interpOrder=3)\ntranslateData(fieldMovingCupy3, translationInMM=translation, outputBox=outputBox, tryGPU=True, interpOrder=3)\ntranslateData(maskMovingCupy3, translationInMM=translation, outputBox=outputBox, tryGPU=True, interpOrder=3)\n\ntranslateData(movingSitk, translationInMM=translation, outputBox=outputBox, fillValue=-1000)\ntranslateData(fieldMovingSitk, translationInMM=translation, outputBox=outputBox)\ntranslateData(maskMovingSitk, translationInMM=translation, outputBox=outputBox)\n\nshowImagesAndFieldAndMask(fixed, movingCupy1, movingCupy3, movingSitk, fieldFixed, fieldMovingCupy1,\n fieldMovingCupy3, fieldMovingSitk,\n maskFixed, maskMovingCupy1, maskMovingCupy3, maskMovingSitk, y_slice,\n figTitle='Test using translateData', showField=showField, showImage=showImage,\n showMask=showMask)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test using rotateData\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print('-' * 40)\n\nmovingCupy1 = copy.deepcopy(fixed)\nmovingCupy3 = copy.deepcopy(fixed)\nmovingSitk = copy.deepcopy(fixed)\nfieldMovingCupy1 = copy.deepcopy(fieldFixed)\nfieldMovingCupy3 = copy.deepcopy(fieldFixed)\nfieldMovingSitk = copy.deepcopy(fieldFixed)\nmaskMovingCupy1 = copy.deepcopy(maskFixed)\nmaskMovingCupy3 = copy.deepcopy(maskFixed)\nmaskMovingSitk = copy.deepcopy(maskFixed)\n\nprint('Moving with rotateData')\nrotateData(movingCupy1, rotAnglesInDeg=rotation, outputBox=outputBox, fillValue=-1000, rotCenter=rotCenter, tryGPU=True, interpOrder=1)\nrotateData(fieldMovingCupy1, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter, tryGPU=True, interpOrder=1)\nrotateData(maskMovingCupy1, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter, tryGPU=True, interpOrder=1)\n\nrotateData(movingCupy3, rotAnglesInDeg=rotation, outputBox=outputBox, fillValue=-1000, rotCenter=rotCenter, tryGPU=True, interpOrder=3)\nrotateData(fieldMovingCupy3, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter, tryGPU=True, interpOrder=3)\nrotateData(maskMovingCupy3, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter, tryGPU=True, interpOrder=3)\n\nrotateData(movingSitk, rotAnglesInDeg=rotation, outputBox=outputBox, fillValue=-1000, rotCenter=rotCenter)\nrotateData(fieldMovingSitk, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter)\nrotateData(maskMovingSitk, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter)\n\nshowImagesAndFieldAndMask(fixed, movingCupy1, movingCupy3, movingSitk, fieldFixed, fieldMovingCupy1,\n fieldMovingCupy3, fieldMovingSitk,\n maskFixed, maskMovingCupy1, maskMovingCupy3, maskMovingSitk, y_slice,\n figTitle='Test using rotateData', showField=showField, showImage=showImage,\n showMask=showMask)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/imageProcessing/exampleDRRwithTigre.ipynb b/auto_examples/imageProcessing/exampleDRRwithTigre.ipynb new file mode 100644 index 0000000..dd08715 --- /dev/null +++ b/auto_examples/imageProcessing/exampleDRRwithTigre.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# DRR with Tigre\nauthor: OpenTPS team\n\nThis example demonstrates how to generate Digital Reconstructed Radiographs (DRR) using the TIGRE library in OpenTPS.\n\nrunning time: ~ 10 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## setting up the environment in google colab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install cupy-cuda12x')\n get_ipython().system('git clone https://github.com/CERN/TIGRE.git')\n get_ipython().system('pip install ./TIGRE')\n get_ipython().system('pip install scipy==1.10.1')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import math\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images import CTImage\nfrom opentps.core.processing.imageSimulation.ForwardProjectorTigre import forwardProjectionTigre\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'ExampleDRRwithTigre')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## GENERATE SYNTHETIC CT IMAGE\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "im = np.full((170, 170, 100), -1000)\nim[20:150, 70:130, :] = 0\nim[30:70, 80:120, 20:] = -800\nim[100:140, 80:120, 20:] = -800\nim[45:55, 95:105, 30:40] = 0\nim[80:90, 115:125, :] = 800\nim[:, 130:140, :] = 100 # couch\nct = CTImage(imageArray=im, name='fixed', origin=[0, 0, 0], spacing=[2, 2.5, 3])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute projections\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "angles = np.array([0,90,180])*2*math.pi/360\nDRR_no_noise = forwardProjectionTigre(ct, angles, axis='Z', poissonNoise=None, gaussianNoise=None)\nDRR_realistic = forwardProjectionTigre(ct, angles, axis='Z')\nDRR_high_noise = forwardProjectionTigre(ct, angles, axis='Z', poissonNoise=3e4, gaussianNoise=30)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute error\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "error_realistic_projections = np.abs(DRR_realistic-DRR_no_noise)\nerror_realistic_projections_high_noise = np.abs(DRR_high_noise-DRR_no_noise)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Display results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(3, 5)\nax[0,0].imshow(DRR_no_noise[0][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise))\nax[0,1].imshow(DRR_realistic[0][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise))\nax[0,2].imshow(error_realistic_projections[0][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100)\nax[0,3].imshow(DRR_high_noise[0][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise))\nax[0,4].imshow(error_realistic_projections_high_noise[0][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100)\nax[1,0].imshow(DRR_no_noise[1][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise))\nax[1,1].imshow(DRR_realistic[1][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise))\nax[1,2].imshow(error_realistic_projections[1][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100)\nax[1,3].imshow(DRR_high_noise[1][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise))\nax[1,4].imshow(error_realistic_projections_high_noise[1][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100)\nax[2,0].imshow(DRR_no_noise[2][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise))\nax[2,1].imshow(DRR_realistic[2][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise))\nax[2,2].imshow(error_realistic_projections[2][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100)\nax[2,3].imshow(DRR_high_noise[2][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise))\nax[2,4].imshow(error_realistic_projections_high_noise[2][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100)\nax[0,0].title.set_text('Perfect DRR')\nax[0,1].title.set_text('DRR with moderate noise')\nax[0,2].title.set_text('Moderate noise')\nax[0,3].title.set_text('DRR with high noise')\nax[0,4].title.set_text('High noise')\n\nplt.savefig(os.path.join(output_path, 'ExampleDRRwithTigre.png'))\n\nprint('TIGRE DRR example completed')\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/imageProcessing/exampleTransform3DCupy.ipynb b/auto_examples/imageProcessing/exampleTransform3DCupy.ipynb new file mode 100644 index 0000000..5f082c8 --- /dev/null +++ b/auto_examples/imageProcessing/exampleTransform3DCupy.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Transform 3D Image with CuPy\nauthor: OpenTPS team\n\nThis example demonstrates how to apply a 3D transformation to a synthetic CT image using the OpenTPS library with CuPy for efficient computation.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import copy\n\nimport matplotlib.pyplot as plt\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images import VectorField3D\nfrom opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel\nfrom opentps.core.data._transform3D import Transform3D\nfrom opentps.core.examples.showStuff import showModelWithAnimatedFields\nfrom opentps.core.examples.syntheticData import *\nfrom opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D\nfrom opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData\nfrom opentps.core.processing.imageProcessing.resampler3D import resample\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'ExampleTransform3DCupy')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## GENERATE SYNTHETIC INPUT IMAGES\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fixed = CTImage()\nfixed.spacing = np.array([1, 1, 1])\nfixed.imageArray = np.full((20, 20, 20), -1000)\nfixed.imageArray[11:16, 5:14, 11:14] = 100.0\n\nmoving = copy.deepcopy(fixed)\nmovingTrans = copy.deepcopy(fixed)\nmovingRot = copy.deepcopy(fixed)\nmovingBoth = copy.deepcopy(fixed)\n\ntranslation = np.array([0, 0, 0])\nrotation = np.array([0, 45, 0])\nrotCenter='imgCenter'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a transform 3D\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print('Create a transform 3D')\ntransform3D = Transform3D()\ntransform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation)\ntransform3D.setCenter(rotCenter)\nprint('Translation', transform3D.getTranslation())\nprint('Rotation', transform3D.getRotationAngles(inDegrees=True))\n\nprint('moving with transform3D')\nmoving = transform3D.deformData(moving, outputBox='same', fillValue=-1000, tryGPU=True)\n\nprint('moving translation')\ntranslateData(movingTrans, translationInMM=translation, outputBox='same', fillValue=-1000, tryGPU=True)\nprint('moving rotation')\nrotateData(movingRot, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=-1000, tryGPU=True)\n# movingRot = resampleImage3DOnImage3D(movingRot, fixedImage=fixed, fillValue=-1000)\nprint('moving both')\ntranslateData(movingBoth, translationInMM=translation, outputBox='same', fillValue=-1000, tryGPU=True)\nrotateData(movingBoth, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=-1000, tryGPU=True)\n\ny_slice = 10\n\nfig, ax = plt.subplots(1, 6)\nax[0].set_title('fixed')\nax[0].imshow(fixed.imageArray[:, y_slice, :])\nax[0].set_xlabel(f\"{fixed.origin}\\n{fixed.spacing}\\n{fixed.gridSize}\")\n\nax[1].set_title('translateData')\nax[1].imshow(movingTrans.imageArray[:, y_slice, :])\nax[1].set_xlabel(f\"{movingTrans.origin}\\n{movingTrans.spacing}\\n{movingTrans.gridSize}\")\n\nax[2].set_title('rotateData')\nax[2].imshow(movingRot.imageArray[:, y_slice, :])\nax[2].set_xlabel(f\"{movingRot.origin}\\n{movingRot.spacing}\\n{movingRot.gridSize}\")\n\nax[3].set_title('both')\nax[3].imshow(movingBoth.imageArray[:, y_slice, :])\nax[3].set_xlabel(f\"{movingBoth.origin}\\n{movingBoth.spacing}\\n{movingBoth.gridSize}\")\n\nax[4].set_title('transform3D')\nax[4].imshow(moving.imageArray[:, y_slice, :])\nax[4].set_xlabel(f\"{moving.origin}\\n{moving.spacing}\\n{moving.gridSize}\")\n\nax[5].set_title('transform3D-both')\nax[5].imshow(moving.imageArray[:, y_slice, :] - movingBoth.imageArray[:, y_slice, :])\n\nplt.savefig(os.path.join(output_path, 'ExampleTransform3DCupy.png'))\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a dynamic model with the transform\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(' --------------------- start test with model -----------------------------')\n\nCT4D = createSynthetic4DCT(numberOfPhases=4)\n# GENERATE MIDP\nfixedDynMod = Dynamic3DModel()\nfixedDynMod.computeMidPositionImage(CT4D, 0, tryGPU=True)\n\nprint(fixedDynMod.midp.origin, fixedDynMod.midp.spacing, fixedDynMod.midp.gridSize)\nprint('Resample model image')\nfixedDynMod = resample(fixedDynMod, gridSize=(80, 50, 50))\nprint('after resampling', fixedDynMod.midp.origin, fixedDynMod.midp.spacing, fixedDynMod.midp.gridSize)\n\n# option 3\nfor field in fixedDynMod.deformationList:\n print('Resample model field')\n field.resample(spacing=fixedDynMod.midp.spacing, gridSize=fixedDynMod.midp.gridSize, origin=fixedDynMod.midp.origin)\n print('after resampling', field.origin, field.spacing, field.gridSize)\n\nshowModelWithAnimatedFields(fixedDynMod)\n\nmovingDynMod = copy.copy(fixedDynMod)\n\nrotateData(movingDynMod, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=-1000, tryGPU=True)\n\nshowModelWithAnimatedFields(movingDynMod)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate synthetic input images \n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fixed = CTImage()\nfixed.imageArray = np.full((20, 20, 20), -1000)\ny_slice = 10\n\npointList = [[15, y_slice, 15], [15, y_slice, 10], [12, y_slice, 12], [10, y_slice, 10]]\nfor point in pointList:\n fixed.imageArray[point[0], point[1], point[2]] = 200\n\nfieldFixed = VectorField3D()\nfieldFixed.imageArray = np.zeros((20, 20, 20, 3))\nvectorList = [np.array([2, 3, 4]), np.array([0, 3, 4]), np.array([7, 3, 3]), np.array([2, 0, 0])]\nfor pointIdx in range(len(pointList)):\n fieldFixed.imageArray[pointList[pointIdx][0], pointList[pointIdx][1], pointList[pointIdx][2]] = vectorList[\n pointIdx]\n\nmoving = copy.copy(fixed)\nfieldMoving = copy.copy(fieldFixed)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a transform 3D\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print('Create a transform 3D')\ntransform3D = Transform3D()\ntransform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation)\ntransform3D.setCenter(rotCenter)\nprint('Translation', transform3D.getTranslation())\nprint('Rotation', transform3D.getRotationAngles(inDegrees=True))\n\nprint('moving with transform3D')\n# moving = transform3D.deformData(moving, fillValue=-1000, outputBox='same', tryGPU=True)\n# fieldMoving = transform3D.deformData(fieldMoving, fillValue=0, outputBox='same', tryGPU=True)\n\nrotateData(moving, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=-1000, tryGPU=True)\nrotateData(fieldMoving, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=0, tryGPU=True)\n\nmoving = resampleImage3DOnImage3D(moving, fixedImage=fixed, fillValue=-1000)\nprint('fixed.origin', fixed.origin, 'moving.origin', moving.origin)\nfieldMoving = resampleImage3DOnImage3D(fieldMoving, fixedImage=fixed, fillValue=0)\nprint('fieldFixed.origin', fieldFixed.origin, 'fieldMoving.origin', fieldMoving.origin)\n\nprint('ici ', fieldMoving.imageArray[10, y_slice, 10])\n\ncompXFixed = fieldFixed.imageArray[:, y_slice, :, 0]\ncompZFixed = fieldFixed.imageArray[:, y_slice, :, 2]\ncompXMoving = fieldMoving.imageArray[:, y_slice, :, 0]\ncompZMoving = fieldMoving.imageArray[:, y_slice, :, 2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Display results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(1, 2)\nax[0].imshow(fixed.imageArray[:, y_slice, :])\nax[0].quiver(compZFixed, compXFixed, alpha=0.5, color='red', angles='xy', scale_units='xy', scale=2, width=.010)\nax[1].imshow(moving.imageArray[:, y_slice, :])\nax[1].quiver(compZMoving, compXMoving, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010)\nplt.show()\n\n\nprint('start ROIMask test')\nfixedMask = ROIMask.fromImage3D(fixed)\nfixedMask.imageArray = np.zeros(fixedMask.gridSize).astype(bool)\nfixedMask.imageArray[12:15, 8:12, 8:18] = True\nplt.figure()\nplt.imshow(fixedMask.imageArray[:, y_slice, :])\nplt.show()\n\nprint(fixedMask.origin, fixedMask.gridSize, fixedMask.spacing, fixedMask.imageArray.dtype)\n\nmovingMask = copy.copy(fixedMask)\nmovingMask = transform3D.deformData(movingMask, outputBox='same')\n\nprint(movingMask.origin, movingMask.gridSize, movingMask.spacing, movingMask.imageArray.dtype)\n\nplt.figure()\nplt.subplot(1, 2, 1)\nplt.imshow(fixedMask.imageArray[:, y_slice, :])\nplt.savefig(os.path.join(output_path, 'ExampleTransform3DCupy_fixexMask.png'))\nplt.subplot(1, 2, 2)\nplt.imshow(movingMask.imageArray[:, y_slice, :])\nplt.savefig(os.path.join(output_path, 'ExampleTransform3DCupy_movingMask.png'))\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/imageProcessing/run_exampleApplyBaselineShift.ipynb b/auto_examples/imageProcessing/run_exampleApplyBaselineShift.ipynb new file mode 100644 index 0000000..0071943 --- /dev/null +++ b/auto_examples/imageProcessing/run_exampleApplyBaselineShift.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Applying Baseline Shift\nauthor: OpenTPS team\n\nThis example demonstrates how to apply a baseline shift to a synthetic CT image and its corresponding tumor mask using the OpenTPS library. The example generates a synthetic 3D CT image, applies different baseline shifts, and visualizes the results.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport matplotlib.pyplot as plt\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images import CTImage\nfrom opentps.core.data.images import ROIMask\nfrom opentps.core.processing.imageProcessing.syntheticDeformation import applyBaselineShift\nfrom opentps.core.examples.syntheticData import *\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'ExampleApplyBasilineShift')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## GENERATE SYNTHETIC CT IMAGE AND TUMOR MASK\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ct, roi = createSynthetic3DCT(returnTumorMask=True) # roi = [45, 54], [95, 104], [30, 39]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## APPLY BASELINE SHIFT\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ctDef1, maskDef1 = applyBaselineShift(ct, roi, [4, 4, 4])\nctDef2, maskDef2 = applyBaselineShift(ct, roi, [-4, -4, -4])\nctDef3, maskDef3 = applyBaselineShift(ct, roi, [0, 0, -16])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CHECK RESULTS\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "assert (np.all(ctDef1.imageArray[50:57, 100:107, 36:42] > -700)), f\"Error for baseline shift +4,+4,+4\"\nassert (np.all(ctDef2.imageArray[42:49, 92:99, 28:34] > -700)), f\"Error for baseline shift -4,-4,-4\"\nassert (np.all(ctDef3.imageArray[46:53, 96:103, 22:32] > -700)), f\"Error for baseline shift 0,0,-16\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## DISPLAY RESULTS\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(2, 4)\nfig.tight_layout()\ny_slice = 100\nz_slice = 35 #round(ct.imageArray.shape[2] / 2) - 1\nax[0,0].imshow(ct.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,0].title.set_text('CT')\nax[0,1].imshow(ctDef1.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,1].title.set_text('baseline shift 4,4,4')\nax[0,2].imshow(ctDef2.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,2].title.set_text('baseline shift -4,-4,-4')\nax[0,3].imshow(ctDef3.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[0,3].title.set_text('baseline shift 0,0,-16')\n\nax[1,0].imshow(ct.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,0].title.set_text('CT')\nax[1,1].imshow(ctDef1.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,1].title.set_text('baseline shift 4,4,4')\nax[1,2].imshow(ctDef2.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,2].title.set_text('baseline shift -4,-4,-4')\nax[1,3].imshow(ctDef3.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000)\nax[1,3].title.set_text('baseline shift 0,0,-16')\n\nplt.savefig(os.path.join(output_path, 'ExampleApplyBaselinesShift.png'))\nprint('Baseline shift example completed')\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/imageProcessing/run_exampleDilateBinaryMask.ipynb b/auto_examples/imageProcessing/run_exampleDilateBinaryMask.ipynb new file mode 100644 index 0000000..b7a7884 --- /dev/null +++ b/auto_examples/imageProcessing/run_exampleDilateBinaryMask.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Dilate Binary Mask \nauthor: OpenTPS team\n\nThis example shows how to dilate a binary mask using the OpenTPS library.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images._roiMask import ROIMask\nfrom opentps.core.processing.imageProcessing.roiMasksProcessing import buildStructElem, dilateMaskScipy\nimport os\nimport logging\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'ExampleDilateBinaryMask')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a synthetic 3D ROI mask\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "roi = ROIMask(name='TV')\nroi.color = (255, 0, 0)# red\ndata = np.zeros((100, 100, 100)).astype(bool)\ndata[50:60, 50:60, 50:60] = True\nroi.imageArray = data\nroi.spacing = np.array([1, 1, 2])\n\nradius = np.array([4, 4, 6])\nstruct = buildStructElem(radius / np.array(roi.spacing))\n\nroi_scipy = roi.copy()\ndilateMaskScipy(roi_scipy, radius=radius) # scipy\nprint(radius, 'before roi_sitk')\nroi_sitk = roi.copy()\nroi_sitk.dilateMask(radius=radius)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualize the results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plt.figure()\nplt.subplot(2, 4, 1)\nplt.imshow(roi.imageArray[55, :, :], cmap='gray')\nplt.title(\"Original\")\n\nplt.subplot(2, 4, 2)\nplt.imshow(roi_scipy.imageArray[55, :, :], cmap='gray')\nplt.title(\"Scipy\")\n\nplt.subplot(2, 4, 3)\nplt.imshow(roi_sitk.imageArray[55, :, :], cmap='gray')\nplt.title(\"SITK\")\n\nplt.subplot(2, 4, 5)\nplt.imshow(roi_scipy.imageArray[55, :, :] ^ roi_sitk.imageArray[55, :, :], cmap='gray')\nplt.title(\"diff Scipy-SITK\")\n\nplt.subplot(2, 4, 6)\nplt.imshow(roi_scipy.imageArray[55, :, :] ^ roi.imageArray[55, :, :], cmap='gray')\nplt.title(\"diff Scipy-ori\")\n\nplt.subplot(2, 4, 7)\nplt.imshow(roi_sitk.imageArray[55, :, :] ^ roi.imageArray[55, :, :], cmap='gray')\nplt.title(\"diff SITK-ori\")\n\nplt.savefig(os.path.join(output_path, 'ExampleDilateBinary.png')) \nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/imageProcessing/run_exampleTransform3D.ipynb b/auto_examples/imageProcessing/run_exampleTransform3D.ipynb new file mode 100644 index 0000000..359c5ad --- /dev/null +++ b/auto_examples/imageProcessing/run_exampleTransform3D.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Transform 3D\nauthor: OpenTPS team\n\nThis example demonstrates how to apply a 3D transformation to a synthetic CT image using the OpenTPS library.\n\nrunning time: ~ 6 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import copy\n\nimport matplotlib.pyplot as plt\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images import VectorField3D\nfrom opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel\nfrom opentps.core.data._transform3D import Transform3D\nfrom opentps.core.examples.showStuff import showModelWithAnimatedFields\nfrom opentps.core.examples.syntheticData import *\nfrom opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D\nfrom opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData\nfrom opentps.core.processing.imageProcessing.resampler3D import resample\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = os.path.join(os.getcwd(), 'Output', 'ExampleTransform3D')\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## GENERATE SYNTHETIC INPUT IMAGES\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fixed = CTImage()\nfixed.imageArray = np.full((20, 20, 20), -1000)\nfixed.imageArray[11:16, 5:14, 11:14] = 100.0\n\nmoving = copy.copy(fixed)\nmovingTrans = copy.copy(fixed)\nmovingRot = copy.copy(fixed)\nmovingBoth = copy.copy(fixed)\n\ntranslation = np.array([-5, 0, -2])\nrotation = np.array([0, -20, 0])\nrotCenter='imgCenter'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a transform 3D\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print('Create a transform 3D')\ntransform3D = Transform3D()\ntransform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation)\ntransform3D.setCenter(rotCenter)\nprint('Translation', transform3D.getTranslation())\nprint('Rotation', transform3D.getRotationAngles(inDegrees=True))\n\nprint('moving with transform3D')\nmoving = transform3D.deformData(moving, outputBox='same')\n\nprint('moving translation')\ntranslateData(movingTrans, translationInMM=translation)\nprint('moving rotation')\nrotateData(movingRot, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same')\n# movingRot = resampleImage3DOnImage3D(movingRot, fixedImage=fixed, fillValue=-1000)\nprint('moving both')\ntranslateData(movingBoth, translationInMM=translation, outputBox='same')\nrotateData(movingBoth, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same')\n\ny_slice = 10\n\nfig, ax = plt.subplots(1, 6)\nax[0].set_title('fixed')\nax[0].imshow(fixed.imageArray[:, y_slice, :])\nax[0].set_xlabel(f\"{fixed.origin}\\n{fixed.spacing}\\n{fixed.gridSize}\")\n\nax[1].set_title('translateData')\nax[1].imshow(movingTrans.imageArray[:, y_slice, :])\nax[1].set_xlabel(f\"{movingTrans.origin}\\n{movingTrans.spacing}\\n{movingTrans.gridSize}\")\n\nax[2].set_title('rotateData')\nax[2].imshow(movingRot.imageArray[:, y_slice, :])\nax[2].set_xlabel(f\"{movingRot.origin}\\n{movingRot.spacing}\\n{movingRot.gridSize}\")\n\nax[3].set_title('both')\nax[3].imshow(movingBoth.imageArray[:, y_slice, :])\nax[3].set_xlabel(f\"{movingBoth.origin}\\n{movingBoth.spacing}\\n{movingBoth.gridSize}\")\n\nax[4].set_title('transform3D')\nax[4].imshow(moving.imageArray[:, y_slice, :])\nax[4].set_xlabel(f\"{moving.origin}\\n{moving.spacing}\\n{moving.gridSize}\")\n\nax[5].set_title('transform3D-both')\nax[5].imshow(moving.imageArray[:, y_slice, :] - movingBoth.imageArray[:, y_slice, :])\n\nplt.savefig(os.path.join(output_path, 'ExampleTransform3D.png'))\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a dynamic model with the transform\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(' --------------------- start test with model -----------------------------')\n\nCT4D = createSynthetic4DCT(numberOfPhases=4)\n# GENERATE MIDP\nfixedDynMod = Dynamic3DModel()\nfixedDynMod.computeMidPositionImage(CT4D, 0, tryGPU=True)\n\nprint(fixedDynMod.midp.origin, fixedDynMod.midp.spacing, fixedDynMod.midp.gridSize)\nprint('Resample model image')\nfixedDynMod = resample(fixedDynMod, gridSize=(80, 50, 50))\nprint('after resampling', fixedDynMod.midp.origin, fixedDynMod.midp.spacing, fixedDynMod.midp.gridSize)\n\n# option 3\nfor field in fixedDynMod.deformationList:\n print('Resample model field')\n field.resample(spacing=fixedDynMod.midp.spacing, gridSize=fixedDynMod.midp.gridSize, origin=fixedDynMod.midp.origin)\n print('after resampling', field.origin, field.spacing, field.gridSize)\n\nshowModelWithAnimatedFields(fixedDynMod)\n\nmovingDynMod = copy.copy(fixedDynMod)\n\nrotateData(movingDynMod, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same')\n\nshowModelWithAnimatedFields(movingDynMod)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate synthetic input images\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fixed = CTImage()\nfixed.imageArray = np.full((20, 20, 20), -1000)\ny_slice = 10\n\npointList = [[15, y_slice, 15], [15, y_slice, 10], [12, y_slice, 12], [10, y_slice, 10]]\nfor point in pointList:\n fixed.imageArray[point[0], point[1], point[2]] = 200\n\nfieldFixed = VectorField3D()\nfieldFixed.imageArray = np.zeros((20, 20, 20, 3))\nvectorList = [np.array([2, 3, 4]), np.array([0, 3, 4]), np.array([7, 3, 3]), np.array([2, 0, 0])]\nfor pointIdx in range(len(pointList)):\n fieldFixed.imageArray[pointList[pointIdx][0], pointList[pointIdx][1], pointList[pointIdx][2]] = vectorList[\n pointIdx]\n\nmoving = copy.copy(fixed)\nfieldMoving = copy.copy(fieldFixed)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a transform 3D\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print('Create a transform 3D')\ntransform3D = Transform3D()\ntransform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation)\ntransform3D.setCenter(rotCenter)\nprint('Translation', transform3D.getTranslation())\nprint('Rotation', transform3D.getRotationAngles(inDegrees=True))\n\nprint('moving with transform3D')\nmoving = transform3D.deformData(moving, outputBox='same')\nfieldMoving = transform3D.deformData(fieldMoving, outputBox='same')\nmoving = resampleImage3DOnImage3D(moving, fixedImage=fixed, fillValue=-1000)\nprint('fixed.origin', fixed.origin, 'moving.origin', moving.origin)\nfieldMoving = resampleImage3DOnImage3D(fieldMoving, fixedImage=fixed, fillValue=0)\nprint('fieldFixed.origin', fieldFixed.origin, 'fieldMoving.origin', fieldMoving.origin)\n\nprint('ici ', fieldMoving.imageArray[10, y_slice, 10])\n\ncompXFixed = fieldFixed.imageArray[:, y_slice, :, 0]\ncompZFixed = fieldFixed.imageArray[:, y_slice, :, 2]\ncompXMoving = fieldMoving.imageArray[:, y_slice, :, 0]\ncompZMoving = fieldMoving.imageArray[:, y_slice, :, 2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Display results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(1, 2)\nax[0].imshow(fixed.imageArray[:, y_slice, :])\nax[0].quiver(compZFixed, compXFixed, alpha=0.5, color='red', angles='xy', scale_units='xy', scale=2, width=.010)\nax[1].imshow(moving.imageArray[:, y_slice, :])\nax[1].quiver(compZMoving, compXMoving, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010)\nplt.show()\n\n\nprint('start ROIMask test')\nfixedMask = ROIMask.fromImage3D(fixed)\nfixedMask.imageArray = np.zeros(fixedMask.gridSize).astype(bool)\nfixedMask.imageArray[12:15, 8:12, 8:18] = True\nplt.figure()\nplt.imshow(fixedMask.imageArray[:, y_slice, :])\nplt.show()\n\nprint(fixedMask.origin, fixedMask.gridSize, fixedMask.spacing, fixedMask.imageArray.dtype)\n\nmovingMask = copy.copy(fixedMask)\nmovingMask = transform3D.deformData(movingMask, outputBox='same')\n\nprint(movingMask.origin, movingMask.gridSize, movingMask.spacing, movingMask.imageArray.dtype)\n\nplt.figure()\nplt.subplot(1, 2, 1)\nplt.imshow(fixedMask.imageArray[:, y_slice, :])\nplt.subplot(1, 2, 2)\nplt.imshow(movingMask.imageArray[:, y_slice, :])\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/multiplePythonEnv/backAndForthChild.ipynb b/auto_examples/multiplePythonEnv/backAndForthChild.ipynb new file mode 100644 index 0000000..d57b4c4 --- /dev/null +++ b/auto_examples/multiplePythonEnv/backAndForthChild.ipynb @@ -0,0 +1,83 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Back and Forth Child\nauthor: OpenTPS team\n\nThis script is the child script that will be launched by the parent script.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport sys\nfrom multiprocessing import shared_memory\nimport matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class AIModel():\n\n def __init__(self, image=np.zeros((30, 40, 50)), name='test'):\n\n self.img = image\n self.testVariable = 0\n\n def processImage(self):\n\n self.testVariable += 1\n targetPos = [120, 100, 45]\n self.img[targetPos[0] - 10:targetPos[0] + 10, targetPos[1] - 10:targetPos[1] + 10,\n targetPos[2] - 10:targetPos[2] + 10] += 500" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "if __name__ == \"__main__\":\n\n for line in sys.stdin:\n command = line.strip()\n # print(command)\n # Process the command\n # You can perform specific tasks based on the received command\n # For example:\n if command == 'init':\n existing_shm = shared_memory.SharedMemory(name='sharedArray')\n img = np.ndarray((170, 170, 100), dtype=int, buffer=existing_shm.buf)\n aimodel = AIModel(image=img)\n ## !!! Without this print the response is never sent and the script is blocked\n print('Initialize AI Model. testVariable =', aimodel.testVariable)\n else:\n method = getattr(aimodel, command)\n method()\n ## !!! Without this print the response is never sent and the script is blocked\n print(f'Executing {command} of AI Model. testVariable =', aimodel.testVariable)\n\n # Flush the output to ensure the parent script receives it\n sys.stdout.flush()\n\n\n # print(\"Start script 2\")\n # existing_shm = shared_memory.SharedMemory(name='sharedArray')\n # arrayInScript2 = np.ndarray((30, 40, 50), dtype=float, buffer=existing_shm.buf)\n # processedArray = script2(arrayInScript2)\n # del arrayInScript2\n # existing_shm.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/multiplePythonEnv/run_backAndForthParent.ipynb b/auto_examples/multiplePythonEnv/run_backAndForthParent.ipynb new file mode 100644 index 0000000..766dee0 --- /dev/null +++ b/auto_examples/multiplePythonEnv/run_backAndForthParent.ipynb @@ -0,0 +1,248 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Back and Forth\nauthor: OpenTPS team\n\nThis example shows how two different python environements can be used together.\nA parent script launches a child script which uses a different python\nenvironement but shares the same image data. It is possible to pass commands back and forth between the two scripts.\nThe child script in this example simulates the use of an AI model. The first command passed to the child script\nis to initialise the model (the neural network structure is created and its weights are loaded for example).\nThen, later in the parent script, another command is passed to the child script\nto use the AI model, multiple times in a row if necessary.\n\nImportant to note: the code executed in the child script must end with a print to send a response, else the script is\nstuck waiting for the response\n\nKey features:\n- Use of multiple python envs in communicating scripts.\n- Share RAM memory space between 2 scripts without the need to save and load data on/from hard drives.\n- The possibility to initialise first, then later in the parent script, use the AI model.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\nimport numpy as np\nfrom multiprocessing import shared_memory\nfrom subprocess import Popen, PIPE\nfrom pathlib import Path\nimport subprocess\n\nfrom opentps.core.examples.syntheticData import createSynthetic3DCT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set the child script environnement path and child scrip file path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "childEnvPath = sys.executable # absolute path to current python\nchildScriptPath = str(Path.cwd() / \"backAndForthChild.py\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create test image to share between scripts\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ct = createSynthetic3DCT()\nsliceToShow = 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Initialize shared memory and copy image array to this space\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(ct.imageArray.shape) ## These must be either passed to the child script as arguments or fixed and known\nprint(ct.imageArray.dtype) ## These must be either passed to the child script as arguments or fixed and known\nprint(ct.imageArray.nbytes) ## These must be either passed to the child script as arguments or fixed and known\nshm = shared_memory.SharedMemory(create=True, size=ct.imageArray.nbytes, name='sharedArray')\nsharedTestArray = np.ndarray(ct.imageArray.shape, dtype=ct.imageArray.dtype, buffer=shm.buf)\nsharedTestArray[:] = ct.imageArray[:]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plot initial image\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plt.figure()\nplt.title(\"Before initialize\")\nplt.imshow(sharedTestArray[:, sliceToShow, :])\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Launch child process\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "process = Popen([childEnvPath, childScriptPath],\n stdin=PIPE, stdout=PIPE, encoding='utf-8', text=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Send the command 'init' to second process\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "process.stdin.write('init' + '\\n')\nprocess.stdin.flush()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get the response from the second script\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "response = process.stdout.readline().strip()\nprint(f'Back in script 1 after init command: Response: {response}')\n\nprint('Do something else in script 1')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plot image after init command\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ct.imageArray[:] = sharedTestArray[:]\nplt.figure()\nplt.title(\"After initialize\")\nplt.imshow(ct.imageArray[:, sliceToShow, :])\nplt.show()\n\nfor i in range(3):\n\n ## Send command 'processImage'\n process.stdin.write('processImage' + '\\n')\n process.stdin.flush()\n\n ## Get the response from the second script\n response = process.stdout.readline().strip()\n print(f'Back in script 1 after command \"processImage\": Response: {response}')\n\n ## Plot image after process image command\n ct.imageArray[:] = sharedTestArray[:]\n plt.figure()\n plt.title(\"After process image\")\n plt.imshow(ct.imageArray[:, sliceToShow, :])\n plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Close the communication\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "process.stdin.close()\nprocess.stdout.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Close the shared memory\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "shm.close()\ntry:\n shm.unlink()\nexcept FileNotFoundError:\n print(\"Shared memory already unlinked, skipping.\")\n\nprint('End of script 1')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/multiplePythonEnv/run_sharedMemoryParent.ipynb b/auto_examples/multiplePythonEnv/run_sharedMemoryParent.ipynb new file mode 100644 index 0000000..2500843 --- /dev/null +++ b/auto_examples/multiplePythonEnv/run_sharedMemoryParent.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Shared Memory \nauthor: OpenTPS team\n\nThis example shows how two different python environnements can be used together.\nA parent script launches a child script which uses a different python\nenvironement but shares the same image data.\n\nKey features:\n- The parent script launches a child script which use a different python environnement\n- Share RAM memory space between 2 scripts without the need to save and load data on/from hard drives.\n\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\nimport numpy as np\nimport subprocess\nfrom multiprocessing import shared_memory\nfrom pathlib import Path\n\nfrom opentps.core.examples.syntheticData import createSynthetic3DCT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "set the child script environnement path and child scrip file path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "script2EnvPath = sys.executable # absolute path to current python\nscript2Path = str(Path.cwd() / \"sharedMemoryChild.py\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "create test image to share between scripts\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ct = createSynthetic3DCT()\nsliceToShow = 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "initialize shared memory and copy image array to this space\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(ct.imageArray.shape) ## these must be either passed to the child script as arguments or fixed and known\nprint(ct.imageArray.dtype)\nprint(ct.imageArray.nbytes)\nshm = shared_memory.SharedMemory(create=True, size=ct.imageArray.nbytes, name='sharedArray')\nsharedTestArray = np.ndarray(ct.imageArray.shape, dtype=ct.imageArray.dtype, buffer=shm.buf)\nsharedTestArray[:] = ct.imageArray[:]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "plot image before child script call\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plt.figure()\nplt.title(\"Before subprocess\")\nplt.imshow(ct.imageArray[:, sliceToShow, :])\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call to child script\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "subprocess.call([script2EnvPath, script2Path])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copy shared memory space to test image array\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ct.imageArray[:] = sharedTestArray[:]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plot image after child script call\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plt.figure()\nplt.title(\"After subprocess\")\nplt.imshow(ct.imageArray[:, sliceToShow, :])\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Close the shared memory\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "shm.close()\ntry:\n shm.unlink()\nexcept FileNotFoundError:\n print(\"Shared memory already unlinked, skipping.\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/multiplePythonEnv/sharedMemoryChild.ipynb b/auto_examples/multiplePythonEnv/sharedMemoryChild.ipynb new file mode 100644 index 0000000..9ef3ae2 --- /dev/null +++ b/auto_examples/multiplePythonEnv/sharedMemoryChild.ipynb @@ -0,0 +1,83 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Shared Memory child \nauthor: OpenTPS team\n\nThis script is the child script that will be launched by the parent script.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nfrom multiprocessing import shared_memory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def processImage(img):\n ## This is the function that will be applied to the shared image\n targetPos = [120, 100, 45]\n img[targetPos[0] - 10:targetPos[0] + 10, targetPos[1] - 10:targetPos[1] + 10, targetPos[2] - 10:targetPos[2] + 10] = 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "if __name__ == \"__main__\":\n\n print(\"Start script 2\")\n existing_shm = shared_memory.SharedMemory(name='sharedArray')\n try:\n # Must match parent array shape and dtype\n arrayInScript2 = np.ndarray((170, 170, 100), dtype=np.int64, buffer=existing_shm.buf)\n processImage(arrayInScript2)\n finally:\n del arrayInScript2\n existing_shm.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/registration/run_exampleMorphons.ipynb b/auto_examples/registration/run_exampleMorphons.ipynb new file mode 100644 index 0000000..ef4882b --- /dev/null +++ b/auto_examples/registration/run_exampleMorphons.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Morphons Registration \nauthor: OpenTPS team\n\nThis example will present the basis of morphons registration with openTPS core.\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\nimport matplotlib.pyplot as plt\nimport time\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.data.images import CTImage\nfrom opentps.core.processing.registration.registrationMorphons import RegistrationMorphons\nfrom opentps.core.examples.syntheticData import *\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = 'Output'\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate synthetic images\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fixed_img = np.full((100, 100, 100), -1000)\nfixed_img[25:75, 25:75, 25:75] = 0\nfixed = CTImage(imageArray=fixed_img, name='fixed', origin=[0, 0, 0], spacing=[1, 1, 1])\nmoving_img = np.full((100, 100, 100), -1000)\nmoving_img[30:75, 35:75, 40:75] = 0\nmoving = CTImage(imageArray=moving_img, name='moving', origin=[0, 0, 0], spacing=[1, 1, 1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Perform the Morphons registration\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "start_time = time.time()\nreg = RegistrationMorphons(fixed, moving, baseResolution=2.0, nbProcesses=1)\ndf = reg.compute()\nprocessing_time = time.time() - start_time\nprint('Registration processing time was', processing_time, '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute the difference between the 2 images and check results of the registration\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# COMPUTE IMAGE DIFFERENCE\ndf.resampleOn(moving, fillValue=-1024.)\ndiff_before = fixed.copy()\ndiff_before._imageArray = moving.imageArray - fixed.imageArray\ndiff_after = fixed.copy()\ndiff_after._imageArray = reg.deformed.imageArray - fixed.imageArray\n\n# CHECK RESULTS\ndiff_before_sum = abs(diff_before.imageArray.sum())\ndiff_after_sum = abs(diff_after.imageArray.sum())\nassert diff_before_sum-diff_after_sum > 0, f\"Image difference is larger after registration\"\nassert abs(diff_after.imageArray[27,27,27])==0, f\"Wrong target voxel difference after registration {diff_after.imageArray[27,27,27]} (expected 0)\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(3, 3)\nvmin = -1000\nvmax = 1000\nx_slice = round(fixed.imageArray.shape[0] / 2) - 1\ny_slice = round(fixed.imageArray.shape[1] / 2) - 1\nz_slice = round(fixed.imageArray.shape[2] / 2) - 1\n\n# Plot X-Y field\nu = df.velocity.imageArray[:, :, z_slice, 0]\nv = df.velocity.imageArray[:, :, z_slice, 1]\nu[0,0] = 1\nax[0, 0].imshow(reg.deformed.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=vmin, vmax=vmax)\nax[0, 0].quiver(u.T[::1, ::1], v.T[::1, ::1], alpha=0.2, color='red', angles='xy', scale_units='xy', scale=1)\nax[0, 0].set_xlabel('x')\nax[0, 0].set_ylabel('y')\nax[0, 1].imshow(diff_before.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax)\nax[0, 1].set_xlabel('x')\nax[0, 1].set_ylabel('y')\nax[0, 2].imshow(diff_after.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax)\nax[0, 2].set_xlabel('x')\nax[0, 2].set_ylabel('y')\n\n# Plot X-Z field\ncompX = df.velocity.imageArray[:, y_slice, :, 0]\ncompZ = df.velocity.imageArray[:, y_slice, :, 2]\ncompZ[0,0] = 1\nax[1, 0].imshow(reg.deformed.imageArray[:, y_slice, :].T[::1, ::1], cmap='gray', origin='upper', vmin=vmin, vmax=vmax)\nax[1, 0].quiver(compX.T[::1, ::1], compZ.T[::1, ::1], alpha=0.2, color='red', angles='xy', scale_units='xy', scale=1)\nax[1, 0].set_xlabel('x')\nax[1, 0].set_ylabel('z')\nax[1, 1].imshow(diff_before.imageArray[:, y_slice, :].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax)\nax[1, 1].set_xlabel('x')\nax[1, 1].set_ylabel('z')\nax[1, 2].imshow(diff_after.imageArray[:, y_slice, :].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax)\nax[1, 2].set_xlabel('x')\nax[1, 2].set_ylabel('z')\n\n# Plot Y-Z field\ncompY = df.velocity.imageArray[x_slice, :, :, 1]\ncompZ = df.velocity.imageArray[x_slice, :, :, 2]\ncompZ[0,0] = 1\nax[2, 0].imshow(reg.deformed.imageArray[x_slice, :, :].T[::1, ::1], cmap='gray', origin='upper', vmin=vmin, vmax=vmax)\nax[2, 0].quiver(compY.T[::1, ::1], compZ.T[::1, ::1], alpha=0.2, color='red', angles='xy', scale_units='xy', scale=1)\nax[2, 0].set_xlabel('y')\nax[2, 0].set_ylabel('z')\nax[2, 1].imshow(diff_before.imageArray[x_slice, :, :].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax)\nax[2, 1].set_xlabel('y')\nax[2, 1].set_ylabel('z')\nax[2, 2].imshow(diff_after.imageArray[x_slice, :, :].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax)\nax[2, 2].set_xlabel('y')\nax[2, 2].set_ylabel('z')\n\nax[0, 0].title.set_text('Deformed image and deformation field')\nax[0, 1].title.set_text('Difference before registration')\nax[0, 2].title.set_text('Difference after registration')\n\nplt.savefig(os.path.join(output_path, 'Example_Morphons.png'))\n\nprint('Morphons example completed')\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/auto_examples/registration/run_exampleRigid.ipynb b/auto_examples/registration/run_exampleRigid.ipynb new file mode 100644 index 0000000..a8a438a --- /dev/null +++ b/auto_examples/registration/run_exampleRigid.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Rigid Registration \nauthor: OpenTPS team\n\nThis example will present the basis of rigid registration with openTPS core.\nrunning time: ~ 5 minutes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up the environment in google collab\n First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\nif \"google.colab\" in sys.modules:\n from IPython import get_ipython\n get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git')\n get_ipython().system('pip install ./opentps')\n get_ipython().system('pip install scipy==1.10.1')\n get_ipython().system('pip install cupy-cuda12x')\n import opentps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import copy\n\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport time\nimport logging\nimport os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import the needed opentps.core packages\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from opentps.core.processing.registration.registrationRigid import RegistrationRigid\nfrom opentps.core.examples.syntheticData import *\nfrom opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D\nfrom opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData\n\nlogger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output path\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "output_path = 'Output'\nif not os.path.exists(output_path):\n os.makedirs(output_path)\nlogger.info('Files will be stored in {}'.format(output_path))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate synthetic images\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fixed = createSynthetic3DCT()\nmoving = copy.copy(fixed)\n\ntranslation = np.array([15, 0, 10])\nrotation = np.array([0, 5, 2])\n\ntranslateData(moving, translation, outputBox='same')\nrotateData(moving, rotation, outputBox='same')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Perform the Rigid registration\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "start_time = time.time()\nreg = RegistrationRigid(fixed, moving)\ntransform = reg.compute()\n\nprocessing_time = time.time() - start_time\nprint('Registration processing time was', processing_time, '\\n')\nprint('Translation', transform.getTranslation())\nprint('Rotation in deg', transform.getRotationAngles(inDegrees=True), '\\n')\n\n## Two ways of getting the deformed moving image\ndeformedImage = reg.deformed\n# deformedImage = transform.deformImage(moving)\n\n## Resample it to the same grid as the fixed image\nresampledOnFixedGrid = resampleImage3DOnImage3D(deformedImage, fixedImage=fixed, fillValue=-1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute the difference between the 2 images and check results of the registration\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# COMPUTE IMAGE DIFFERENCE\ndiff_before = fixed.copy()\ndiff_before._imageArray = fixed.imageArray - moving.imageArray\ndiff_after = fixed.copy()\ndiff_after._imageArray = fixed.imageArray - resampledOnFixedGrid.imageArray\n\n# CHECK RESULTS\ndiff_before_sum = abs(diff_before.imageArray).sum()\ndiff_after_sum = abs(diff_after.imageArray).sum()\nassert diff_before_sum - diff_after_sum > 0, f\"Image difference is larger after registration\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot results\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "y_slice = 95\nfig, ax = plt.subplots(2, 3)\nax[0, 0].imshow(fixed.imageArray[:, y_slice, :])\nax[0, 0].set_title('Fixed')\nax[0, 0].set_xlabel('Origin: '+f'{fixed.origin[0]}'+','+f'{fixed.origin[1]}'+','+f'{fixed.origin[2]}')\nax[0, 1].imshow(moving.imageArray[:, y_slice, :])\nax[0, 1].set_title('Moving')\nax[0, 1].set_xlabel('Origin: ' + f'{moving.origin[0]}' + ',' + f'{moving.origin[1]}' + ',' + f'{moving.origin[2]}')\ndiffBef = ax[0, 2].imshow(diff_before.imageArray[:, y_slice, :], vmin=-2000, vmax=2000)\nax[0, 2].set_title('Diff before')\nfig.colorbar(diffBef, ax=ax[0, 2])\nax[1, 0].imshow(deformedImage.imageArray[:, y_slice, :])\nax[1, 0].set_title('DeformedMoving')\nax[1, 0].set_xlabel('Origin: ' + f'{deformedImage.origin[0]:.1f}' + ',' + f'{deformedImage.origin[1]:.1f}' + ',' + f'{deformedImage.origin[2]:.1f}')\nax[1, 1].imshow(resampledOnFixedGrid.imageArray[:, y_slice, :])\nax[1, 1].set_title('resampledOnFixedGrid')\nax[1, 1].set_xlabel('Origin: ' + f'{resampledOnFixedGrid.origin[0]:.1f}' + ',' + f'{resampledOnFixedGrid.origin[1]:.1f}' + ',' + f'{resampledOnFixedGrid.origin[2]:.1f}')\ndiffAft = ax[1, 2].imshow(diff_after.imageArray[:, y_slice, :], vmin=-2000, vmax=2000)\nax[1, 2].set_title('Diff after')\nfig.colorbar(diffAft, ax=ax[1, 2])\nplt.savefig(os.path.join(output_path, 'Example_Registration.png'))\n\nprint('Rigid registration example completed')\nplt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/community/CommunityExample/README.rst b/community/CommunityExample/README.rst new file mode 100644 index 0000000..ef31ab4 --- /dev/null +++ b/community/CommunityExample/README.rst @@ -0,0 +1,2 @@ +Community Examples +------------------ \ No newline at end of file diff --git a/community/CommunityExample/SimpleDoseComputationAndOptOnCT.py b/community/CommunityExample/SimpleDoseComputationAndOptOnCT.py new file mode 100644 index 0000000..3183b07 --- /dev/null +++ b/community/CommunityExample/SimpleDoseComputationAndOptOnCT.py @@ -0,0 +1,318 @@ +''' +Simple dose computation and optimization on a real CT image +=========================================================== +author: Eliot Peeters + +In this example we are going to see how to : + +- Import real dicom images and RT struct +- Create a plan +- Compute beamlets +- Optimize a plan with beamlets +- Save a plan and beamlets +- Compute DVH histograms +''' + +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import numpy as np +import os +from matplotlib import pyplot as plt + +#%% +#import the needed opentps.core packages + +from opentps.core.data.plan import ProtonPlanDesign +from opentps.core.data import DVH +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.io.dataLoader import readData +from opentps.core.data.plan import ObjectivesList +from opentps.core.data.plan import FidObjective +from opentps.core.io.serializedObjectIO import saveBeamlets, saveRTPlan, loadBeamlets, loadRTPlan + +#%% +#In the next cell we configure the CT scan model used for the dose calculation and the bdl model. The ones used in this example are the default configuration of openTPS wich may lead to some imprecision. + +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#%% +#Data importation +#---------------- +#The dataset used in this example comes from the `Proknow website `_, 2018 TROG Plan Study: SRS Brain. The readData functions automatically import the subfolders and detects the type of data (CT or RT_struct). + +ctImagePath = "Path_to_\ProKnows_2018_TROG_Plan_Study_SRS_Brain" #The folder is initially named 'data' +data = readData(ctImagePath) + +rt_struct = data[0] +ct = data[1] + +rt_struct.print_ROINames() + +#%% +#For the purpose of the demonstration we are going to use only 3 different ROI. Note that it is important to specify the CT origin,gridSize and spacing to the getBinaryMask function in order to have a correct binary mask. + +target_name = "GTV4-20Gy" +target = rt_struct.getContourByName(target_name).getBinaryMask(origin=ct.origin,gridSize=ct.gridSize,spacing=ct.spacing) + +OAR_brain = rt_struct.getContourByName("Brain").getBinaryMask(origin=ct.origin,gridSize=ct.gridSize,spacing=ct.spacing) +OAR_brainstem = rt_struct.getContourByName("Brainstem").getBinaryMask(origin=ct.origin,gridSize=ct.gridSize,spacing=ct.spacing) +OAR_optic_chiasm = rt_struct.getContourByName("Optic Chiasm").getBinaryMask(origin=ct.origin,gridSize=ct.gridSize,spacing=ct.spacing) + +#%% +#For further plots we can extract the indexes of the centerOfMass of the tumor. + +COM_coord = target.centerOfMass +COM_index = target.getVoxelIndexFromPosition(COM_coord) +Z_COORD = COM_index[2] + +#%% +#MCsquare configuration +#---------------------- +#We now initialize a MCsquareDoseCalculator and provide the beamModel and ctCalibration imported above. + +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.ctCalibration = ctCalibration + +#%% +#Plan design +#----------- +# +# In the next section we create a planDesign object with 3 beams (of no medical relevance, we just use them for demonstration). There are multiple parameters which can affect computation time : +# * targetMargin : a higher margin will increase the time used to dilate the mask +# * spotSpacing : a lower spot spacing will result in more beamlets therefore longer beamlets calculation time +# * layerSpacing : a lower layer spacing will result in more beamlets therefore longer beamlets calculation time + +# Design plan +beamNames = ["Beam1","Beam2","Beam3"] +gantryAngles = [0.,45.,315.] +couchAngles = [0.,0.,0.] + +# Generate new plan +planDesign = ProtonPlanDesign() +planDesign.ct = ct +planDesign.gantryAngles = gantryAngles +planDesign.targetMask = target +planDesign.beamNames = beamNames +planDesign.couchAngles = couchAngles +planDesign.calibration = ctCalibration +planDesign.spotSpacing = 5.0 +planDesign.layerSpacing = 5.0 +planDesign.targetMargin = 5.0 + +plan = planDesign.buildPlan() # Spot placement +plan.PlanName = "NewPlan" + +#%% +#Beamlets computation and initial dose computation +#------------------------------------------------- +#In the next section we compute the beamlets (this is the most computer-intensive part). We have set the numbers of protons to 5e4. + +mc2.nbPrimaries = 5e4 +beamlets = mc2.computeBeamlets(ct, plan) +plan.planDesign.beamlets = beamlets + +#%% +#After the beamlets computation we can save the plan and the beamlets to reuse them in the future. +#WARNING : the saveRTPlan function automatically remove the beamlets from the memory, if you want to save the beamlets, you have to call the saveBeamlets function before. Those files can be heavy ! +#Afterward you can load the plan and the beamlets via the loadRTPlan and loadBeamlets functions. + +#Output path +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) + +# Save the plan and the beamlets +saveBeamlets(beamlets, os.path.join(output_path, "SimpleRealDoseComputationOptimization_beamlets.blm")) +saveRTPlan(plan, os.path.join(output_path,"SimpleRealDoseComputationOptimization_plan.tps")) +plan = loadRTPlan(os.path.join(output_path,"SimpleRealDoseComputationOptimization_plan.tps")) +plan.planDesign.beamlets = loadBeamlets(os.path.join(output_path,"SimpleRealDoseComputationOptimization_beamlets.blm")) + +#%% +#Note that in the next cell we have augmented the number of protons for the dose computation (computeDose) to have a more accurate dose. This dose is computed with all the weights of the beamlets set to 1. +mc2.nbPrimaries = 1e7 +dose_before_opti = mc2.computeDose(ct,plan) + +#%% +#Plan optimization +#----------------- +#We will now optimize the plan with and without OAR to compare the differences. We first create an ObjectivesList and then add objectives via the addFidObjective which can be either DMIN, DMAX or DMEAN. Note that you can also create other objectives and implement them via the addExoticObjective fucntion. + +plan.planDesign.objectives = ObjectivesList() #create a new objective set +plan.planDesign.objectives.setTarget(target.name, 20.0) #setting a target of 20 Gy for the target +plan.planDesign.objectives.fidObjList = [] +plan.planDesign.objectives.addFidObjective(target, FidObjective.Metrics.DMAX, 19.5, 1.0) +plan.planDesign.objectives.addFidObjective(target, FidObjective.Metrics.DMIN, 20.5, 1.0) +plan.planDesign.defineTargetMaskAndPrescription() + +#%% +#We will use the Scipy-LBFGS as solver for this example but other are also implemented such as : +#- Scipy-BFGS +#- Scipy-LBFGS +#- Gradient +#- BFGS +#- LBFGS +#- FISTA +#- LP +#Feel also free to specify a maxit to the IMPTPlanOptimizer object to speed up the program. + +from opentps.core.processing.planOptimization.planOptimization import IMPTPlanOptimizer + +solver = IMPTPlanOptimizer(method='Scipy-LBFGS',plan=plan) +w, doseImage, ps = solver.optimize() +plan.spotMUs = np.square(w).astype(np.float32) + +#%% +#We can now recompute the dose. + +doseImage_opti = mc2.computeDose(ct,plan) + +#%% +#We here reload the plan in order to reset all weights and the filtering (after optimization, spots that are bellow the solver.thresholdSpotRemoval and corresponding weights are removed). + +plan = loadRTPlan(os.path.join(output_path,"SimpleRealDoseComputationOptimization_plan.tps")) +plan.planDesign.beamlets = loadBeamlets(os.path.join(output_path,"SimpleRealDoseComputationOptimization_beamlets.blm")) +plan.planDesign.objectives = ObjectivesList() #create a new objective set +plan.planDesign.objectives.setTarget(target.name, 20.0) #setting a target of 20 Gy for the target +plan.planDesign.objectives.fidObjList = [] +plan.planDesign.objectives.addFidObjective(target, FidObjective.Metrics.DMAX, 20.5, 1.0) +plan.planDesign.objectives.addFidObjective(target, FidObjective.Metrics.DMIN, 19.5, 1.0) +plan.planDesign.objectives.addFidObjective(OAR_brain, FidObjective.Metrics.DMAX, 8.0, 1.0) +plan.planDesign.objectives.addFidObjective(OAR_brainstem, FidObjective.Metrics.DMAX, 5.0, 1.0) +plan.planDesign.objectives.addFidObjective(OAR_optic_chiasm, FidObjective.Metrics.DMAX, 2.0, 1.0) +plan.planDesign.defineTargetMaskAndPrescription() + +solver = IMPTPlanOptimizer(method='Scipy-LBFGS',plan=plan) +doseImage, ps = solver.optimize() + +doseImage_opti_OAR = mc2.computeDose(ct,plan) + +#%% +#DVH histograms +#-------------- +#We can create simple DVH plots with the DVH objects. Take a look at the class properties to find the D95, … + +target_DVH = DVH(target,doseImage_opti_OAR) +target_DVH_No_OAR = DVH(target,doseImage_opti) +brain_DVH = DVH(OAR_brain,doseImage_opti_OAR) +brainstem_DVH = DVH(OAR_brainstem,doseImage_opti_OAR) +optic_chiasm_DVH = DVH(OAR_optic_chiasm,doseImage_opti_OAR) + +#%% +#Final plots +#----------- +#Now that the different optimizations are done, we can display the results. +from skimage.transform import resize +image_ct_axial = ct.imageArray[:,:,Z_COORD].transpose(1,0) +image_target_axial = target.imageArray[:,:,Z_COORD].transpose(1,0) +image_brain_axial = OAR_brain.imageArray[:,:,Z_COORD].transpose(1,0) +image_brainstem_axial = OAR_brainstem.imageArray[:,:,Z_COORD].transpose(1,0) +image_optic_chiasm_axial = OAR_optic_chiasm.imageArray[:,:,Z_COORD].transpose(1,0) +image_dose_before_opti_axial = dose_before_opti.imageArray[:,:,Z_COORD].transpose(1,0) +image_dose_opti_axial = doseImage_opti.imageArray[:,:,Z_COORD].transpose(1,0) +image_dose_opti_OAR_axial = doseImage_opti_OAR.imageArray[:,:,Z_COORD].transpose(1,0) + +image_ct_sagital = resize(np.rot90(ct.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1])) +image_target_sagital = resize(np.rot90(target.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1])) +image_brain_sagital = resize(np.rot90(OAR_brain.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1])) +image_brainstem_sagital = resize(np.rot90(OAR_brainstem.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1])) +image_optic_chiasm_sagital = resize(np.rot90(OAR_optic_chiasm.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1])) +image_dose_before_opti_sagital = resize(np.rot90(dose_before_opti.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1])) +image_dose_opti_sagital = resize(np.rot90(doseImage_opti.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1])) +image_dose_opti_OAR_sagital = resize(np.rot90(doseImage_opti_OAR.imageArray[COM_index[0],:,:]),(ct.gridSize[:-1])) + +min_dose_val = 0.1 +image_dose_before_opti_axial[image_dose_before_opti_axial <= min_dose_val] = np.nan +image_dose_opti_axial[image_dose_opti_axial <= min_dose_val] = np.nan +image_dose_opti_OAR_axial[image_dose_opti_OAR_axial <= min_dose_val] = np.nan +image_dose_before_opti_sagital[image_dose_before_opti_sagital <= min_dose_val] = np.nan +image_dose_opti_sagital[image_dose_opti_sagital <= min_dose_val] = np.nan +image_dose_opti_OAR_sagital[image_dose_opti_OAR_sagital <= min_dose_val] = np.nan + +vmin=0 +vmax=22 + +fig, ax = plt.subplots(3,3,figsize=(12,12)) + +ax[0,0].imshow(image_ct_axial,cmap="gray") +ax[0,0].contour(image_target_axial,colors="blue") +ax[0,0].contour(image_brain_axial,colors="red") +ax[0,0].contour(image_brainstem_axial,colors="green") +ax[0,0].contour(image_optic_chiasm_axial,colors="orange") +ax[0,0].axis("off") +ax[0,0].set_title("Base CT - Axial vue") + +ax[0,1].imshow(image_ct_sagital,cmap="gray") +ax[0,1].contour(image_target_sagital,colors="blue") +ax[0,1].contour(image_brain_sagital,colors="red") +ax[0,1].contour(image_brainstem_sagital,colors="green") +ax[0,1].contour(image_optic_chiasm_sagital,colors="orange") +ax[0,1].axis("off") +ax[0,1].set_title("Base CT - Sagital vue") + +ax[0,2].plot(target_DVH.histogram[0],target_DVH.histogram[1],label=target_DVH.name + " with OAR",color="blue") +ax[0,2].plot(brain_DVH.histogram[0],brain_DVH.histogram[1],label=brain_DVH.name, color="red") +ax[0,2].plot(brainstem_DVH.histogram[0],brainstem_DVH.histogram[1],label=brainstem_DVH.name, color="green") +ax[0,2].plot(optic_chiasm_DVH.histogram[0],optic_chiasm_DVH.histogram[1],label=optic_chiasm_DVH.name, color="orange") +ax[0,2].plot(target_DVH_No_OAR.histogram[0],target_DVH_No_OAR.histogram[1],label=target_DVH.name + " wihtout OAR",color="blue",linestyle="dashed") +ax[0,2].set_xlim(0,25) +ax[0,2].grid(True) +ax[0,2].legend() +ax[0,2].set_title("DVH after optimization") + +ax[1,0].imshow(image_ct_axial,cmap="gray") +dose_bar_ref = ax[1,0].imshow(image_dose_before_opti_axial,cmap="jet",alpha=.5,vmin=vmin,vmax=vmax) +ax[1,0].contour(image_target_axial,colors="black") +ax[1,0].set_title("Dose before optimization") +ax[1,0].axis("off") + +ax[1,1].imshow(image_ct_axial,cmap="gray") +ax[1,1].imshow(image_dose_opti_axial,cmap="jet",alpha=.5,vmin=vmin,vmax=vmax) +ax[1,1].contour(image_target_axial,colors="black") +ax[1,1].set_title("Dose after optimization without OAR") +ax[1,1].axis("off") + +ax[1,2].imshow(image_ct_axial,cmap="gray") +ax[1,2].imshow(image_dose_opti_OAR_axial,cmap="jet",alpha=.5,vmin=vmin,vmax=vmax) +ax[1,2].contour(image_target_axial,colors="black") +ax[1,2].set_title("Dose after optimization with OAR") +ax[1,2].axis("off") + +ax[2,0].imshow(image_ct_sagital,cmap="gray") +ax[2,0].imshow(image_dose_before_opti_sagital,cmap="jet",alpha=.5,vmin=vmin,vmax=vmax) +ax[2,0].contour(image_target_sagital,colors="black") +ax[2,0].set_title("Dose before optimization") +ax[2,0].axis("off") + +ax[2,1].imshow(image_ct_sagital,cmap="gray") +ax[2,1].imshow(image_dose_opti_sagital,cmap="jet",alpha=.5,vmin=vmin,vmax=vmax) +ax[2,1].contour(image_target_sagital,colors="black") +ax[2,1].set_title("Dose after optimization without OAR") +ax[2,1].axis("off") + +ax[2,2].imshow(image_ct_sagital,cmap="gray") +ax[2,2].imshow(image_dose_opti_OAR_sagital,cmap="jet",alpha=.5,vmin=vmin,vmax=vmax) +ax[2,2].contour(image_target_sagital,colors="black") +ax[2,2].set_title("Dose after optimization with OAR") +ax[2,2].axis("off") +cb_ax = fig.add_axes([0.1, 0.08, 0.8, 0.02]) +fig.colorbar(dose_bar_ref,cax=cb_ax,location="bottom") +plt.savefig(os.path.join(output_path, "SimpleRealDoseComputationOptimization_output.png")) +plt.close() \ No newline at end of file diff --git a/community/README.rst b/community/README.rst new file mode 100644 index 0000000..50338ee --- /dev/null +++ b/community/README.rst @@ -0,0 +1,48 @@ +Community Examples +================== + +This gallery contains contributions from the OpenTPS community. +It is a place to share small scripts that may help others. + +Quick start: **Clone → Create a branch → Add your script → Open a Pull Request** + +How to Contribute +----------------- + +Follow these steps to contribute an example to the gallery: + +1. **Clone the repository** + from GitHub: + https://github.com/OpenTPS/examples.git + +2. **Create a new branch** + + .. code-block:: bash + + git checkout -b my-example + +3. **Add your example script** + + - Place your file in: `examples/community/CommunityExample/` + - Use a clear, descriptive file name. + - Start the file with a short docstring explaining what it does. + - You can copy and adapt the template: `examples/community/Template/template.py` + +4. **Commit your changes** + + .. code-block:: bash + + git add examples/community/CommunityExample/my_example.py + git commit -m "Add community example: my_example" + +5. **Push your branch to GitHub** + + .. code-block:: bash + + git push origin my-example + +6. **Open a Pull Request** + + - Go to GitHub and open a Pull Request from your branch into `main`. + - Maintainers will review your code. + - Once approved, your example will appear in the Community Gallery. diff --git a/community/Template/README.rst b/community/Template/README.rst new file mode 100644 index 0000000..662c046 --- /dev/null +++ b/community/Template/README.rst @@ -0,0 +1,2 @@ +Template +-------- \ No newline at end of file diff --git a/community/Template/run_template.py b/community/Template/run_template.py new file mode 100644 index 0000000..c12fefc --- /dev/null +++ b/community/Template/run_template.py @@ -0,0 +1,42 @@ +""" +Template Example +================ + +This is a minimal example showing how to contribute to the +Community Gallery. It just generates some random data and plots it. +""" + +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps +#%% +#imports +import numpy as np +import matplotlib.pyplot as plt + +#%% +# Step 1. Generate random data +# ------------------------------- +x = np.linspace(0, 10, 100) +y = np.sin(x) + 0.2 * np.random.randn(100) + +#%% +# Step 2. Plot the results +# ------------------------------- +# To display your plot in the Sphinx gallery, make sure plt.show() is the last line of your code cell. +plt.figure() +plt.plot(x, y, label="noisy sine") +plt.legend() +plt.title("Random sine example") +plt.xlabel("x") +plt.ylabel("y") +plt.show() diff --git a/conf.py b/conf.py index 43190a8..b13d332 100644 --- a/conf.py +++ b/conf.py @@ -6,7 +6,7 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'OpenTPS' +project = 'OpenTPS examples' copyright = '2024, OpenTPS team' author = 'OpenTPS team' @@ -19,11 +19,19 @@ sphinx_gallery_conf = { - # Path to your examples directory - 'examples_dirs': 'examples', # Path to your example scripts - 'gallery_dirs': 'auto_examples', # Path where the gallery will be generated + "examples_dirs": [ + "examples", # your main examples + "community", # new folder for community contributions + ], + "gallery_dirs": [ + "auto_examples", # output folder for main gallery + "auto_community", # output folder for community gallery + ], + 'filename_pattern': r'run_.*\.py$', # matches any file starting with 'run_' + 'ignore_pattern': r'__init__\.py', # optional: ignore __init__.py } + # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output @@ -31,7 +39,14 @@ html_static_path = ['_static'] templates_path = ['_templates'] +html_logo = "_static/OpenTPS_logo_dark_big.png" # path relative to docs folder + html_theme_options = { 'repository_url': 'https://github.com/OpenTPS/examples', "article_header_end": "my_header.html", + "show_nav_level": 2, + "logo": { + "text": "OpenTPS examples", # text next to the logo + "image_light": "_static/OpenTPS_logo_dark_big.png", # logo for light mode + }, } \ No newline at end of file diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000..ce5c188 Binary files /dev/null and b/examples/.DS_Store differ diff --git a/examples/DICOMExample/README.rst b/examples/DICOMExample/README.rst new file mode 100644 index 0000000..acffceb --- /dev/null +++ b/examples/DICOMExample/README.rst @@ -0,0 +1,2 @@ +DICOM Examples +-------------- \ No newline at end of file diff --git a/examples/DICOMExample/create3DSeqFromDicom.py b/examples/DICOMExample/create3DSeqFromDicom.py new file mode 100644 index 0000000..36220a6 --- /dev/null +++ b/examples/DICOMExample/create3DSeqFromDicom.py @@ -0,0 +1,61 @@ +''' +Create 3D Sequence from DICOM +============================= +author: OpenTPS team + +This example shows how to read data from a 4DCT folder, create a dynamic 3D sequence with the 4DCT data, and save this sequence in serialized format on the drive. + +running time: ~ 10 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +from pathlib import Path +import sys + +#%% +#import the needed opentps.core packages + +from opentps.core.io.dataLoader import readData +from opentps.core.data.dynamicData._dynamic3DSequence import Dynamic3DSequence +from opentps.core.io.serializedObjectIO import saveSerializedObjects + +#%% +# Get the current working directory, its parent, then add the testData folder at the end of it +testDataPath = os.path.join(Path(os.getcwd()).parent.absolute(), 'testData/') + +#%% +## read a serialized dynamic sequence +dataPath = 'Path_to_your_4DCT_data/' # replace with the path to your 4DCT data folder + +print('Datas present in ' + dataPath + 'are loaded.') +dataList = readData(dataPath) +print(len(dataList), 'images found in the folder') +print('Image type =', type(dataList[0])) + +#%% +# create a Dynamic3DSequence and change its name +dynseq = Dynamic3DSequence(dyn3DImageList=dataList) +print('Type of the created object =', type(dynseq)) +print('Sequence name =', dynseq.name) +dynseq.name = 'Light4DCT' +print('Sequence name = ', dynseq.name) +print('Sequence lenght =', len(dynseq.dyn3DImageList)) + +#%% +# save it as a serialized object +savingPath = testDataPath + 'lightDynSeq' +saveSerializedObjects(dynseq, savingPath) diff --git a/examples/DICOMExample/create3DSeqFromImages.py b/examples/DICOMExample/create3DSeqFromImages.py new file mode 100644 index 0000000..2ffc0be --- /dev/null +++ b/examples/DICOMExample/create3DSeqFromImages.py @@ -0,0 +1,62 @@ +''' +Create 3D Sequence from Images +============================== +author: OpenTPS team + +This example shows how to read data from a 4DCT folder, create a dynamic 3D sequence with the 4DCT data, and save this sequence in serialized format on the drive. + +running time: ~ 10 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +from pathlib import Path +import sys + +#%% +#import the needed opentps.core packages +from opentps.core.io.dataLoader import readData +from opentps.core.data.dynamicData._dynamic3DSequence import Dynamic3DSequence +from opentps.core.io.serializedObjectIO import saveSerializedObjects + +#%% +# Get the current working directory, its parent, then add the testData folder at the end of it +testDataPath = os.path.join(Path(os.getcwd()).parent.absolute(), 'testData/') + +#%% +# read a serialized dynamic sequence + +dataPath = 'Path_to_your_4DCT_data/' # replace with the path to your 4DCT data folder + +print('Datas present in ' + dataPath + 'are loaded.') +dataList = readData(dataPath) +print(len(dataList), 'images found in the folder') +print('Image type =', type(dataList[0])) + +#%% +# create a Dynamic3DSequence and change its name + +dynseq = Dynamic3DSequence(dyn3DImageList=dataList) +print('Type of the created object =', type(dynseq)) +print('Sequence name =', dynseq.name) +dynseq.name = 'Light4DCT' +print('Sequence name = ', dynseq.name) +print('Sequence lenght =', len(dynseq.dyn3DImageList)) + +#%% +# save it as a serialized object +savingPath = testDataPath + 'lightDynSeq' +saveSerializedObjects(dynseq, savingPath) diff --git a/examples/DICOMExample/createDynamic3DModelFromDicomFields.py b/examples/DICOMExample/createDynamic3DModelFromDicomFields.py new file mode 100644 index 0000000..eec5888 --- /dev/null +++ b/examples/DICOMExample/createDynamic3DModelFromDicomFields.py @@ -0,0 +1,61 @@ +''' +Create Dynamic 3D Model from DICOM Fields +========================================= +author: OpenTPS team + +This example shows how to read a DICOM CT and deformation fields, create a dynamic 3D model with the mid-position CT and the deformation fields, and print the model information. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import sys + +#%% +#import the needed opentps.core packages +from opentps.core.io.dataLoader import readData +from opentps.core.data.images._deformation3D import Deformation3D +from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel + +#%% +# Load DICOM CT +inputPaths = 'Path_to_your_CT_data/' # replace with the path to your CT data folder +dataList = readData(inputPaths, maxDepth=0) +midP = dataList[0] +print(type(midP)) + +#%% +# Load DICOM Deformation Fields +inputPaths = 'Path_to_your_deformation_fields/' # replace with the path to your deformation fields folder +defList = readData(inputPaths, maxDepth=0) + +#%% +# Transform VectorField3D to deformation3D +deformationList = [] +for df in defList: + df2 = Deformation3D() + df2.initFromVelocityField(df) + deformationList.append(df2) +del defList +print(deformationList) + +patient_name = 'OpenTPS_Patient' + +#%% +# Create Dynamic 3D Model +model3D = Dynamic3DModel(name=patient_name, midp=midP, deformationList=deformationList) +print(model3D) \ No newline at end of file diff --git a/examples/DICOMExample/createModelWithROIsFromDicomImages.py b/examples/DICOMExample/createModelWithROIsFromDicomImages.py new file mode 100644 index 0000000..a7cd8b0 --- /dev/null +++ b/examples/DICOMExample/createModelWithROIsFromDicomImages.py @@ -0,0 +1,107 @@ +''' +Create Model with ROIs from DICOM Images +======================================== +author: OpenTPS team + +This example shows how to: +- read dicom data from a 4DCT folder +- create a dynamic 3D sequence with the 4DCT data +- read an rtStruct dicom file +- create a dynamic 3D model and compute the midP image with the dynamic 3D sequence +- create a patient, give him the model and rtStruct and save it as serialized data +!!! does not work with public data for now since there is no struct in the public data !!! + +running time: ~ 10 minutes + +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import sys +import time +import numpy as np + +#%% +#import the needed opentps.core packages +from pydicom.uid import generate_uid +from opentps.core.io.dataLoader import readData +from opentps.core.data.dynamicData._dynamic3DSequence import Dynamic3DSequence +from opentps.core.io.serializedObjectIO import saveSerializedObjects +from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel +from opentps.core.data._patient import Patient + +#%% +# chose the patient folder, which will be used as the patient name +patientName = 'Patient_0' + +#%% +# chose the 4DCT data folder +data4DPath = 'Path_to_your_4DCT_data/' # replace with the path to your 4DCT data folder +# chose the dicom rtStruct file +dataStructPath = 'Path_to_your_rtStruct_data/' # replace with the path to your rtStruct data folder +# chose a path to save the results +savingPath = 'Path_to_your_saving_path/' # replace with the path where you want to save the results +#%% +# load the 4DCT data +data4DList = readData(data4DPath) +print(len(data4DList), 'images found in the folder') +print('Image type =', type(data4DList[0])) +print('Image 0 shape =', data4DList[0].gridSize) + +# create a Dynamic3DSequence and change its name +dynSeq = Dynamic3DSequence(dyn3DImageList=data4DList) +dynSeq.name = '4DCT' + +#%% +# load the rtStruct data and print its content +structData = readData(dataStructPath)[0] +print('Available ROIs') +structData.print_ROINames() + +#%% +# create Dynamic3DModel +model3D = Dynamic3DModel() + +# change its name +model3D.name = 'MidP' + +# give it an seriesInstanceUID +model3D.seriesInstanceUID = generate_uid() + +# give it an seriesInstanceUID +model3D.seriesInstanceUID = generate_uid() + +# generate the midP image and deformation fields from the dynamic 3D sequence +startTime = time.time() +model3D.computeMidPositionImage(dynSeq, tryGPU=True) +stopTime = time.time() + +print(model3D.midp.name) +print('MidP computed in ', np.round(stopTime-startTime)) + +#%% +# Create a patient and give it the patient name +patient = Patient() +patient.name = patientName + +#%% +# Add the model and rtStruct to the patient +patient.appendPatientData(model3D) +patient.appendPatientData(structData) + +#%% +## Save it as a serialized object +saveSerializedObjects(patient, savingPath) diff --git a/examples/DICOMExample/crop3DDataAroundROI.py b/examples/DICOMExample/crop3DDataAroundROI.py new file mode 100644 index 0000000..70de1d4 --- /dev/null +++ b/examples/DICOMExample/crop3DDataAroundROI.py @@ -0,0 +1,64 @@ +''' +Crop 3D Data Around ROI +========================= +author: OpenTPS team + +This example shows how to: +- Read a serialized patient with a Dynamic3DSequence, a Dynamic3DModel and an RTStruct +!! The data is not given in the test data folder of the project !! +- Select an ROI from the RTStruct object +- Get the ROI as an ROIMask +- Get the box around the ROI in scanner coordinates +- Crop the dynamic sequence and the dynamic model around the box + +running time: ~ 10 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import sys + +#%% +#import the needed opentps.core packages +from opentps.core.processing.imageProcessing.resampler3D import crop3DDataAroundBox +from opentps.core.processing.segmentation.segmentation3D import getBoxAroundROI +from opentps.core.io.serializedObjectIO import loadDataStructure + +#%% +# Load the serialized patient data +dataPath = 'Path_to_your_serialized_patient_data/' # replace with the path to your serialized patient data +patient = loadDataStructure(dataPath)[0] + +dynSeq = patient.getPatientDataOfType("Dynamic3DSequence")[0] +dynMod = patient.getPatientDataOfType("Dynamic3DModel")[0] +rtStruct = patient.getPatientDataOfType("RTStruct")[0] + +#%% +# get the ROI and mask on which we want to apply the motion signal +print('Available ROIs') +rtStruct.print_ROINames() +bodyContour = rtStruct.getContourByName('body') +ROIMask = bodyContour.getBinaryMask(origin=dynMod.midp.origin, gridSize=dynMod.midp.gridSize, spacing=dynMod.midp.spacing) + +#%% +# get the box around the ROI +box = getBoxAroundROI(ROIMask) +marginInMM = [10, 10, 10] +#%% +# crop the dynamic sequence and the dynamic model around the box +crop3DDataAroundBox(dynSeq, box, marginInMM=marginInMM) +print('-'*50) +crop3DDataAroundBox(dynMod, box, marginInMM=marginInMM) diff --git a/examples/DICOMExample/exampleImageResampling.py b/examples/DICOMExample/exampleImageResampling.py new file mode 100644 index 0000000..98be1ac --- /dev/null +++ b/examples/DICOMExample/exampleImageResampling.py @@ -0,0 +1,156 @@ +''' +Image Resampling +========================= +author: OpenTPS team + +This example shows how the resampling function can be used on image3D objects. + +running time: ~ 10 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import sys +import matplotlib.pyplot as plt +import os +from pathlib import Path + +#%% +#import the needed opentps.core packages +from opentps.core.io.dataLoader import readData +from opentps.core.processing.imageProcessing.resampler3D import resample, resampleOnImage3D + +#%% +# Get the current working directory, its parent, then add the testData folder at the end of it +testDataPath = os.path.join(Path(os.getcwd()).parent.absolute(), 'testData/') + +#%% +#load an image to use as example +dataPath = "Path_to_your_image_data/" # replace with the path to your image data folder +img = readData(dataPath)[0] +print('Image type =', type(img)) +zSlice = int(img.gridSize[2] / 2) + +#%% +# let's resample the image using a specific spacing (upsampling or downsampling) +spacingResampledDown = resample(img, img.spacing * 1.5) +spacingResampledDownZSlice = int(spacingResampledDown.gridSize[2] / 2) +""" +Note that as the spacing is the second argument in the resample function, it can be use without specifying the argument name if put in the second position (here above) +If you prefer to be sure to specify the correct argument, you can use the name as the example here under +""" +spacingResampledUp = resample(img, spacing=img.spacing * 0.5) +spacingResampledUpZSlice = int(spacingResampledUp.gridSize[2] / 2) + +#%% +# display results with spacing, gridSize and origin +fig, ax = plt.subplots(1, 3) + +ax[0].imshow(img.imageArray[:, :, zSlice]) +ax[0].set_title(f"Original image") +ax[0].set_xlabel(f"Spacing, {img.spacing} \n Grid Size {img.gridSize} \n Origin {img.origin}") + +ax[1].imshow(spacingResampledDown.imageArray[:, :, spacingResampledDownZSlice]) +ax[1].set_title(f"Downsampled using spacing") +ax[1].set_xlabel(f"Spacing, {spacingResampledDown.spacing} \n Grid Size {spacingResampledDown.gridSize} \n Origin {spacingResampledDown.origin}") + +ax[2].imshow(spacingResampledUp.imageArray[:, :, spacingResampledUpZSlice]) +ax[2].set_title(f"Upsampled using spacing") +ax[2].set_xlabel(f"Spacing, {spacingResampledUp.spacing} \n Grid Size {spacingResampledUp.gridSize} \n Origin {spacingResampledUp.origin}") + +plt.show() + +#%% +# now let's resample the image using a specific gridSize +gridSizeResampled = resample(img, gridSize=(200, 200, 200)) +gridSizeResampledZSlice = int(gridSizeResampled.gridSize[2] / 2) + +#%% +# and using BOTH a specific spacing AND gridSize +""" +Note that this is not recomanded as it can push parts of the image outside the new array grid and have the same effect as cropping the data +""" +spacingAndGSResampled = resample(img, gridSize=(100, 100, 100), spacing=(2, 2, 2)) +spacingAndGSResampledZSlice = int(spacingAndGSResampled.gridSize[2] / 2) + +#%% +# display results with spacing, gridSize and origin +fig, ax = plt.subplots(1, 3) + +ax[0].imshow(img.imageArray[:, :, zSlice]) +ax[0].set_title(f"Original image") +ax[0].set_xlabel(f"Spacing, {img.spacing} \n Grid Size {img.gridSize} \n Origin {img.origin}") + +ax[1].imshow(gridSizeResampled.imageArray[:, :, gridSizeResampledZSlice]) +ax[1].set_title(f"Resampled using gridSize") +ax[1].set_xlabel(f"Spacing, {gridSizeResampled.spacing} \n Grid Size {gridSizeResampled.gridSize} \n Origin {gridSizeResampled.origin}") + +ax[2].imshow(spacingAndGSResampled.imageArray[:, :, spacingAndGSResampledZSlice]) +ax[2].set_title(f"Resampled using gridSize AND spacing") +ax[2].set_xlabel(f"Spacing, {spacingAndGSResampled.spacing} \n Grid Size {spacingAndGSResampled.gridSize} \n Origin {spacingAndGSResampled.origin}") + +plt.show() + +#%% +# now let's try using the origin, which corresponds to a translation of the image +originResampled = resample(img, origin=(-220, -200, -200)) +originResampledZSlice = int(originResampled.gridSize[2] / 2) + +#%% +# and using BOTH the origin AND gridSize +""" +Note that this is not recomanded as it can push parts of the image outside the new array grid and have the same effect as cropping the data +""" +originAndGSResampled = resample(img, gridSize=(50, 50, 50), origin=(-220, -200, -200)) +originAndGSResampledZSlice = int(originAndGSResampled.gridSize[2] / 2) + +#%% +# display results with spacing, gridSize and origin +fig, ax = plt.subplots(1, 3) + +ax[0].imshow(img.imageArray[:, :, zSlice]) +ax[0].set_title(f"Original image") +ax[0].set_xlabel(f"Spacing, {img.spacing} \n Grid Size {img.gridSize} \n Origin {img.origin}") + +ax[1].imshow(originResampled.imageArray[:, :, originResampledZSlice]) +ax[1].set_title(f"Resampled using origin") +ax[1].set_xlabel(f"Spacing, {originResampled.spacing} \n Grid Size {originResampled.gridSize} \n Origin {originResampled.origin}") + +ax[2].imshow(originAndGSResampled.imageArray[:, :, originAndGSResampledZSlice]) +ax[2].set_title(f"Resampled using gridSize AND origin") +ax[2].set_xlabel(f"Spacing, {originAndGSResampled.spacing} \n Grid Size {originAndGSResampled.gridSize} \n Origin {originAndGSResampled.origin}") + +plt.show() + +#%% +# Now you can also use the following function if you need to resample an image on the grid of another image +resampledOnGrid = resampleOnImage3D(gridSizeResampled, fixedImage=img) + +#%% +# where the spacing, gridSize and origin of the fixedImage is used to resample the data (first argument) + +resampledOnGridZSlice = int(resampledOnGrid.gridSize[2] / 2) + +fig, ax = plt.subplots(1, 2) + +ax[0].imshow(gridSizeResampled.imageArray[:, :, gridSizeResampledZSlice]) +ax[0].set_title(f"Before resampling") +ax[0].set_xlabel(f"Spacing, {gridSizeResampled.spacing} \n Grid Size {gridSizeResampled.gridSize} \n Origin {gridSizeResampled.origin}") + +ax[1].imshow(resampledOnGrid.imageArray[:, :, resampledOnGridZSlice]) +ax[1].set_title(f"After resampling") +ax[1].set_xlabel(f"Spacing, {resampledOnGrid.spacing} \n Grid Size {resampledOnGrid.gridSize} \n Origin {resampledOnGrid.origin}") + +plt.show() diff --git a/examples/DICOMExample/generateDRRAndGTVMasks.py b/examples/DICOMExample/generateDRRAndGTVMasks.py new file mode 100644 index 0000000..a539d3a --- /dev/null +++ b/examples/DICOMExample/generateDRRAndGTVMasks.py @@ -0,0 +1,335 @@ +''' +Generate DRR and GTV Masks +========================== +author: OpenTPS team + +This example shows how to: +- read model + ROI data from a serialized file +- create a breathing signal using the motion amplitude present in the model +- chose an ROI to apply the breathing signal to its center of mass +- + +!!! does not work with public data for now since there is no struct in the public data !!! + +running time: ~ 10 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import matplotlib.pyplot as plt +from scipy.ndimage import zoom +import math +import time +import concurrent +from itertools import repeat +import os +import sys +import numpy as np + +#%% +# import the needed opentps.core packages + +from opentps.core.processing.imageProcessing.resampler3D import crop3DDataAroundBox +from opentps.core.io.serializedObjectIO import saveSerializedObjects, loadDataStructure +from opentps.core.data.dynamicData._breathingSignals import SyntheticBreathingSignal +from opentps.core.processing.deformableDataAugmentationToolBox.generateDynamicSequencesFromModel import generateDeformationListFromBreathingSignalsAndModel +from opentps.core.processing.imageSimulation.DRRToolBox import forwardProjection +from opentps.core.processing.imageProcessing.image2DManip import getBinaryMaskFromROIDRR, get2DMaskCenterOfMass +from opentps.core.processing.imageProcessing.crop2D import getBoxAroundROI +from opentps.core.processing.imageProcessing.imageTransform3D import getVoxelIndexFromPosition +from opentps.core.processing.deformableDataAugmentationToolBox.modelManipFunctions import getAverageModelValuesAroundPosition + +#%% +#paths selection + +patientFolder = 'Patient_0' +patientFolderComplement = '' +organ = 'liver' +basePath = 'D:/ImageData/' + +dataSetFolder = '/test/' +dataSetDataFolder = 'data/' + +dataPath = "Path_to_your_serialized_patient_data/" # replace with the path to your serialized patient data +savingPath = "Path_to_your_saving_path/" # replace with the path where you want to save the results +if not os.path.exists(savingPath): + os.umask(0) + os.makedirs(savingPath) # Create a new directory because it does not exist + os.makedirs(savingPath + dataSetDataFolder) # Create a new directory because it does not exist + print("New directory created to save the data: ", savingPath) + +#%% +# parameters selection +sequenceDurationInSecs = 10 +samplingFrequency = 4 +subSequenceSize = 50 +outputSize = [64, 64] +bodyContourToUse = 'body' +otherContourToUse = 'MidP CT GTV' +marginInMM = [50, 0, 100] + +#%% +# breathing signal parameters +amplitude = 'model' +variationAmplitude = 2 +breathingPeriod = 4 +variationFrequency = 0.1 +shift = 2 +meanNoise = 0 +varianceNoise = 0.5 +samplingPeriod = 1 / samplingFrequency +simulationTime = sequenceDurationInSecs +meanEvent = 2 / 30 +# use Z - 0 for Coronal and Z - 90 for sagittal +projAngle = 0 +projAxis = 'Z' + +multiprocessing = False +maxMultiProcUse = 4 +tryGPU = True + +#%% +# This function is specific to this example and used to : +#- deform a CTImage and an ROIMask, +#- create DRR's for both, +#- binarize the DRR of the ROIMask +#- compute its center of mass + +def deformImageAndMaskAndComputeDRRs(img, ROIMask, deformation, projectionAngle=0, projectionAxis='Z', tryGPU=True, outputSize=[]): + print('Start deformations and projections for deformation', deformation.name) + image = deformation.deformImage(img, fillValue='closest', outputType=np.int16, tryGPU=tryGPU) + # print(image.imageArray.shape, np.min(image.imageArray), np.max(image.imageArray), np.mean(image.imageArray)) + mask = deformation.deformImage(ROIMask, fillValue='closest', outputType=np.int16, tryGPU=tryGPU) + centerOfMass3D = mask.centerOfMass + + DRR = forwardProjection(image, projectionAngle, axis=projectionAxis) + DRRMask = forwardProjection(mask, projectionAngle, axis=projectionAxis) + + halfDiff = int((DRR.shape[1] - image.gridSize[2])/2) ## not sure this will work if orientation is changed + croppedDRR = DRR[:, halfDiff + 1:DRR.shape[1] - halfDiff - 1] ## not sure this will work if orientation is changed + croppedDRRMask = DRRMask[:, halfDiff + 1:DRRMask.shape[1] - halfDiff - 1] ## not sure this will work if orientation is changed + + if outputSize: + # print('Before resampling') + # print(croppedDRR.shape, np.min(croppedDRR), np.max(croppedDRR), np.mean(croppedDRR)) + ratio = [outputSize[0]/croppedDRR.shape[0], outputSize[1]/croppedDRR.shape[1]] + croppedDRR = zoom(croppedDRR, ratio) + croppedDRRMask = zoom(croppedDRRMask, ratio) + # print('After resampling') + # print(croppedDRR.shape, np.min(croppedDRR), np.max(croppedDRR), np.mean(croppedDRR)) + + binaryDRRMask = getBinaryMaskFromROIDRR(croppedDRRMask) + centerOfMass = get2DMaskCenterOfMass(binaryDRRMask) + # print('CenterOfMass:', centerOfMass) + + del image # to release the RAM + del mask # to release the RAM + + print('Deformations and projections finished for deformation', deformation.name) + + # plt.figure() + # plt.subplot(1, 5, 1) + # plt.imshow(DRR) + # plt.subplot(1, 5, 2) + # plt.imshow(croppedDRR) + # plt.subplot(1, 5, 3) + # plt.imshow(DRRMask) + # plt.subplot(1, 5, 4) + # plt.imshow(croppedDRRMask) + # plt.subplot(1, 5, 5) + # plt.imshow(binaryDRRMask) + # plt.show() + + return [croppedDRR, binaryDRRMask, centerOfMass, centerOfMass3D] + +#%% +# load the patient data structure and get the model and RTStruct + +patient = loadDataStructure(dataPath)[0] +dynMod = patient.getPatientDataOfType("Dynamic3DModel")[0] +rtStruct = patient.getPatientDataOfType("RTStruct")[0] + +#%% +# get the ROI and mask on which we want to apply the motion signal +print('Available ROIs') +rtStruct.print_ROINames() + +gtvContour = rtStruct.getContourByName(otherContourToUse) +GTVMask = gtvContour.getBinaryMask(origin=dynMod.midp.origin, gridSize=dynMod.midp.gridSize, spacing=dynMod.midp.spacing) +gtvBox = getBoxAroundROI(GTVMask) + +#%% +# get the body contour to adjust the crop in the direction of the DRR projection +bodyContour = rtStruct.getContourByName(bodyContourToUse) +bodyMask = bodyContour.getBinaryMask(origin=dynMod.midp.origin, gridSize=dynMod.midp.gridSize, spacing=dynMod.midp.spacing) +bodyBox = getBoxAroundROI(bodyMask) + +if projAngle == 0 and projAxis == 'Z': # coronal + croppingBox = [gtvBox[0], bodyBox[1], gtvBox[2]] ## create the used box combining the two boxes +elif projAngle == 90 and projAxis == 'Z': # sagittal + croppingBox = [bodyBox[0], gtvBox[1], gtvBox[2]] +elif projAngle == 0 and projAxis == 'X': # coronal + croppingBox = [gtvBox[0], bodyBox[1], gtvBox[2]] +elif projAngle == 0 and projAxis == 'Y': # sagittal + croppingBox = [bodyBox[0], gtvBox[1], gtvBox[2]] +else: + print('Do not know how to handle crop in this axis/angle configuration, so the body is used') + croppingBox = [bodyBox[0], bodyBox[1], bodyBox[2]] + +#%% +# crop the model data using the box +crop3DDataAroundBox(dynMod, croppingBox, marginInMM=marginInMM) + +#%% +# if you want to see the crop in the opentps_core you can save the data in cropped version +saveSerializedObjects(patient, savingPath + 'croppedModelAndROIs') + +#%% +# get the 3D center of mass of this ROI +gtvCenterOfMass = gtvContour.getCenterOfMass(dynMod.midp.origin, dynMod.midp.gridSize, dynMod.midp.spacing) +gtvCenterOfMassInVoxels = getVoxelIndexFromPosition(gtvCenterOfMass, dynMod.midp) +print('Used ROI name', gtvContour.name) +print('Used ROI center of mass :', gtvCenterOfMass) +print('Used ROI center of mass in voxels:', gtvCenterOfMassInVoxels) + +if amplitude == 'model': + ## to get amplitude from model !!! it takes some time because 10 displacement fields must be computed just for this + modelValues = getAverageModelValuesAroundPosition(gtvCenterOfMass, dynMod, dimensionUsed='Z') + amplitude = np.max(modelValues) - np.min(modelValues) + print('Amplitude of deformation at ROI center of mass', amplitude) + +#%% +# Signal creation +newSignal = SyntheticBreathingSignal(amplitude=amplitude, + variationAmplitude=variationAmplitude, + breathingPeriod=breathingPeriod, + variationFrequency=variationFrequency, + shift=shift, + meanNoise=meanNoise, + varianceNoise=varianceNoise, + samplingPeriod=samplingPeriod, + simulationTime=sequenceDurationInSecs, + meanEvent=meanEvent) + +newSignal.generate1DBreathingSignal() + +pointList = [gtvCenterOfMass] +pointVoxelList = [gtvCenterOfMassInVoxels] +signalList = [newSignal] + +saveSerializedObjects([signalList, pointList], savingPath + 'ROIsAndSignalObjects') +for signalIndex in range(len(signalList)): + signalList[signalIndex] = signalList[signalIndex].breathingSignal + +#%% +#show signals and ROIs + +prop_cycle = plt.rcParams['axes.prop_cycle'] +colors = prop_cycle.by_key()['color'] +plt.figure(figsize=(12, 6)) +signalAx = plt.subplot(2, 1, 2) + +for pointIndex, point in enumerate(pointList): + ax = plt.subplot(2, 2 * len(pointList), 2 * pointIndex + 1) + ax.set_title('Slice Y:' + str(pointVoxelList[pointIndex][1])) + ax.imshow(np.rot90(dynMod.midp.imageArray[:, pointVoxelList[pointIndex][1], :])) + ax.scatter([pointVoxelList[pointIndex][0]], [dynMod.midp.imageArray.shape[2] - pointVoxelList[pointIndex][2]], + c=colors[pointIndex], marker="x", s=100) + ax2 = plt.subplot(2, 2 * len(pointList), 2 * pointIndex + 2) + ax2.set_title('Slice Z:' + str(pointVoxelList[pointIndex][2])) + ax2.imshow(np.rot90(dynMod.midp.imageArray[:, :, pointVoxelList[pointIndex][2]])) + ax2.scatter([pointVoxelList[pointIndex][0]], [dynMod.midp.imageArray.shape[2] - pointVoxelList[pointIndex][2]], + c=colors[pointIndex], marker="x", s=100) + signalAx.plot(newSignal.timestamps / 1000, signalList[pointIndex], c=colors[pointIndex]) + +signalAx.set_xlabel('Time (s)') +signalAx.set_ylabel('Deformation amplitude in Z direction (mm)') +plt.savefig(savingPath + 'ROI_And_Signals_fig.pdf', dpi=300) +plt.show() + +## ------------------------------------------------------------- + +sequenceSize = newSignal.breathingSignal.shape[0] +print('Sequence Size =', sequenceSize, 'split by stack of ', subSequenceSize, '. Multiprocessing =', multiprocessing) + +subSequencesIndexes = [subSequenceSize * i for i in range(math.ceil(sequenceSize/subSequenceSize))] +subSequencesIndexes.append(sequenceSize) +print('Sub sequences indexes', subSequencesIndexes) + +startTime = time.time() + +if multiprocessing == False: + + resultList = [] + + for i in range(len(subSequencesIndexes)-1): + print('Creating deformations for images', subSequencesIndexes[i], 'to', subSequencesIndexes[i + 1] - 1) + + deformationList = generateDeformationListFromBreathingSignalsAndModel(dynMod, + signalList, + pointList, + signalIdxUsed=[subSequencesIndexes[i], subSequencesIndexes[i+1]], + dimensionUsed='Z', + outputType=np.float32) + + for deformationIndex, deformation in enumerate(deformationList): + resultList.append(deformImageAndMaskAndComputeDRRs(dynMod.midp, + GTVMask, + deformation, + projectionAngle=projAngle, + projectionAxis=projAxis, + outputSize=outputSize, + tryGPU=True)) + + + savingPath += dataSetDataFolder + f'Patient_0_{sequenceSize}_DRRMasksAndCOM' + saveSerializedObjects(resultList, savingPath + str(sequenceSize)) + + +elif multiprocessing == True: + + resultList = [] + + if subSequenceSize > maxMultiProcUse: ## re-adjust the subSequenceSize since this will be done in multi processing + subSequenceSize = maxMultiProcUse + print('SubSequenceSize put to', maxMultiProcUse, 'for multiprocessing.') + print('Sequence Size =', sequenceSize, 'split by stack of ', subSequenceSize, '. Multiprocessing =', multiprocessing) + subSequencesIndexes = [subSequenceSize * i for i in range(math.ceil(sequenceSize / subSequenceSize))] + subSequencesIndexes.append(sequenceSize) + + for i in range(len(subSequencesIndexes)-1): + print('Creating deformations for images', subSequencesIndexes[i], 'to', subSequencesIndexes[i + 1] - 1) + + deformationList = generateDeformationListFromBreathingSignalsAndModel(dynMod, + signalList, + pointList, + signalIdxUsed=[subSequencesIndexes[i], subSequencesIndexes[i+1]], + dimensionUsed='Z', + outputType=np.float32) + + print('Start multi process deformation with', len(deformationList), 'deformations') + with concurrent.futures.ProcessPoolExecutor() as executor: + + results = executor.map(deformImageAndMaskAndComputeDRRs, repeat(dynMod.midp), repeat(GTVMask), deformationList, repeat(projAngle), repeat(projAxis), repeat(tryGPU), repeat(outputSize)) + resultList += results + + print('ResultList lenght', len(resultList)) + + savingPath += dataSetDataFolder + f'Patient_0_{sequenceSize}_DRRMasksAndCOM_multiProcTest' + saveSerializedObjects(resultList, savingPath) + +stopTime = time.time() +print('Test with multiprocessing =', multiprocessing, '. Sub-sequence size:', str(subSequenceSize), 'finished in', np.round(stopTime - startTime, 2) / 60, 'minutes') +print(np.round((stopTime - startTime)/len(resultList), 2), 'sec per sample') diff --git a/examples/DICOMExample/run_generateRandomSamplesFromModel.py b/examples/DICOMExample/run_generateRandomSamplesFromModel.py new file mode 100644 index 0000000..f1dfb32 --- /dev/null +++ b/examples/DICOMExample/run_generateRandomSamplesFromModel.py @@ -0,0 +1,72 @@ +''' +Generate random samples from a model +==================================== +author: OpenTPS team + +This example demonstrates how to generate random samples from a model using OpenTPS. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import sys +import time +import numpy as np +import matplotlib.pyplot as plt +from pathlib import Path +import opentps + +#%% +# import the needed opentps.core packages +from opentps.core.io.serializedObjectIO import loadDataStructure +from opentps.core.processing.deformableDataAugmentationToolBox.generateRandomSamplesFromModel import generateRandomImagesFromModel, generateRandomDeformationsFromModel + +#%% +#Data path +testDataPath = os.path.join(Path(os.getcwd()).parent.absolute(), 'testData/') + +#%% +# read a serialized dynamic sequence +dataPath = testDataPath + "veryLightDynMod.p" +dynMod = loadDataStructure(dataPath)[0] + +tryGPU = True +numberOfSamples = 50 + +imageList = [] + +startTime = time.time() + +defList = generateRandomDeformationsFromModel(dynMod, numberOfSamples=numberOfSamples, ampDistribution='gaussian') +for deformation in defList: + imageList.append(deformation.deformImage(dynMod.midp, fillValue='closest', tryGPU=tryGPU)) + +print(len(imageList)) +print('first test done in ', np.round(time.time() - startTime, 2)) + +plt.figure() +plt.imshow(imageList[0].imageArray[:, 50, :]) +plt.show() + +#%% +startTime = time.time() +imageList = generateRandomImagesFromModel(dynMod, numberOfSamples=numberOfSamples, ampDistribution='gaussian', tryGPU=tryGPU) +print('second test done in ', np.round(time.time() - startTime, 2)) + +plt.figure() +plt.imshow(imageList[0].imageArray[:, 50, :]) +plt.show() \ No newline at end of file diff --git a/examples/DoseComputation/README.rst b/examples/DoseComputation/README.rst new file mode 100644 index 0000000..9a88bdf --- /dev/null +++ b/examples/DoseComputation/README.rst @@ -0,0 +1,2 @@ +Dose Computation +---------------- diff --git a/examples/DoseComputation/run_SimpleDoseCalculation.py b/examples/DoseComputation/run_SimpleDoseCalculation.py new file mode 100644 index 0000000..1b02481 --- /dev/null +++ b/examples/DoseComputation/run_SimpleDoseCalculation.py @@ -0,0 +1,178 @@ +''' +Simple dose computation +======================= +author: OpenTPS team + +In this example we are going to create a generic CT and use the MCsquare dose calculator to compute the dose image + +running time: ~ 7 minute +''' + +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps +#%% +#imports + +import numpy as np +import os +from matplotlib import pyplot as plt +import math + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data.plan import ProtonPlanDesign +from opentps.core.data import DVH +from opentps.core.data import Patient +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D,resampleImage3D + +#%% +#Generic CT creation +#------------------- +#we will first create a generic CT of a box fill with water and air + +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +patient = Patient() +patient.name = 'Patient' + +ctSize = 150 + +ct = CTImage() +ct.name = 'CT' +ct.patient = patient + +huAir = -1024. +huWater = ctCalibration.convertRSP2HU(1.) +data = huAir * np.ones((ctSize, ctSize, ctSize)) +data[:, 50:, :] = huWater +ct.imageArray = data + +#%% +#Region of interest +#------------------ +#we will now create a region of interest wich is a small 3D box of size 20*20*20 + +roi = ROIMask() +roi.patient = patient +roi.name = 'TV' +roi.color = (255, 0, 0) # red +data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +data[65:85, 65:85, 65:85] = True +roi.imageArray = data + +#%% +# Configuration of MCsquare +#--------------------------- +#to configure Mcsquare we need to calibrate it with the CT calibration obtained above + +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.ctCalibration = ctCalibration +mc2.nbPrimaries = 1e7 + +#%% +# Plan Creation +#--------------- +#we will now create a plan and create one beam + +# Design plan +beamNames = ["Beam1","Beam2","Beam3"] +gantryAngles = [0.,90.,270.] +couchAngles = [0.,0.,0.] + +# Generate new plan +planDesign = ProtonPlanDesign() +planDesign.ct = ct +planDesign.targetMask = roi +planDesign.gantryAngles = gantryAngles +planDesign.beamNames = beamNames +planDesign.couchAngles = couchAngles +planDesign.calibration = ctCalibration +planDesign.spotSpacing = 5.0 +planDesign.layerSpacing = 5.0 +planDesign.targetMargin = 5.0 + +plan = planDesign.buildPlan() # Spot placement +plan.PlanName = "NewPlan" + +#%% +# Center of mass +#---------------- +#Here we look at the part of the 3D CT image where "stuff is happening" by getting the CoM. We use the function resampleImage3DOnImage3D to the same array size for both images. + +roi = resampleImage3DOnImage3D(roi, ct) +COM_coord = roi.centerOfMass +COM_index = roi.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = roi.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) + +#Output path +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) + +image = plt.imshow(img_ct,cmap='Blues') +plt.colorbar(image) +plt.contour(img_mask,colors="red") +plt.title("Created CT with ROI") +plt.text(5,40,"Air",color= 'black') +plt.text(5,100,"Water",color = 'white') +plt.text(71,77,"TV",color ='red') +plt.savefig(os.path.join(output_path, 'SimpleCT.png'),format = 'png') +plt.show() + +#%% +#Dose Computation +#---------------- +#We now use the MCsquare dose calculator to compute the dose of the created plan + +doseImage = mc2.computeDose(ct, plan) + +#%% +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) +scoringSpacing = [2, 2, 2] +scoringGridSize = [int(math.floor(i / j * k)) for i, j, k in zip([150,150,150], scoringSpacing, [1,1,1])] +roiResampled = resampleImage3D(roi, origin=ct.origin, gridSize=scoringGridSize, spacing=scoringSpacing) +target_DVH = DVH(roiResampled, doseImage) + +#%% +fig, ax = plt.subplots(1, 2, figsize=(12, 5)) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +cbar = plt.colorbar(dose, ax=ax[0]) +cbar.set_label('Dose(Gy)') +ax[1].plot(target_DVH.histogram[0], target_DVH.histogram[1], label=target_DVH.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() +plt.savefig(os.path.join(output_path, 'SimpleDose.png'), format = 'png') +plt.show() + +#%% +print('D95 = ' + str(target_DVH.D95) + ' Gy') +print('D5 = ' + str(target_DVH.D5) + ' Gy') +print('D5 - D95 = {} Gy'.format(target_DVH.D5 - target_DVH.D95)) diff --git a/examples/DoseComputation/run_UseOfRangeShifterForProtonPlan.py b/examples/DoseComputation/run_UseOfRangeShifterForProtonPlan.py new file mode 100644 index 0000000..4fb031f --- /dev/null +++ b/examples/DoseComputation/run_UseOfRangeShifterForProtonPlan.py @@ -0,0 +1,260 @@ +''' +Use of Range Shifter for Proton Plan +========================================= +author: OpenTPS team + +In this example, we will show how to create a plan from scratch and use range shifters. + +running time: ~ 10 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import logging +import sys + +from matplotlib import pyplot as plt + +#%% +#import the needed opentps.core packages +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +sys.path.append('..') +import numpy as np +from pathlib import Path +from opentps.core.data.plan._planProtonBeam import PlanProtonBeam +from opentps.core.data.plan._planProtonLayer import PlanProtonLayer +from opentps.core.data.plan._protonPlan import ProtonPlan +from opentps.core.data.plan._rtPlan import RTPlan +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import loadRTPlan, saveRTPlan +from opentps.core.io import mcsquareIO +from opentps.core.data._dvh import DVH +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.io.dataLoader import readData +from opentps.core.io.mhdIO import exportImageMHD +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.data.images import CTImage, DoseImage +from opentps.core.data.images import ROIMask +from opentps.core.io.dicomIO import writeDicomCT, writeRTPlan, writeRTDose, readDicomDose, writeRTStruct +from opentps.core.io.mcsquareIO import RangeShifter +from opentps.core.data.CTCalibrations.MCsquareCalibration._mcsquareMolecule import MCsquareMolecule +from opentps.core.data._rtStruct import RTStruct +from opentps.core.data import Patient + + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Exemple_RangeShifter') +if not os.path.exists(output_path): + os.makedirs(output_path) + print(f"Directory '{output_path}' created.") +else: + print(f"Directory '{output_path}' already exists.") + +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Choosing default scanner +doseCalculator = MCsquareDoseCalculator() +doseCalculator.ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +# Or a specific one if you do have +# MCSquarePath = os.path.join(openTPS_path, 'core', 'processing', 'doseCalculation', 'MCsquare') +# scannerPath = os.path.join(MCSquarePath, 'Scanners', 'UCL_Toshiba') +# doseCalculator.ctCalibration = MCsquareCTCalibration(fromFiles=(os.path.join(scannerPath, 'HU_Density_Conversion.txt'), +# os.path.join(scannerPath, 'HU_Material_Conversion.txt'), +# os.path.join(MCSquarePath, 'Materials'))) + +#%% +# Path of your BDL if you do have one +# Otherwise the default bdl 'BDL_default_DN_RangeShifter' is used +bdl_path = "Path/to/your/BDL" +if 'bdl_path' in locals() and os.path.isfile(bdl_path): + DoseCalculationConfig().bdlFile = bdl_path + +# chossing default BDL +doseCalculator.beamModel = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#%% +# Get different range shifters +#------------------------------ +# From bdl file : Be sure the material you use is in core/processing/doseCalculation/protons/MCsquare/Materials and you have the good MCsquare material ID in the bdl +# If you want to add a new material, you can add the folder with the necessary material properties in MCsquare/Materials +# The MCsquare material ID (RS_material) of the new material to add in the bdl will be print in the terminal and is in MCsquare/Materials/list.dat +rs_1 = doseCalculator.beamModel.rangeShifters[0] +# From scratch : +rs_2 = RangeShifter(material='Lexan', density=1.217, WET=40.3) +rs_2.ID = 'RS_Lexan_66' +rs_2.type = 'binary' +print('Range shifter 1:', rs_1) +print('Range shifter 2:', rs_2) + +#%% +# Configure dose calculation +#--------------------------- +doseCalculator.nbPrimaries = 1e7 # number of primary particles, 1e4 is enough for a quick test, otherwise 1e7 is recommended (It can take several minutes to compute). + +patient = Patient() +patient.name = 'TestPatient' + +#%% +# Define CT and Target +#--------------------- +ctSize = 200 +ct = CTImage() +ct.name = 'TestPhantom' +ct.patient = patient + +target = ROIMask() +target.name = 'TV' +target.spacing = ct.spacing +target.color = (255, 0, 0) # red +targetArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +radius = 20 +x0, y0, z0 = (100, 100, 100) +x, y, z = np.mgrid[0:ctSize:1, 0:ctSize:1, 0:ctSize:1] +r = np.sqrt((x - x0) ** 2 + (y - y0) ** 2 + (z - z0) ** 2) +targetArray[r < radius] = True +target.imageArray = targetArray + +huAir = -1024. +huWater = doseCalculator.ctCalibration.convertRSP2HU(1.) +ctArray = huAir * np.ones((ctSize, ctSize, ctSize)) +ctArray[1:ctSize - 1, 1:ctSize - 1, 1:ctSize - 1] = huWater +ctArray[targetArray >= 0.5] = 50 +ct.imageArray = ctArray + +body = ROIMask() +body.name = 'Body' +body.spacing = ct.spacing +body.color = (0, 0, 255) +bodyArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +bodyArray[1:ctSize - 1, 1:ctSize - 1, 1:ctSize - 1] = True +body.imageArray = bodyArray + +#%% +# Create plan from scratch +#--------------------------- +plan = ProtonPlan() +plan.appendBeam(PlanProtonBeam()) +plan.appendBeam(PlanProtonBeam()) +plan.appendBeam(PlanProtonBeam()) +plan.beams[0].gantryAngle = 0. +plan.beams[1].gantryAngle = 0. +plan.beams[2].gantryAngle = 90. +plan.beams[0].appendLayer(PlanProtonLayer(120)) # Nominal energy of the layer +plan.beams[1].appendLayer(PlanProtonLayer(120)) +plan.beams[2].appendLayer(PlanProtonLayer(120)) +plan[0].layers[0].appendSpot([50, 60], [100, 100], [300, 300]) # Two spots to the target from beam 0 (0. gantryAngle) +plan[1].layers[0].appendSpot([90, 100], [100, 100], [300, 300]) # Two spots to the target from beam 1 (0. gantryAngle) +plan[2].layers[0].appendSpot([100, 110], [100, 110], [300, 300]) # Two spots placed outside the target from beam 2 (90. gantryAngle) + +#%% +# Use 2 different range shifters for the two beams +plan.rangeShifter = [rs_1, rs_2] +plan.beams[0].rangeShifter = [rs_1] +plan.beams[1].rangeShifter = None +plan.beams[2].rangeShifter = [rs_2] + +#%% +# Range Shifter beam 0 parameters +plan[0].layers[0].rangeShifterSettings.isocenterToRangeShifterDistance = 0 # [mm] +plan[0].layers[0].rangeShifterSettings.rangeShifterSetting = 'IN' +plan[0].layers[0].rangeShifterSettings.rangeShifterWaterEquivalentThickness = None # [mm] None means get thickness from BDL + +#%% +# Range Shifter beam 2 parameters +plan[2].layers[0].rangeShifterSettings.isocenterToRangeShifterDistance = 200 # [mm] +plan[2].layers[0].rangeShifterSettings.rangeShifterSetting = 'IN' +plan[2].layers[0].rangeShifterSettings.rangeShifterWaterEquivalentThickness = 15 # [mm] None means get thickness from BDL + +#%% +# Save plan in OpenTPS format (serialized) +saveRTPlan(plan, os.path.join(output_path, 'dummy_plan.tps')) + +#%% +# Load plan in OpenTPS format (serialized) +plan2 = loadRTPlan(os.path.join(output_path, 'dummy_plan.tps')) +print(plan2[0].layers[0].spotWeights) +print(plan[0].layers[0].spotWeights) # plan2 is the same as plan + +#%% +# Save plan in Dicom format +dicomPath = os.path.join(output_path) +writeRTPlan(plan, dicomPath) +if not os.path.exists(os.path.join(dicomPath, 'CT')): + os.mkdir(os.path.join(dicomPath, 'CT')) +writeDicomCT(ct, os.path.join(dicomPath, 'CT')) +print('Dicom files saved in', dicomPath) + +# For contour, they must be RTStruct +contour = target.getROIContour() +struct = RTStruct() +struct.appendContour(contour) +writeRTStruct(struct, os.path.join(output_path, 'CT')) + +# load plan in Dicom format +dataList = readData(dicomPath, maxDepth=2) +ctDicom = [d for d in dataList if isinstance(d, CTImage)][0] +planDicom = [d for d in dataList if isinstance(d, RTPlan)][0] + +#%% +# Generic example: box of water with spherical target +#---------------------------------------------------- + +# Load CT & contours +# ct = [d for d in dataList if isinstance(d, CTImage)][0] +# struct = [d for d in dataList if isinstance(d, RTStruct)][0] +# target = struct.getContourByName('TV') +# body = struct.getContourByName('Body') + +# Compute the dose +doseImage = doseCalculator.computeDose(ct, plan) # You can choose the plan you want to use, results will be the same +# doseImage = importImageMHD(output_path) # If you want to import a dose image from a MHD file +# doseImageDicom = [d for d in dataList if isinstance(d, DoseImage)][0] # If you want to import a dose image from a Dicom file + +# Export dose (MHD) +# exportImageMHD(os.path.join(output_path,'DoseImage'), doseImage) + +# Export dose (Dicom) +writeRTDose(doseImage, dicomPath) + +#%% +# Plot dose +#---------- +target = resampleImage3DOnImage3D(target, ct) +COM_coord = target.centerOfMass +COM_index = target.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = target.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +#%% +# Display dose +#---------------- +plt.imshow(img_ct, cmap='gray') +plt.contour(img_mask, colors='red') # PTV +dose = plt.imshow(img_dose, cmap='jet', alpha=.6) +colorbar = plt.colorbar(dose) +colorbar.set_label('Dose [Gy]', fontsize=12) +plt.show() +plt.savefig(os.path.join(output_path, 'Dose_protonWithRangeShifters.png')) diff --git a/examples/DoseComputation/run_photonPlanCreationAndDoseCalculation.py b/examples/DoseComputation/run_photonPlanCreationAndDoseCalculation.py new file mode 100644 index 0000000..ea8a6ed --- /dev/null +++ b/examples/DoseComputation/run_photonPlanCreationAndDoseCalculation.py @@ -0,0 +1,176 @@ +''' +Photon Plan Creation and Dose Calculation +========================================= +author: OpenTPS team + +This example demonstrates how to create a photon plan and perform dose calculation using OpenTPS. + +running time: ~ 10 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +from opentps.core.data.images._ctImage import CTImage +from opentps.core.io.scannerReader import readScanner +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.photons.cccDoseCalculator import CCCDoseCalculator +from opentps.core.io.sitkIO import exportImageSitk +import numpy as np +from opentps.core.data.images import ROIMask +import logging +from opentps.core.data.plan._photonPlan import PhotonPlan +from opentps.core.data.plan._planPhotonBeam import PlanPhotonBeam +from opentps.core.data.plan._planPhotonSegment import PlanPhotonSegment +from opentps.core.io.serializedObjectIO import loadRTPlan, saveRTPlan +from pathlib import Path +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.data._dvh import DVH +import matplotlib.pyplot as plt + +logger = logging.getLogger(__name__) +#%% +def getMLCCoordinates(Ymin, Ymax, step): + first_column = np.arange(Ymin, Ymax, step) + second_column = np.arange(Ymin + step, Ymax + step, step) + Xl = np.zeros(len(first_column)) + Xr = np.zeros(len(first_column)) + # Xl[15:25] = np.random.rand(10) * -5 + # Xr[15:25] = np.random.rand(10) * 5 + Xl[15:25] = np.ones(10) * -5 + Xr[15:25] = np.ones(10) * 5 + return np.column_stack((first_column, second_column, Xl, Xr)) + +#%% +def initializeSegment(beamSegment,Ymin, Ymax, step): + beamSegment.Xmlc_mm = getMLCCoordinates(Ymin, Ymax, step) + beamSegment.x_jaw_mm = [-50, 50] + beamSegment.y_jaw_mm = [-200, 200] + beamSegment.mu = 5000 + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Output_Example','PhotonDoseCalculation') +if not os.path.exists(output_path): + os.makedirs(output_path) + +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Create plan from scratch +#------------------------- +plan = PhotonPlan() +plan.appendBeam(PlanPhotonBeam()) +plan.appendBeam(PlanPhotonBeam()) +plan.beams[0].appendBeamSegment(PlanPhotonSegment()) +plan.beams[1].appendBeamSegment(PlanPhotonSegment()) + +Ymax = 200 +Ymin = -200 +step = 10 +initializeSegment(plan.beams[0].beamSegments[0], Ymin, Ymax, step) +initializeSegment(plan.beams[1].beamSegments[0], Ymin, Ymax, step) +plan.beams[1].beamSegments[0].gantryAngle_degree = 90. + +# Save plan +saveRTPlan(plan,os.path.join(output_path,'dummy_plan.tps')) + +# Load plan +plan2 = loadRTPlan(os.path.join(output_path,'dummy_plan.tps')) +print(plan2) + +#%% +# Dose computation from plan +#--------------------------- +ccc = CCCDoseCalculator(batchSize= 30) +ccc.ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) + +#%% +# Create CT and contours +#----------------------- +ctSize = 20 +ct = CTImage() +ct.name = 'CT' +ct.origin = -ctSize/2 * ct.spacing + +target = ROIMask() +target.name = 'TV' +target.origin = -ctSize/2 * ct.spacing +target.spacing = ct.spacing +target.color = (255, 0, 0) # red +targetArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +radius = 2.5 +x0, y0, z0 = (10, 10, 10) +x, y, z = np.mgrid[0:ctSize:1, 0:ctSize:1, 0:ctSize:1] +r = np.sqrt((x - x0) ** 2 + (y - y0) ** 2 + (z - z0) ** 2) +targetArray[r < radius] = True +target.imageArray = targetArray + +ctArray = np.zeros((ctSize, ctSize, ctSize)) +ctArray[1:ctSize - 1, 1:ctSize - 1, 1:ctSize - 1] = 1 +ctArray[targetArray>=0.5] = 10 +ct.imageArray = ctArray + +body = ROIMask() +body.name = 'Body' +body.spacing = ct.spacing +body.origin = -ctSize/2 * ct.spacing +body.color = (0, 0, 255) +bodyArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +bodyArray[1:ctSize- 1, 1:ctSize - 1, 1:ctSize - 1] = True +body.imageArray = bodyArray + +doseImage = ccc.computeDose(ct, plan) + +#%% +# DVH +#----- +dvh = DVH(target, doseImage) +print("D95",dvh._D95) +print("D5",dvh._D5) +print("Dmax",dvh._Dmax) +print("Dmin",dvh._Dmin) + +#%% +# Plot dose +#---------------- +target = resampleImage3DOnImage3D(target, ct) +COM_coord = target.centerOfMass +COM_index = target.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = target.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +#%% +# Display dose +fig, ax = plt.subplots(1, 2, figsize=(10, 5)) +ax[0].axes.get_xaxis().set_visible(False) +ax[0].axes.get_yaxis().set_visible(False) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +plt.colorbar(dose, ax=ax[0]) +ax[1].plot(dvh.histogram[0], dvh.histogram[1], label=dvh.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +ax[1].grid(True) +ax[1].legend() +plt.savefig(os.path.join(output_path, 'dose.png')) +plt.show() \ No newline at end of file diff --git a/examples/DoseComputation/run_protonPlanCreationAndDoseCalculation.py b/examples/DoseComputation/run_protonPlanCreationAndDoseCalculation.py new file mode 100644 index 0000000..43fb4fb --- /dev/null +++ b/examples/DoseComputation/run_protonPlanCreationAndDoseCalculation.py @@ -0,0 +1,203 @@ +''' +Proton Plan Creation and Dose Calculation +========================================= +author: OpenTPS team + +This example demonstrates how to create a proton plan and perform dose calculation using OpenTPS. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import logging +import sys + +#%% +#import the needed opentps.core packages +from matplotlib import pyplot as plt + +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.processing.planOptimization.tools import evaluateClinical +sys.path.append('..') +import numpy as np + +from opentps.core.data.plan import ProtonPlan, RTPlan +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import loadRTPlan, saveRTPlan +from opentps.core.io.dicomIO import readDicomDose, readDicomPlan +from opentps.core.io.dataLoader import readData +from opentps.core.data.CTCalibrations.MCsquareCalibration._mcsquareCTCalibration import MCsquareCTCalibration +from opentps.core.io import mcsquareIO +from opentps.core.data._dvh import DVH +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.io.mhdIO import exportImageMHD +from opentps.core.data.plan import PlanProtonBeam +from opentps.core.data.plan import PlanProtonLayer +from opentps.core.data.images import CTImage, DoseImage +from opentps.core.data import RTStruct +from opentps.core.data import Patient +from opentps.core.data.images import ROIMask +from pathlib import Path + +logger = logging.getLogger(__name__) + +#%% +#Output path +#------------ + +output_path = os.getcwd() + +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Create plan from scratch and save it +#------------------------------------------------ +plan = ProtonPlan() +plan.appendBeam(PlanProtonBeam()) +plan.appendBeam(PlanProtonBeam()) +plan.beams[1].gantryAngle = 120. +plan.beams[0].appendLayer(PlanProtonLayer(100)) +plan.beams[0].appendLayer(PlanProtonLayer(90)) +plan.beams[1].appendLayer(PlanProtonLayer(80)) +plan[0].layers[0].appendSpot([-1,0,1], [1,2,3], [0.1,0.2,0.3]) +plan[0].layers[1].appendSpot([0,1], [2,3], [0.2,0.3]) +plan[1].layers[0].appendSpot(1, 1, 0.5) + +# Save plan +saveRTPlan(plan,os.path.join(output_path,'dummy_plan.tps')) + +#%% +# Load plan in OpenTPS format (serialized) +plan2 = loadRTPlan(os.path.join(output_path,'dummy_plan.tps')) +print(plan2[0].layers[1].spotWeights) +print(plan[0].layers[1].spotWeights) + +# Load DICOM plan +#dicomPath = os.path.join(Path(os.getcwd()).parent.absolute(),'opentps','testData','Phantom') +#print(dicomPath) +#dataList = readData(dicomPath, maxDepth=1) +#plan3 = [d for d in dataList if isinstance(d, RTPlan)][0] +# or provide path to RTPlan and read it +# plan_path = os.path.join(Path(os.getcwd()).parent.absolute(),'opentps/testData/Phantom/Plan_SmallWaterPhantom_cropped_resampled_optimized.dcm') +# plan3 = readDicomPlan(plan_path) + +#%% +# Dose computation from plan +#--------------------------- +# Choosing default Scanner and BDL +doseCalculator = MCsquareDoseCalculator() +doseCalculator.ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +doseCalculator.beamModel = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) +doseCalculator.nbPrimaries = 1e7 + +#%% +# Generic example: box of water with spherical target +#---------------------------------------------------- + + +# Load CT & contours +# ct = [d for d in dataList if isinstance(d, CTImage)][0] +# struct = [d for d in dataList if isinstance(d, RTStruct)][0] +# target = struct.getContourByName('TV') +# body = struct.getContourByName('Body') + +# or create CT and contours +ctSize = 20 +ct = CTImage() +ct.name = 'CT' + +target = ROIMask() +target.name = 'TV' +target.spacing = ct.spacing +target.color = (255, 0, 0) # red +targetArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +radius = 2.5 +x0, y0, z0 = (10, 10, 10) +x, y, z = np.mgrid[0:ctSize:1, 0:ctSize:1, 0:ctSize:1] +r = np.sqrt((x - x0) ** 2 + (y - y0) ** 2 + (z - z0) ** 2) +targetArray[r < radius] = True +target.imageArray = targetArray + +huAir = -1024. +huWater = doseCalculator.ctCalibration.convertRSP2HU(1.) +ctArray = huAir * np.ones((ctSize, ctSize, ctSize)) +ctArray[1:ctSize - 1, 1:ctSize - 1, 1:ctSize - 1] = huWater +ctArray[targetArray>=0.5] = 50 +ct.imageArray = ctArray + +body = ROIMask() +body.name = 'Body' +body.spacing = ct.spacing +body.color = (0, 0, 255) +bodyArray = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +bodyArray[1:ctSize- 1, 1:ctSize - 1, 1:ctSize - 1] = True +body.imageArray = bodyArray + +#%% + +# MCsquare simulation +doseImage = doseCalculator.computeDose(ct, plan) +# or Load dicom dose +#doseImage = [d for d in dataList if isinstance(d, DoseImage)][0] +# or +#dcm_dose_file = os.path.join(output_path, "Dose_SmallWaterPhantom_resampled_optimized.dcm") +#doseImage = readDicomDose(dcm_dose_file) + +# Export dose +#output_path = os.getcwd() +#exportImageMHD(output_path, doseImage) + +#%% +# DVH +#----- +dvh = DVH(target, doseImage) +print("D95",dvh._D95) +print("D5",dvh._D5) +print("Dmax",dvh._Dmax) +print("Dmin",dvh._Dmin) + +#%% +# Plot dose +#---------- +target = resampleImage3DOnImage3D(target, ct) +COM_coord = target.centerOfMass +COM_index = target.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = target.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +#%% +# Display dose +#---------------- +fig, ax = plt.subplots(1, 2, figsize=(10, 5)) +ax[0].axes.get_xaxis().set_visible(False) +ax[0].axes.get_yaxis().set_visible(False) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +plt.colorbar(dose, ax=ax[0]) +ax[1].plot(dvh.histogram[0], dvh.histogram[1], label=dvh.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +ax[1].grid(True) +ax[1].legend() +plt.show() \ No newline at end of file diff --git a/examples/DoseDeliverySimulation/PlanDeliverySimulation.py b/examples/DoseDeliverySimulation/PlanDeliverySimulation.py new file mode 100644 index 0000000..456489a --- /dev/null +++ b/examples/DoseDeliverySimulation/PlanDeliverySimulation.py @@ -0,0 +1,121 @@ +''' +Plan Delivery Simulation +========================= +author: OpenTPS team + +This example will present the basis of plan delivery simulation with openTPS core. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import matplotlib.pyplot as plt +import logging +import os + +#%% +#import the needed opentps.core packages + +from opentps.core.io.dataLoader import readData +from opentps.core.io.dicomIO import readDicomPlan, readDicomStruct +from opentps.core.data.images._ctImage import CTImage +from opentps.core.data.images._deformation3D import Deformation3D +from opentps.core.data._rtStruct import RTStruct +from opentps.core.processing.planDeliverySimulation.planDeliverySimulation import * + +logger = logging.getLogger(__name__) + +#%% +#Output path +#----------- +output_path = os.path.join(os.getcwd(), 'Output', 'planDeliverySimulation') +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Simulation on 4DCT +#-------------------- +# Load plan + +plan_path = "./testData/RP1.2.840.10008.5.1.4.1.1.481.8.dcm" # Generate a plan and save it with writeRTPlan from dicomIO +plan = readDicomPlan(plan_path) + +#%% +# Load 4DCT +dataPath = "path/to/4DCT_folder" # Use a 4DCT with structures availables (are going to be use below) +dataList = readData(dataPath, 1) +CT4D = [data for data in dataList if type(data) is CTImage] +CT4D = Dynamic3DSequence(CT4D) + +# If already have a 3D model, load it and pass it to PlanDeliverySimulation: +# model3D = pickle.load('path/to_model3D') + +#%% +# Create plan delivery object +PDS = PlanDeliverySimulation(plan, CT4D) + +#%% + +# 4D Dose simulation +PDS.simulate4DDose() + +# 4D dynamic simulation +PDS.simulate4DDynamicDose() + +#%% +# Simulate fractionation scenarios +#--------------------------------- +number_of_fractions=5 # number of fractions of the plan +number_of_starting_phases=3 # number of simulations (from a different starting phase) +number_of_fractionation_scenarios=7 # how many scenarios we select where each scenario is a random combination with replacement +PDS.simulate4DDynamicDoseScenarios(number_of_fractions=number_of_fractions, number_of_starting_phases=number_of_starting_phases, number_of_fractionation_scenarios=number_of_fractionation_scenarios) + +#%% +# Plot DVH with bands for a single fraction +midP_struct_path = 'path/to/dicom_struct' +midP_struct = readDicomStruct(midP_struct_path) +dvh_bands = PDS.computeDVHBand4DDD(midP_struct.contours, singleFraction=True) + +# Display DVH + DVH-bands +fig, ax = plt.subplots(1, 1, figsize=(5, 5)) +for dvh_band in dvh_bands: + phigh = ax.plot(dvh_band._dose, dvh_band._volumeHigh, alpha=0) + plow = ax.plot(dvh_band._dose, dvh_band._volumeLow, alpha=0) + pNominal = ax.plot(dvh_band._nominalDVH._dose, dvh_band._nominalDVH._volume, label=dvh_band._roiName) + pfill = ax.fill_between(dvh_band._dose, dvh_band._volumeHigh, dvh_band._volumeLow, alpha=0.2) +ax.set_xlabel("Dose (Gy)") +ax.set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() +plt.show() + +#%% +# Plot DVH with band for the accumulation of 5 fractions +dvh_bands = PDS.computeDVHBand4DDD(midP_struct.contours, singleFraction=False) + +# Display DVH + DVH-bands +fig, ax = plt.subplots(1, 1, figsize=(5, 5)) +for dvh_band in dvh_bands: + phigh = ax.plot(dvh_band._dose, dvh_band._volumeHigh, alpha=0) + plow = ax.plot(dvh_band._dose, dvh_band._volumeLow, alpha=0) + pNominal = ax.plot(dvh_band._nominalDVH._dose, dvh_band._nominalDVH._volume, label=dvh_band._roiName) + pfill = ax.fill_between(dvh_band._dose, dvh_band._volumeHigh, dvh_band._volumeLow, alpha=0.2) +ax.set_xlabel("Dose (Gy)") +ax.set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() +plt.show() +plt.savefig(os.path.join(output_path, 'Dose_4DCT.png')) \ No newline at end of file diff --git a/examples/DoseDeliverySimulation/README.rst b/examples/DoseDeliverySimulation/README.rst new file mode 100644 index 0000000..e06dabf --- /dev/null +++ b/examples/DoseDeliverySimulation/README.rst @@ -0,0 +1,2 @@ +Dose Delivery Simulation +------------------------ \ No newline at end of file diff --git a/examples/DoseDeliverySimulation/run_PBSDeliveryTimings.py b/examples/DoseDeliverySimulation/run_PBSDeliveryTimings.py new file mode 100644 index 0000000..5383d38 --- /dev/null +++ b/examples/DoseDeliverySimulation/run_PBSDeliveryTimings.py @@ -0,0 +1,60 @@ +''' +PBS Delivery Timings +==================== +author: OpenTPS team + +This example will present the basis of PBS delivery timings with openTPS core. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import numpy as np +np.random.seed(42) + +#%% +#import the needed opentps.core packages + +from opentps.core.data.plan._planProtonBeam import PlanProtonBeam +from opentps.core.data.plan._planProtonLayer import PlanProtonLayer +from opentps.core.data.plan._protonPlan import ProtonPlan +from opentps.core.processing.planDeliverySimulation.scanAlgoBeamDeliveryTimings import ScanAlgoBeamDeliveryTimings +from opentps.core.processing.planDeliverySimulation.simpleBeamDeliveryTimings import SimpleBeamDeliveryTimings +from opentps.core.io.dicomIO import readDicomPlan + +#%% +# Create random plan +#------------------- +plan = ProtonPlan() +plan.appendBeam(PlanProtonBeam()) +energies = np.array([130, 140, 150, 160, 170]) +for m in energies: + layer = PlanProtonLayer(m) + x = 10*np.random.random(5) - 5 + y = 10*np.random.random(5) - 5 + mu = 5*np.random.random(5) + + layer.appendSpot(x, y, mu) + plan.beams[0].appendLayer(layer) + + +bdt = SimpleBeamDeliveryTimings(plan) +plan_with_timings = bdt.getPBSTimings(sort_spots="true") + +#%% +# Print plan +#----------- +print(plan_with_timings._beams[0]._layers[0].__dict__) \ No newline at end of file diff --git a/examples/PlanOptimization/.DS_Store b/examples/PlanOptimization/.DS_Store new file mode 100644 index 0000000..2f2e87d Binary files /dev/null and b/examples/PlanOptimization/.DS_Store differ diff --git a/examples/PlanOptimization/README.rst b/examples/PlanOptimization/README.rst new file mode 100644 index 0000000..fdc0a23 --- /dev/null +++ b/examples/PlanOptimization/README.rst @@ -0,0 +1,2 @@ +Plan Optimization +------------------ \ No newline at end of file diff --git a/examples/PlanOptimization/run_4DProtonOptimization.py b/examples/PlanOptimization/run_4DProtonOptimization.py new file mode 100644 index 0000000..618bbad --- /dev/null +++ b/examples/PlanOptimization/run_4DProtonOptimization.py @@ -0,0 +1,332 @@ +''' +4D Proton Optimization +========================= +author: OpenTPS team + +This example shows how to create and optimize a 4D proton plan using OpenTPS. + +running time: ~ 1 hours +''' +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import logging +import numpy as np +from matplotlib import pyplot as plt +import sys +import pydicom +import datetime +sys.path.append('..') + +#%% +# import the needed opentps.core packages +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data.plan._protonPlanDesign import ProtonPlanDesign +from opentps.core.data import DVH +from opentps.core.data import Patient +from opentps.core.data.plan import FidObjective +from opentps.core.io import mcsquareIO +from opentps.core.io.dataLoader import readData +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import loadRTPlan, saveRTPlan +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.processing.planOptimization.planOptimization import IntensityModulationOptimizer +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.io.dicomIO import writeRTDose, readDicomDose + + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Exemple_Robust4DOptimization') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +#CT calibration and BDL +#---------------------- +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#%% +#CT and ROI creation +#--------------------------- +# Generic example: 4DCT composed of 3 CTs : 2 phases and the MidP. +# The anatomy consists of a square target moving vertically, with an organ at risk and soft tissue (muscle) in front of it. +CT4D = [] +ROI4D = [] +for i in range(0, 3): + # ++++Don't delete UIDs to build the simple study+++++++++++++++++++ + studyInstanceUID = pydicom.uid.generate_uid() + ctSeriesInstanceUID = pydicom.uid.generate_uid() + frameOfReferenceUID = pydicom.uid.generate_uid() + # structSeriesInstanceUID = pydicom.uid.generate_uid() + dt = datetime.datetime.now() + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + # CT + patient = Patient() + patient.name = f'Miro_OpenTPS_4DCT' + Patient.id = f'12082024' + Patient.birthDate = dt.strftime('%Y%m%d') + patient.sex = "" + + ctSize = 150 + ct = CTImage(seriesInstanceUID=ctSeriesInstanceUID, frameOfReferenceUID=frameOfReferenceUID) + ct.name = f'CT_Phase_{i}' + ct.patient = patient + ct.studyInstanceUID = studyInstanceUID + + huWater = 50 + huTarget = 100 + huMuscle = 200 + data = huWater * np.ones((ctSize, ctSize, ctSize)) + + # Muscle + data[100:140, 20:130, 55:95] = huMuscle + # OAR + data[70:80, 70:80, 65:85] = huTarget + # TargetVolume + if i == 0 : + data[25:45, 70:100, 65:85] = huTarget + if i == 1 : + data[25:45, 60:90, 65:85] = huTarget + if i == 2 : + data[25:45, 50:80, 65:85] = huTarget + ct.imageArray = data + # writeDicomCT(ct, output_path) + + #---------------------ROI + ROI = [] + + # TargetVolume + TV = ROIMask() + TV.patient = patient + TV.name = 'TV' + TV.color = (255, 0, 0) # red + data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) + if i == 0 : + data[25:45, 70:100, 65:85] = True + if i == 1 : + data[25:45, 60:90, 65:85] = True + if i == 2 : + data[25:45, 50:80, 65:85] = True + TV.imageArray = data + ROI.append(TV) + + # Muscle + Muscle = ROIMask() + Muscle.patient = patient + Muscle.name = 'Muscle' + Muscle.color = (150, 0, 0) + data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) + data[100:140, 20:130, 55:95] = True + Muscle.imageArray = data + ROI.append(Muscle) + + # OAR + OAR = ROIMask() + OAR.patient = patient + OAR.name = 'OAR' + OAR.color = (100, 0, 0) + data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) + data[70:80, 70:80, 65:85] = True + OAR.imageArray = data + ROI.append(OAR) + + # Body + BODY = ROIMask() + BODY.patient = patient + BODY.name = 'Body' + BODY.color = (100, 0, 0) + data = np.ones((ctSize, ctSize, ctSize)).astype(bool) + data[np.where(OAR.imageArray)] = False + data[np.where(Muscle.imageArray)] = False + data[np.where(TV.imageArray)] = False + BODY.imageArray = data + ROI.append(BODY) + + CT4D.append(ct) + ROI4D.append(ROI) + +RefCT = CT4D[1] +RefTV = ROI4D[1][0] +RefOAR = ROI4D[1][2] +RefBody = ROI4D[1][3] + +#%% +# Design plan +#---------------- +beamNames = ["Beam1"] +gantryAngles = [90.] +couchAngles = [0.] + +#%% +# Configure MCsquare +#---------------------- +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.nbPrimaries = 1e3 +mc2.ctCalibration = ctCalibration + +#%% +# Load / Generate new plan +#---------------------- +plan_file = os.path.join(output_path, f"RobustPlan_4D.tps") + +if os.path.isfile(plan_file): + plan = loadRTPlan(plan_file) + logger.info('Plan loaded') + +else: + planDesign = ProtonPlanDesign() + planDesign.ct = RefCT # Here, it's the MidP + planDesign.targetMask = RefTV + planDesign.gantryAngles = gantryAngles + planDesign.beamNames = beamNames + planDesign.couchAngles = couchAngles + planDesign.calibration = ctCalibration + + # Robustness settings + planDesign.robustness.setupSystematicError = [1.6, 1.6, 1.6] # mm (sigma) + planDesign.robustness.setupRandomError = [0.0, 0.0, 0.0] # mm (sigma) + planDesign.robustness.rangeSystematicError = 3.0 # % + + # 4D Evaluation mode + planDesign.robustness.Mode4D = planDesign.robustness.Mode4D.MCsquareAccumulation # Or MCsquareSystematic + planDesign.robustness.selectionStrategy = planDesign.robustness.Strategies.REDUCED_SET # RANDOM not available for MCsquareSystematic + # planDesign.robustness.selectionStrategy = planDesign.robustness.Strategies.ALL (includes diagonals on sphere) + # planDesign.robustness.selectionStrategy = planDesign.robustness.Strategies.RANDOM + planDesign.robustness.numScenarios = 50 # Specify how many random scenarios to simulate, default = 100 + + # # 4D settings : only for the mode MCsquareAccumulation with the RANDOM strategie + # planDesign.robustness.Create4DCTfromRef = True + # planDesign.robustness.SystematicAmplitudeError = 5.0 # % # Only with RANDOM strategie + # planDesign.robustness.RandomAmplitudeError = 5.0 # % + # planDesign.robustness.Dynamic_delivery = True + # planDesign.robustness.SystematicPeriodError = 5.0 # % # Spot timing required. If not, we calculate them with SimpleBeamDeliveryTimings() + # planDesign.robustness.RandomPeriodError = 5.0 # % + # planDesign.robustness.Breathing_period = 1 # x100% # default value + + planDesign.spotSpacing = 10.0 + planDesign.layerSpacing = 10.0 + planDesign.targetMargin = 7 # Enough to encompass target motion + + planDesign.defineTargetMaskAndPrescription(target = RefTV, targetPrescription = 60.) + + plan = planDesign.buildPlan() + plan.rtPlanName = f"RobustPlan_4D" + + # refIndex : + # ACCUMULATED -> Index of the Image in the 4DCT one wish we will accumulate the dose. + ## SYSTEMATIC -> Index of the Image in the 4DCT who will be used as the nominal. So the one closer to the MidP. Or the Midp. + + nominal, scenarios = mc2.compute4DRobustScenarioBeamlets(CT4D, plan, refIndex=1, roi=ROI4D, storePath=output_path) + + plan.planDesign.beamlets = nominal + plan.planDesign.robustness.scenarios = scenarios + plan.planDesign.robustness.numScenarios = len(scenarios) + saveRTPlan(plan, plan_file) + +#%% +# Set objectives +#---------------------- +plan.planDesign.objectives.addFidObjective(RefTV, FidObjective.Metrics.DMAX, limitValue = 63.0, weight = 100.0, robust=True) +plan.planDesign.objectives.addFidObjective(RefTV, FidObjective.Metrics.DMIN, limitValue = 60.0, weight = 100.0, robust=True) +plan.planDesign.objectives.addFidObjective(RefOAR, FidObjective.Metrics.DMAX, limitValue = 40.0, weight = 80.0) +plan.planDesign.objectives.addFidObjective(RefBody, FidObjective.Metrics.DMAX, limitValue = 40.0, weight = 80.0) + +#%% +# Optimize treatment plan +#---------------------- +DoseFile = 'DoseRobustPlan4D' +Dose_file = os.path.join(output_path, DoseFile + '.dcm') +if os.path.isfile(Dose_file): + doseImage = readDicomDose(Dose_file) + print('Dose imported') +else : + plan.planDesign.ROI_cropping = False + solver = IntensityModulationOptimizer(method='Scipy_L-BFGS-B', plan=plan, maxiter=150) + # Optimize treatment plan + doseImage, ps = solver.optimize() + saveRTPlan(plan, os.path.join(output_path, "RobustPlan_4D_weighted.tps")) + writeRTDose(doseImage, output_path, DoseFile) + +#%% +# Display results +#---------------------- +target_DVH = DVH(RefTV, doseImage) +print('TV -> D95 = ' + str(target_DVH.D95) + ' Gy') +print('TV -> D5 = ' + str(target_DVH.D5) + ' Gy') +print('TV -> D5 - D95 = {} Gy'.format(target_DVH.D5 - target_DVH.D95)) + +oar = resampleImage3DOnImage3D(RefOAR, ct) +oar_DVH = DVH(oar, doseImage) +print('OAR -> D95 = ' + str(oar_DVH.D95) + ' Gy') +print('OAR -> DMAX = ' + str(oar_DVH.Dmax) + ' Gy') + +Body = resampleImage3DOnImage3D(RefBody, ct) +Body_DVH = DVH(Body, doseImage) +print('Body -> D95 = ' + str(Body_DVH.D95) + ' Gy') +print('Body -> DMAX = ' + str(Body_DVH.Dmax) + ' Gy') + +#%% +# center of mass +#---------------------- +RefTV = resampleImage3DOnImage3D(RefTV, RefCT) +COM_coord = RefTV.centerOfMass +COM_index = RefTV.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = RefCT.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = RefTV.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, RefCT) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +contourTargetMask0 = ROI4D[0][0].getBinaryContourMask() +img_maskP1 = contourTargetMask0.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask2 = ROI4D[2][0].getBinaryContourMask() +img_maskP2 = contourTargetMask2.imageArray[:, :, Z_coord].transpose(1, 0) +contourOAR = RefOAR.getBinaryContourMask() +img_OAR = contourOAR.imageArray[:, :, Z_coord].transpose(1, 0) + +#%% +# Display dose +#---------------------- +fig, ax = plt.subplots(1, 2, figsize=(12, 5)) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV +ax[0].imshow(img_maskP1, alpha=.2, cmap='binary') +ax[0].imshow(img_maskP2, alpha=.2, cmap='binary') +ax[0].imshow(img_OAR, alpha=.2, cmap='binary') +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +plt.colorbar(dose, ax=ax[0]) +ax[1].plot(target_DVH.histogram[0], target_DVH.histogram[1], label=target_DVH.name) +ax[1].plot(oar_DVH.histogram[0], oar_DVH.histogram[1], label=oar_DVH.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() + +plt.savefig(f'{output_path}/DoseRobustOptimization_4D.png', format = 'png') +plt.show() \ No newline at end of file diff --git a/examples/PlanOptimization/run_4DprotonEvaluation.py b/examples/PlanOptimization/run_4DprotonEvaluation.py new file mode 100644 index 0000000..cb26845 --- /dev/null +++ b/examples/PlanOptimization/run_4DprotonEvaluation.py @@ -0,0 +1,280 @@ +''' +4D Proton Evaluation +========================= +author: OpenTPS team + +This example shows how to evaluate a 4D proton plan using OpenTPS. + +running time: ~ 20 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import datetime +import logging +import pydicom +import datetime + +import numpy as np +from matplotlib import pyplot as plt + +#%% +# import the needed opentps.core packages +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data.plan._protonPlanDesign import ProtonPlanDesign +from opentps.core.data import Patient +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import saveRTPlan, loadRTPlan +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.processing.planEvaluation.robustnessEvaluation import RobustnessEval +from opentps.core.io.dataLoader import readData +from opentps.core.data import DVH + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ + +output_path = os.path.join(os.getcwd(), 'Exemple_Robust4DOptimization') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +#CT calibration and BDL +#---------------------- + +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#%% +# CT and ROI creation +#---------------------- +# Generic example: 4DCT composed of 3 CTs : 2 phases and the MidP. +# The anatomy consists of a square target moving vertically, with an organ at risk and soft tissue (muscle) in front of it. +CT4D = [] +ROI4D = [] +for i in range(0, 3): + # ++++Don't delete UIDs to build the simple study+++++++++++++++++++ + studyInstanceUID = pydicom.uid.generate_uid() + ctSeriesInstanceUID = pydicom.uid.generate_uid() + frameOfReferenceUID = pydicom.uid.generate_uid() + # structSeriesInstanceUID = pydicom.uid.generate_uid() + dt = datetime.datetime.now() + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + # CT + patient = Patient() + patient.name = f'Miro_OpenTPS_4DCT' + Patient.id = f'12082024' + Patient.birthDate = dt.strftime('%Y%m%d') + patient.sex = "" + + ctSize = 150 + ct = CTImage(seriesInstanceUID=ctSeriesInstanceUID, frameOfReferenceUID=frameOfReferenceUID) + ct.name = f'CT_Phase_{i}' + ct.patient = patient + ct.studyInstanceUID = studyInstanceUID + + huWater = 50 + huTarget = 100 + huMuscle = 200 + data = huWater * np.ones((ctSize, ctSize, ctSize)) + + # Muscle + data[100:140, 20:130, 55:95] = huMuscle + # OAR + data[70:80, 70:80, 65:85] = huTarget + # TargetVolume + if i == 0 : + data[25:45, 70:100, 65:85] = huTarget + if i == 1 : + data[25:45, 60:90, 65:85] = huTarget + if i == 2 : + data[25:45, 50:80, 65:85] = huTarget + ct.imageArray = data + # writeDicomCT(ct, output_path) + + #---------------------ROI + ROI = [] + + # TargetVolume + TV = ROIMask() + TV.patient = patient + TV.name = 'TV' + TV.color = (255, 0, 0) # red + data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) + if i == 0 : + data[25:45, 70:100, 65:85] = True + if i == 1 : + data[25:45, 60:90, 65:85] = True + if i == 2 : + data[25:45, 50:80, 65:85] = True + TV.imageArray = data + ROI.append(TV) + + # Muscle + Muscle = ROIMask() + Muscle.patient = patient + Muscle.name = 'Muscle' + Muscle.color = (150, 0, 0) + data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) + data[100:140, 20:130, 55:95] = True + Muscle.imageArray = data + ROI.append(Muscle) + + # OAR + OAR = ROIMask() + OAR.patient = patient + OAR.name = 'OAR' + OAR.color = (100, 0, 0) + data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) + data[70:80, 70:80, 65:85] = True + OAR.imageArray = data + ROI.append(OAR) + + # Body + BODY = ROIMask() + BODY.patient = patient + BODY.name = 'Body' + BODY.color = (100, 0, 0) + data = np.ones((ctSize, ctSize, ctSize)).astype(bool) + data[np.where(OAR.imageArray)] = False + data[np.where(Muscle.imageArray)] = False + data[np.where(TV.imageArray)] = False + BODY.imageArray = data + ROI.append(BODY) + + CT4D.append(ct) + ROI4D.append(ROI) + +RefCT = CT4D[1] +RefTV = ROI4D[1][0] +#%% +# Design plan +#---------------- +beamNames = ["Beam1"] +gantryAngles = [90.] +couchAngles = [0.] +#%% +# Configure MCsquare +#---------------------- +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.nbPrimaries = 1e3 +mc2.statUncertainty = 2. +mc2.ctCalibration = ctCalibration + +#%% +# Load / Generate new plan +#------------------------- +plan_file = os.path.join(output_path, f"RobustPlan_4D_weighted.tps") + +if os.path.isfile(plan_file): + plan = loadRTPlan(plan_file) + logger.info('Plan weighted loaded') +else: + planDesign = ProtonPlanDesign() + planDesign.ct = RefCT # Here, it's the MidP + planDesign.targetMask = RefTV + planDesign.gantryAngles = gantryAngles + planDesign.beamNames = beamNames + planDesign.couchAngles = couchAngles + planDesign.calibration = ctCalibration + + planDesign.spotSpacing = 7.0 + planDesign.layerSpacing = 7.0 + planDesign.targetMargin = 10 # Enough to encompass target motion + + planDesign.defineTargetMaskAndPrescription(target = RefTV, targetPrescription = 60.) + + plan = planDesign.buildPlan() + plan.rtPlanName = f"RobustPlan_4D" + + +#%% +# Load / Generate scenarios +#--------------------------- +scenario_folder = os.path.join(output_path, 'Robustness4D_Test') +if os.path.isdir(scenario_folder): + scenarios = RobustnessEval() + scenarios.selectionStrategy = RobustnessEval.Strategies.REDUCED_SET + # scenarios.selectionStrategy = RobustnessEval.Strategies.ALL + # scenarios.selectionStrategy = RobustnessEval.Strategies.RANDOM + scenarios.setupSystematicError = plan.planDesign.robustnessEval.setupSystematicError + scenarios.setupRandomError = plan.planDesign.robustnessEval.setupRandomError + scenarios.rangeSystematicError = plan.planDesign.robustnessEval.rangeSystematicError + scenarios.load(scenario_folder) +else: + # MCsquare config for scenario dose computation + mc2.nbPrimaries = 1e6 + plan.planDesign.robustnessEval = RobustnessEval() + plan.planDesign.robustnessEval.setupSystematicError = [1.6, 1.6, 1.6] # mm (sigma) + plan.planDesign.robustnessEval.setupRandomError = [0.0, 0.0, 0.0] # mm (sigma) + plan.planDesign.robustnessEval.rangeSystematicError = 3.0 # % + + # 4D Evaluation mode + plan.planDesign.robustnessEval.Mode4D = plan.planDesign.robustnessEval.Mode4D.MCsquareAccumulation # Or MCsquareSystematic + + # # 4D settings : only for the mode MCsquareAccumulation with the RANDOM strategie + # plan.planDesign.robustnessEval.Create4DCTfromRef = True + # plan.planDesign.robustnessEval.SystematicAmplitudeError = 5.0 # % + # plan.planDesign.robustnessEval.RandomAmplitudeError = 5.0 # % + # plan.planDesign.robustnessEval.Dynamic_delivery = True + # plan.planDesign.robustnessEval.SystematicPeriodError = 5.0 # % # Spot timing required. If not, we calculate them with SimpleBeamDeliveryTimings() + # plan.planDesign.robustnessEval.RandomPeriodError = 5.0 # % + # plan.planDesign.robustnessEval.Breathing_period = 1 # x100% + + # Regular scenario sampling + plan.planDesign.robustnessEval.selectionStrategy = plan.planDesign.robustnessEval.Strategies.REDUCED_SET + + # All scenarios (includes diagonals on sphere) + # plan.planDesign.robustnessEval.selectionStrategy = plan.planDesign.robustnessEval.Strategies.ALL + + # Random scenario sampling + # plan.planDesign.robustnessEval.selectionStrategy = plan.planDesign.robustnessEval.Strategies.RANDOM + plan.planDesign.robustnessEval.numScenarios = 50 # Specify how many random scenarios to simulate, default = 100 + + # Run MCsquare simulation + scenarios = mc2.compute4DRobustScenario(CT4D, plan = plan, refIndex = 1, roi = ROI4D) # 4D method + output_folder = os.path.join(output_path, 'Robustness4D_Test') + scenarios.save(output_folder) + + +scenarios.analyzeErrorSpace(RefCT, "D95", RefTV, plan.planDesign.objectives.targetPrescription) +scenarios.printInfo() +scenarios.recomputeDVH([RefTV]) + +#%% +# Show results +#---------------------- +fig, ax = plt.subplots(1, 1, figsize=(5, 5)) +for i, dvh_band in enumerate(scenarios.dvhBands): + color = f'C{i % 10}' + phigh = ax.plot(dvh_band._dose, dvh_band._volumeHigh, alpha=0) + plow = ax.plot(dvh_band._dose, dvh_band._volumeLow, alpha=0) + pNominal = ax.plot(dvh_band._nominalDVH._dose, dvh_band._nominalDVH._volume, label=dvh_band._roiName, color = color) + pfill = ax.fill_between(dvh_band._dose, dvh_band._volumeHigh, dvh_band._volumeLow, alpha=0.2, color=color) +ax.set_xlabel("Dose (Gy)") +ax.set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() +plt.savefig(f'{output_path}/Dose4DEvaluation.png', format = 'png') +plt.show() \ No newline at end of file diff --git a/examples/PlanOptimization/run_SimpleOptimizationPhoton.py b/examples/PlanOptimization/run_SimpleOptimizationPhoton.py new file mode 100644 index 0000000..f2d2c68 --- /dev/null +++ b/examples/PlanOptimization/run_SimpleOptimizationPhoton.py @@ -0,0 +1,221 @@ +''' +Simple IMPT photon plan optimization +==================================== +author: OpenTPS team + +In this example, we will create and optimize a simple Photons plan. + +running time: ~ 15 minutes +''' + +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import os +import logging +import numpy as np +from matplotlib import pyplot as plt +import sys +import copy +from scipy.sparse import csc_matrix +sys.path.append('..') + + +#%% +#import the needed opentps.core packages + +from opentps.core.io.dicomIO import writeRTDose, writeDicomCT, writeRTPlan, writeRTStruct +from opentps.core.processing.planOptimization.tools import evaluateClinical +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data import DVH +from opentps.core.data import Patient +from opentps.core.data.plan import FidObjective +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import saveRTPlan, loadRTPlan +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.processing.planOptimization.planOptimization import IntensityModulationOptimizer +from opentps.core.processing.doseCalculation.photons.cccDoseCalculator import CCCDoseCalculator +from opentps.core.data.plan import PhotonPlanDesign + +logger = logging.getLogger(__name__) + +#%% +#CT calibration +#-------------- + +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) + +#%% +#Create synthetic CT and ROI +#--------------------------- + +patient = Patient() +patient.name = 'Simple_Patient' + +ctSize = 150 +ct = CTImage() +ct.name = 'CT' +ct.patient = patient +# ct.origin = -ctSize/2 * ct.spacing + +huAir = -1024. +huWater = 0 +data = huAir * np.ones((ctSize, ctSize, ctSize)) +data[:, 50:, :] = huWater +ct.imageArray = data +#writeDicomCT(ct, output_path) + +# Struct +roi = ROIMask() +roi.patient = patient +# roi.origin = -ctSize/2 * ct.spacing +roi.name = 'TV' +roi.color = (255, 0, 0) # red +data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +data[100:120, 100:120, 100:120] = True +roi.imageArray = data + +#%% +#Output path +#----------- + +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) + +#%% +# Plan Creation +#-------------- + +# Design plan +beamNames = ["Beam1", "Beam2"] +gantryAngles = [0., 90.] +couchAngles = [0.,0] + +## Dose computation from plan +ccc = CCCDoseCalculator(batchSize= 30) +ccc.ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) + +# Load / Generate new plan +plan_file = os.path.join(output_path, "PhotonPlan_WaterPhantom_cropped_resampled.tps") + +if os.path.isfile(plan_file): ### Remove the False to load the plan + plan = loadRTPlan(plan_file, radiationType='photon') + logger.info('Plan loaded') +else: + planDesign = PhotonPlanDesign() + planDesign.ct = ct + planDesign.targetMask = roi + planDesign.isocenterPosition_mm = None # None take the center of mass of the target + planDesign.gantryAngles = gantryAngles + planDesign.couchAngles = couchAngles + planDesign.beamNames = beamNames + planDesign.calibration = ctCalibration + planDesign.xBeamletSpacing_mm = 5 + planDesign.yBeamletSpacing_mm = 5 + planDesign.targetMargin = 5.0 + planDesign.defineTargetMaskAndPrescription(target = roi, targetPrescription = 20.) + + plan = planDesign.buildPlan() + + beamlets = ccc.computeBeamlets(ct, plan) + doseInfluenceMatrix = copy.deepcopy(beamlets) + + plan.planDesign.beamlets = beamlets + beamlets.storeOnFS(os.path.join(output_path, "BeamletMatrix_" + plan.seriesInstanceUID + ".blm")) + # Save plan with initial spot weights in serialized format (OpenTPS format) + saveRTPlan(plan, plan_file) + + +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMAX, 20.0, 1.0) +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMIN, 20.5, 1.0) + +plan.numberOfFractionsPlanned = 30 + +plan.planDesign.ROI_cropping = False # False, not cropping allows you to keep the dose outside the ROIs and then use the 'shift' evaluation method, which simply shifts the beamlets. +solver = IntensityModulationOptimizer(method='Scipy_L-BFGS-B', plan=plan, maxiter=1000) + +#%% +# Optimize treatment plan +#------------------------ +doseImage, ps = solver.optimize() +writeRTDose(doseImage, output_path) + +# Save plan with updated spot weights in serialized format (OpenTPS format) +plan_file_optimized = os.path.join(output_path, "Plan_WaterPhantom_cropped_resampled_optimized.tps") +saveRTPlan(plan, plan_file_optimized) +# Save plan with updated spot weights in dicom format +plan.patient = patient +# writeRTPlan(plan, output_path, outputFilename = plan.name ) +# writeDicomPhotonRTPlan(plan, output_path ) +#%% +# Compute DVH on resampled contour +#--------------------------------- + +target_DVH = DVH(roi, doseImage) +print('D5 - D95 = {} Gy'.format(target_DVH.D5 - target_DVH.D95)) +clinROI = [roi.name, roi.name] +clinMetric = ["Dmin", "Dmax"] +clinLimit = [19., 21.] +clinObj = {'ROI': clinROI, 'Metric': clinMetric, 'Limit': clinLimit} +print('Clinical evaluation') +evaluateClinical(doseImage, [roi], clinObj) + +#%% +# center of mass +#--------------- +roi = resampleImage3DOnImage3D(roi, ct) +COM_coord = roi.centerOfMass +COM_index = roi.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = roi.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +#%% +# Display dose +#------------- +fig, ax = plt.subplots(1, 3, figsize=(15, 5)) +ax[0].axes.get_xaxis().set_visible(False) +ax[0].axes.get_yaxis().set_visible(False) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +plt.colorbar(dose, ax=ax[0]) +ax[1].plot(target_DVH.histogram[0], target_DVH.histogram[1], label=target_DVH.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +ax[1].grid(True) +ax[1].legend() + +convData = solver.getConvergenceData() +x_data = np.linspace(0, convData['time'], len(convData['func_0'])) +y_data = convData['func_0'] +ax[2].plot(x_data, y_data , 'bo-', lw=2, label='Fidelity') +ax[2].set_xlabel('Time (s)') +ax[2].set_ylabel('Cost') +ax[2].set_yscale('symlog') +ax2 = ax[2].twiny() +ax2.set_xlabel('Iterations') +ax2.set_xlim(0, convData['nIter']) +ax[2].grid(True) +plt.savefig(os.path.join(output_path, 'Dose_SimpleOptimizationPhotons.png')) +plt.show() \ No newline at end of file diff --git a/examples/PlanOptimization/run_SimpleOptimizationProton.py b/examples/PlanOptimization/run_SimpleOptimizationProton.py new file mode 100644 index 0000000..9200f4c --- /dev/null +++ b/examples/PlanOptimization/run_SimpleOptimizationProton.py @@ -0,0 +1,197 @@ +''' +Simple IMPT proton plan optimization +==================================== +author: OpenTPS team + +In this example, we will create and optimize a simple Protons plan. + +running time: ~ 12 minutes +''' +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import math +import os +import sys + +import numpy as np +from matplotlib import pyplot as plt + + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data.plan import ObjectivesList +from opentps.core.data.plan import ProtonPlanDesign +from opentps.core.data import DVH +from opentps.core.data import Patient +from opentps.core.data.plan import FidObjective +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import saveRTPlan, loadRTPlan +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D, resampleImage3D +from opentps.core.processing.planOptimization.planOptimization import IntensityModulationOptimizer + +#%% +#CT calibration and BDL +#---------------------- + +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#%% +#Create synthetic CT and ROI +#--------------------------- + +patient = Patient() +patient.name = 'Patient' + +ctSize = 150 + +ct = CTImage() +ct.name = 'CT' +ct.patient = patient + + +huAir = -1024. +huWater = ctCalibration.convertRSP2HU(1.) +data = huAir * np.ones((ctSize, ctSize, ctSize)) +data[:, 50:, :] = huWater +ct.imageArray = data + +roi = ROIMask() +roi.patient = patient +roi.name = 'TV' +roi.color = (255, 0, 0) # red +data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +data[100:120, 100:120, 100:120] = True +roi.imageArray = data + +#%% +#Configure dose engine +#--------------------- + +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.nbPrimaries = 5e4 +mc2.ctCalibration = ctCalibration + +mc2._independentScoringGrid = True +scoringSpacing = [2, 2, 2] +mc2._scoringVoxelSpacing = scoringSpacing + +#%% +#Design plan +#----------- + +beamNames = ["Beam1"] +gantryAngles = [0.] +couchAngles = [0.] + +planInit = ProtonPlanDesign() +planInit.ct = ct +planInit.gantryAngles = gantryAngles +planInit.beamNames = beamNames +planInit.couchAngles = couchAngles +planInit.calibration = ctCalibration +planInit.spotSpacing = 6.0 +planInit.layerSpacing = 6.0 +planInit.targetMargin = 0.0 +planInit.setScoringParameters(scoringSpacing=[2, 2, 2], adapt_gridSize_to_new_spacing=True) +# needs to be called after scoringGrid settings but prior to spot placement +planInit.defineTargetMaskAndPrescription(target = roi, targetPrescription = 20.) + +plan = planInit.buildPlan() # Spot placement +plan.PlanName = "NewPlan" + +beamlets = mc2.computeBeamlets(ct, plan, roi=[roi]) +plan.planDesign.beamlets = beamlets +# doseImageRef = beamlets.toDoseImage() + +#%% +#objectives +#---------- + +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMAX, 20.0, 1.0) +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMIN, 20.5, 1.0) +# Other examples of objectives +# plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMEAN, 20, 1.0) +# plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DUNIFORM, 20, 1.0) +# plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DVHMIN, 19, 1.0, volume = 95) +# plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DVHMAX, 21, 1.0, volume = 5) +# plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.EUDMIN, 19.5, 1.0, EUDa = 0.2) +# plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.EUDMAX, 20, 1.0, EUDa = 1) +# plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.EUDUNIFORM, 20.5, 1.0, EUDa = 0.5) +# plan.planDesign.objectives.addFidObjective(BODY, FidObjective.Metrics.DFALLOFF, weight=10, fallOffDistance=1, fallOffLowDoseLevel=0, fallOffHighDoseLevel=21) + +#%% +#Optimize plan +#------------- + +solver = IntensityModulationOptimizer(method='Scipy_L-BFGS-B', plan=plan, maxiter=50) +doseImage, ps = solver.optimize() + +#%% +#Final dose computation +#---------------------- + +mc2.nbPrimaries = 1e7 +doseImage = mc2.computeDose(ct, plan) + +#%% +#Plots +#----- + +# Compute DVH on resampled contour +roiResampled = resampleImage3D(roi, origin=ct.origin, spacing=scoringSpacing) +target_DVH = DVH(roiResampled, doseImage) +print('D95 = ' + str(target_DVH.D95) + ' Gy') +print('D5 = ' + str(target_DVH.D5) + ' Gy') +print('D5 - D95 = {} Gy'.format(target_DVH.D5 - target_DVH.D95)) + +# center of mass +roi = resampleImage3DOnImage3D(roi, ct) +COM_coord = roi.centerOfMass +COM_index = roi.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = roi.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +#Output path +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) + +# Display dose +fig, ax = plt.subplots(1, 2, figsize=(12, 5)) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +plt.colorbar(dose, ax=ax[0]) +ax[1].plot(target_DVH.histogram[0], target_DVH.histogram[1], label=target_DVH.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() +plt.savefig(os.path.join(output_path, 'SimpleOpti1.png'),format = 'png') +plt.show() \ No newline at end of file diff --git a/examples/PlanOptimization/run_beamletFreeOpti.py b/examples/PlanOptimization/run_beamletFreeOpti.py new file mode 100644 index 0000000..18b7ee0 --- /dev/null +++ b/examples/PlanOptimization/run_beamletFreeOpti.py @@ -0,0 +1,198 @@ +''' +Beamlet Free Optimization +========================= +author: OpenTPS team + +This example will present the basis of beamlet optimization with openTPS core. + +running time: ~ 15 minutes +''' +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import numpy as np +import os +from matplotlib import pyplot as plt + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data.plan import ObjectivesList +from opentps.core.data.plan._protonPlanDesign import ProtonPlanDesign +from opentps.core.data import DVH +from opentps.core.data import Patient +from opentps.core.data.plan import FidObjective +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D + +#%% +#Output path +#----------- + +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) + + +#%% +#Generic CT creation +#------------------- +#we will first create a generic CT of a box fill with water and air + +#calibratioin of the CT +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#creation of the patient object +patient = Patient() +patient.name = 'Patient' + +#size of the 3D box +ctSize = 150 + +#creation of the CTImage object +ct = CTImage() +ct.name = 'CT' +ct.patient = patient + +huAir = -1024. #Hounsfield unit of water +huWater = ctCalibration.convertRSP2HU(1.) #convert a stopping power of 1. to HU units +data = huAir * np.ones((ctSize, ctSize, ctSize)) +data[:, 50:, :] = huWater +ct.imageArray = data #the CT generic image is created + +#%% +#Region of interest +#------------------ +#we will now create a region of interest which is a small 3D box of size 20*20*20 + +roi = ROIMask() +roi.patient = patient +roi.name = 'TV' +roi.color = (255, 0, 0) # red +data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +data[100:120, 100:120, 100:120] = True +roi.imageArray = data + +image = plt.imshow(ct.imageArray[110,:,:],cmap='Blues') +plt.colorbar(image) +plt.contour(roi.imageArray[110,:,:],colors="red") +plt.title("Created CT with ROI") +plt.text(5,40,"Air",color= 'black') +plt.text(5,100,"Water",color = 'white') +plt.text(106,111,"TV",color ='red') +plt.savefig(os.path.join(output_path,'beamFree1.png'),format = 'png') +plt.show() + +#%% +#Configuration of Mcsquare +#------------------------- +#To configure the MCsquare calculator we need to calibrate it with the CT calibration obtained above + +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.ctCalibration = ctCalibration +mc2.nbPrimaries = 1e7 + +#%% +# Plan Creation +#-------------- +#We will now create a plan and set objectives for the optimization and set a goal of 20Gy to the target + +# Design plan + +beamNames = ["Beam1"] +gantryAngles = [0.] +couchAngles = [0.] + +# Generate new plan + +planDesign = ProtonPlanDesign() #create a new plan +planDesign.ct = ct +planDesign.gantryAngles = gantryAngles +planDesign.beamNames = beamNames +planDesign.couchAngles = couchAngles +planDesign.calibration = ctCalibration +planDesign.spotSpacing = 5.0 +planDesign.layerSpacing = 5.0 +planDesign.targetMargin = 5.0 +# needs to be called prior to spot placement +planDesign.defineTargetMaskAndPrescription(target = roi, targetPrescription = 20.) + + +plan = planDesign.buildPlan() # Spot placement +plan.PlanName = "NewPlan" + +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMAX, 20.0, 1.0) +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMIN, 20.0, 1.0) + +#%% +#Mcsquare beamlet free planOptimization +#-------------------------------------- +#Now that we have every needed objects we can compute the optimization through MCsquare. :warning: It may take some time to compute. + +doseImage = mc2.optimizeBeamletFree(ct, plan, [roi]) + +#%% +#Dose volume histogram +#--------------------- + +target_DVH = DVH(roi, doseImage) +print('D95 = ' + str(target_DVH.D95) + ' Gy') +print('D5 = ' + str(target_DVH.D5) + ' Gy') +print('D5 - D95 = {} Gy'.format(target_DVH.D5 - target_DVH.D95)) + +#%% +#Center of mass +#-------------- +#Here we look at the part of the 3D CT image where "stuff is happening" by getting the CoM. We use the function resampleImage3DOnImage3D to the same array size for both images. + +roi = resampleImage3DOnImage3D(roi, ct) +COM_coord = roi.centerOfMass +COM_index = roi.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = roi.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +#%% +#Plot of the dose +#---------------- + +fig, ax = plt.subplots(1, 2, figsize=(12, 5)) +ax[0].axes.get_xaxis().set_visible(False) +ax[0].axes.get_yaxis().set_visible(False) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +plt.colorbar(dose, ax=ax[0]) +ax[1].plot(target_DVH.histogram[0], target_DVH.histogram[1], label=target_DVH.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +ax[0].set_title("Computed dose") +ax[1].set_title("DVH") +plt.grid(True) +plt.legend() +plt.savefig(os.path.join(output_path,'beamFree2.png'),format = 'png') +plt.show() diff --git a/examples/PlanOptimization/run_boundConstraintsOpti.py b/examples/PlanOptimization/run_boundConstraintsOpti.py new file mode 100644 index 0000000..c5ca325 --- /dev/null +++ b/examples/PlanOptimization/run_boundConstraintsOpti.py @@ -0,0 +1,203 @@ +''' +Bound Constraint Optimization +============================= +author: OpenTPS team + +In this example, we optimize an ion plan (Protons) using the BoundConstraintsOptimizer function. +This function allows optimization with constraints on the Monitor Unit (MU) values of each spot. +It helps to stay as close as possible to reality when certain machines cannot accept MU/spot values that are too high or too low. + +running time: ~ 15 minutes +''' + +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import math +import os +import logging +import numpy as np +from matplotlib import pyplot as plt +import sys +sys.path.append('..') + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data.plan import ObjectivesList +from opentps.core.data.plan import ProtonPlanDesign +from opentps.core.data import DVH +from opentps.core.data import Patient +from opentps.core.data.plan import FidObjective +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import saveRTPlan, loadRTPlan +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D, resampleImage3D +from opentps.core.processing.planOptimization.planOptimization import BoundConstraintsOptimizer, IntensityModulationOptimizer +logger = logging.getLogger(__name__) + +#%% +#Output path +#----------- +#We will create an output folder to store the results of this example +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + + +#%% +#Generic CT creation +#------------------- +#we will first create a generic CT of a box fill with water and air + +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +patient = Patient() +patient.name = 'Patient' + +ctSize = 150 + +ct = CTImage() +ct.name = 'CT' +ct.patient = patient + +huAir = -1024. +huWater = ctCalibration.convertRSP2HU(1.) +data = huAir * np.ones((ctSize, ctSize, ctSize)) +data[:, 50:, :] = huWater +ct.imageArray = data + +#%% +#Region of interest +#------------------ +#we will now create a region of interest wich is a small 3D box of size 20*20*20 + +roi = ROIMask() +roi.patient = patient +roi.name = 'TV' +roi.color = (255, 0, 0) # red +data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +data[100:120, 100:120, 100:120] = True +roi.imageArray = data + +#%% +#Configuration of Mcsquare +#------------------------- +#To configure the MCsquare calculator we need to calibrate it with the CT calibration obtained above + +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.ctCalibration = ctCalibration +mc2.nbPrimaries = 5e4 + +#%% +#Plan Creation +#------------- + +# Design plan +beamNames = ["Beam1"] +gantryAngles = [0.] +couchAngles = [0.] + +# Load / Generate new plan +plan_file = os.path.join(output_path,"Plan_WaterPhantom_cropped_resampled.tps") + +if os.path.isfile(plan_file): + plan = loadRTPlan(plan_file) + logger.info('Plan loaded') +else: + planInit = ProtonPlanDesign() + planInit.ct = ct + planInit.gantryAngles = gantryAngles + planInit.beamNames = beamNames + planInit.couchAngles = couchAngles + planInit.calibration = ctCalibration + planInit.spotSpacing = 5.0 + planInit.layerSpacing = 5.0 + planInit.targetMargin = 5.0 + planInit.setScoringParameters(scoringSpacing=[2, 2, 2], adapt_gridSize_to_new_spacing=True) + # needs to be called after scoringGrid settings but prior to spot placement + planInit.defineTargetMaskAndPrescription(target = roi, targetPrescription = 20.) + + plan = planInit.buildPlan() # Spot placement + plan.PlanName = "NewPlan" + + beamlets = mc2.computeBeamlets(ct, plan, roi=[roi]) + plan.planDesign.beamlets = beamlets + + beamlets.storeOnFS(os.path.join(output_path, "BeamletMatrix_" + plan.seriesInstanceUID + ".blm")) + + saveRTPlan(plan, plan_file) + +# Set objectives (attribut is already initialized in planDesign object) +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMAX, 20.0, 1.0) +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMIN, 20.5, 1.0) + +solver = BoundConstraintsOptimizer(method='Scipy_L-BFGS-B', plan=plan, maxiter=50, bounds=(0.2, 50)) + +#%% +#Optimize treatment plan +#----------------------- +doseImage, ps = solver.optimize() + +#%% +#Dose volume histogram +#--------------------- + +target_DVH = DVH(roi, doseImage) +print('D95 = ' + str(target_DVH.D95) + ' Gy') +print('D5 = ' + str(target_DVH.D5) + ' Gy') +print('D5 - D95 = {} Gy'.format(target_DVH.D5 - target_DVH.D95)) + +#%% +#Center of mass +#-------------- +#Here we look at the part of the 3D CT image where "stuff is happening" by getting the CoM. We use the function resampleImage3DOnImage3D to the same array size for both images. + +roi = resampleImage3DOnImage3D(roi, ct) +COM_coord = roi.centerOfMass +COM_index = roi.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = roi.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +#%% +#Plot of the dose +#---------------- + +fig, ax = plt.subplots(1, 2, figsize=(12, 5)) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +plt.colorbar(dose, ax=ax[0]) +ax[1].plot(target_DVH.histogram[0], target_DVH.histogram[1], label=target_DVH.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() +plt.show() +plt.savefig(f'{output_path}/Dose_BoundContraintOpti.png', format = 'png') +plt.show() \ No newline at end of file diff --git a/examples/PlanOptimization/run_evaluatePhotonRobustness.py b/examples/PlanOptimization/run_evaluatePhotonRobustness.py new file mode 100644 index 0000000..4e0af3b --- /dev/null +++ b/examples/PlanOptimization/run_evaluatePhotonRobustness.py @@ -0,0 +1,174 @@ +''' +Evaluate Photon Plan Robustness +========================= +author: OpenTPS team + +This example shows how to evaluate a photon plan robustness using OpenTPS. + +running time: ~ 15 minutes +''' +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import logging +import numpy as np + +from matplotlib import pyplot as plt + +#%% +# import the needed opentps.core packages +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data import Patient +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import saveRTPlan, loadRTPlan +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.planEvaluation.robustnessEvaluation import RobustnessEvalPhoton +from opentps.core.processing.doseCalculation.photons.cccDoseCalculator import CCCDoseCalculator +from opentps.core.data.plan import PhotonPlanDesign + + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Photon_Robust_Output_Example') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +#CT calibration and BDL +#---------------------- + # Generic example: box of water with squared target +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#%% +# CT and ROI creation +#---------------------- +patient = Patient() +patient.name = 'Patient' + +ctSize = 150 +ct = CTImage() +ct.name = 'CT' +ct.patient = patient + +huAir = -1024. +huWater = 0 +data = huAir * np.ones((ctSize, ctSize, ctSize)) +data[:, 50:, :] = huWater +ct.imageArray = data + +roi = ROIMask() +roi.patient = patient +roi.name = 'TV' +roi.color = (255, 0, 0) # red +data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +data[100:120, 100:120, 100:120] = True +roi.imageArray = data + +#%% +# Create output folder +if not os.path.isdir(output_path): + os.mkdir(output_path) + +# Design plan +beamNames = ["Beam1", "Beam2"] +gantryAngles = [0., 90.] +couchAngles = [0.,0] +#%% +# Configure MCsquare +ccc = CCCDoseCalculator(batchSize= 30) +ccc.ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) + +#%% +# Load / Generate new plan +plan_file = os.path.join(output_path, "Plan_Photon_WaterPhantom_notCropped_optimized.tps") +# plan_file = os.path.join(output_path, "Plan_Photon_WaterPhantom_cropped_optimized.tps") + +if os.path.isfile(plan_file): + plan = loadRTPlan(plan_file, 'photon') + logger.info('Plan loaded') +else: + planDesign = PhotonPlanDesign() + planDesign.ct = ct + planDesign.targetMask = roi + planDesign.isocenterPosition_mm = None # None take the center of mass of the target + planDesign.gantryAngles = gantryAngles + planDesign.couchAngles = couchAngles + planDesign.beamNames = beamNames + planDesign.calibration = ctCalibration + planDesign.xBeamletSpacing_mm = 5 + planDesign.yBeamletSpacing_mm = 5 + planDesign.targetMargin = 5.0 + planDesign.defineTargetMaskAndPrescription(target = roi, targetPrescription = 20.) + + plan = planDesign.buildPlan() +#%% +# Load / Generate scenarios +scenario_folder = os.path.join(output_path, "RobustnessTest") +if os.path.isdir(scenario_folder): + scenarios = RobustnessEvalPhoton() + scenarios.selectionStrategy = RobustnessEvalPhoton.Strategies.DEFAULT + scenarios.setupSystematicError = plan.planDesign.robustnessEval.setupSystematicError + scenarios.setupRandomError = plan.planDesign.robustnessEval.setupRandomError + scenarios.load(scenario_folder) +else: + + # Robust config for scenario dose computation + plan.planDesign.robustnessEval = RobustnessEvalPhoton() + plan.planDesign.robustnessEval.setupSystematicError = [1.6, 1.6, 1.6] #sigma (mm) + plan.planDesign.robustnessEval.setupRandomError = [1.4, 1.4, 1.4] #sigma (mm) + + # Strategy selection + plan.planDesign.robustnessEval.selectionStrategy = plan.planDesign.robustnessEval.Strategies.REDUCED_SET + # plan.planDesign.robustnessEval.selectionStrategy = plan.planDesign.robustnessEval.Strategies.ALL + # plan.planDesign.robustnessEval.selectionStrategy = plan.planDesign.robustnessEval.Strategies.RANDOM + # plan.planDesign.robustnessEval.NumScenarios = 50 + + plan.planDesign.robustnessEval.doseDistributionType = "Nominal" + + plan.patient = None + + # run MCsquare simulation + scenarios = ccc.computeRobustScenario(ct, plan, roi = [roi], robustMode = "Shift") # 'Simulation' for total recomputation + output_folder = os.path.join(output_path, "RobustnessTest") + scenarios.save(output_folder) + +#%% +# Robustness analysis +scenarios.recomputeDVH([roi]) +scenarios.analyzeErrorSpace(ct, "D95", roi, plan.planDesign.objectives.targetPrescription) +scenarios.printInfo() + +#%% +# Display DVH + DVH-bands +fig, ax = plt.subplots(1, 1, figsize=(5, 5)) +for dvh_band in scenarios.dvhBands: + phigh = ax.plot(dvh_band._dose, dvh_band._volumeHigh, alpha=0) + plow = ax.plot(dvh_band._dose, dvh_band._volumeLow, alpha=0) + pNominal = ax.plot(dvh_band._nominalDVH._dose, dvh_band._nominalDVH._volume, label=dvh_band._roiName, color = 'C0') + pfill = ax.fill_between(dvh_band._dose, dvh_band._volumeHigh, dvh_band._volumeLow, alpha=0.2, color='C0') +ax.set_xlabel("Dose (Gy)") +ax.set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() +plt.savefig(os.path.join(output_path, 'Evaluation_RobustOptimizationPhotons.png')) +plt.show() \ No newline at end of file diff --git a/examples/PlanOptimization/run_evaluateProtonRobustness.py b/examples/PlanOptimization/run_evaluateProtonRobustness.py new file mode 100644 index 0000000..0dc2069 --- /dev/null +++ b/examples/PlanOptimization/run_evaluateProtonRobustness.py @@ -0,0 +1,185 @@ +''' +Evaluate Proton Plan Robustness +========================= +author: OpenTPS team + +In this example, we evaluate an optimized ion plan. +It is possible to assess range and setup errors and generate DVHs. + +running time: ~ 45 minutes +''' +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import datetime +import logging + +import numpy as np +from matplotlib import pyplot as plt + +#%% +# import the needed opentps.core packages +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data.plan._protonPlanDesign import ProtonPlanDesign +from opentps.core.data import Patient +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import saveRTPlan, loadRTPlan +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.processing.planEvaluation.robustnessEvaluation import RobustnessEval + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Proton_Robust_Output_Example') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +#CT calibration and BDL +#---------------------- +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#%% +# CT and ROI creation +# ---------------------- +# Generic example: box of water with squared target +patient = Patient() +patient.name = 'Patient' + +ctSize = 150 + +ct = CTImage() +ct.name = 'CT' +ct.patient = patient + + +huAir = -1024. +huWater = ctCalibration.convertRSP2HU(1.) +data = huAir * np.ones((ctSize, ctSize, ctSize)) +data[:, 50:, :] = huWater +ct.imageArray = data + +roi = ROIMask() +roi.patient = patient +roi.name = 'TV' +roi.color = (255, 0, 0) # red +data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +data[100:120, 100:120, 100:120] = True +roi.imageArray = data + +#%% +# Design plan +#---------------- +beamNames = ["Beam1"] +gantryAngles = [90.] +couchAngles = [0.] +#%% +# Configure MCsquare +#--------------------- +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.nbPrimaries = 1e3 +mc2.statUncertainty = 2. +mc2.ctCalibration = ctCalibration + +#%% +# Load / Generate new plan +#---------------------- +plan_file = os.path.join(output_path, "Plan_Proton_WaterPhantom_cropped_optimized.tps") + +if os.path.isfile(plan_file): + plan = loadRTPlan(plan_file) + print('Plan loaded') +else: + planInit = ProtonPlanDesign() + planInit.ct = ct + planInit.gantryAngles = gantryAngles + planInit.beamNames = beamNames + planInit.couchAngles = couchAngles + planInit.calibration = ctCalibration + planInit.spotSpacing = 10.0 + planInit.layerSpacing = 10.0 + planInit.targetMargin = 5.0 + planInit.setScoringParameters(scoringSpacing=[2, 2, 2], adapt_gridSize_to_new_spacing=True) + # needs to be called after scoringGrid settings but prior to spot placement + planInit.defineTargetMaskAndPrescription(target = roi, targetPrescription = 20.) + + plan = planInit.buildPlan() # Spot placement + plan.PlanName = "NewPlan" +#%% +# Load / Generate scenarios +#---------------------- +scenario_folder = os.path.join(output_path,'RobustnessTest') +if os.path.isdir(scenario_folder): + scenarios = RobustnessEval() + scenarios.selectionStrategy = RobustnessEval.Strategies.ALL + scenarios.setupSystematicError = plan.planDesign.robustnessEval.setupSystematicError + scenarios.setupRandomError = plan.planDesign.robustnessEval.setupRandomError + scenarios.rangeSystematicError = plan.planDesign.robustnessEval.rangeSystematicError + scenarios.load(scenario_folder) +else: + # MCsquare config for scenario dose computation + mc2.nbPrimaries = 1e7 + plan.planDesign.robustnessEval = RobustnessEval() + plan.planDesign.robustnessEval.setupSystematicError = [5.0, 5.0, 5.0] # mm + plan.planDesign.robustnessEval.setupRandomError = [0.0, 0.0, 0.0] # mm (sigma) + plan.planDesign.robustnessEval.rangeSystematicError = 3.0 # % + + # Regular scenario sampling + plan.planDesign.robustnessEval.selectionStrategy = plan.planDesign.robustnessEval.Strategies.REDUCED_SET + + # All scenarios (includes diagonals on sphere) + # plan.planDesign.robustnessEval.selectionStrategy = plan.planDesign.robustnessEval.Strategies.ALL + + # Random scenario sampling + #plan.planDesign.robustnessEval.selectionStrategy = plan.planDesign.robustnessEval.Strategies.RANDOM + plan.planDesign.robustnessEval.numScenarios = 30 # specify how many random scenarios to simulate, default = 100 + + plan.patient = None + # run MCsquare simulation + scenarios = mc2.computeRobustScenario(ct, plan, [roi]) + output_folder = os.path.join(output_path, "RobustnessTest") + scenarios.save(output_folder) + +#%% +# Robustness analysis +#-------------------- +scenarios.analyzeErrorSpace(ct, "D95", roi, plan.planDesign.objectives.targetPrescription) +scenarios.printInfo() +scenarios.recomputeDVH([roi]) + +#%% +# Display DVH + DVH-bands +#----------------------- +fig, ax = plt.subplots(1, 1, figsize=(5, 5)) +for dvh_band in scenarios.dvhBands: + phigh = ax.plot(dvh_band._dose, dvh_band._volumeHigh, alpha=0) + plow = ax.plot(dvh_band._dose, dvh_band._volumeLow, alpha=0) + pNominal = ax.plot(dvh_band._nominalDVH._dose, dvh_band._nominalDVH._volume, label=dvh_band._roiName, color = 'C0') + pfill = ax.fill_between(dvh_band._dose, dvh_band._volumeHigh, dvh_band._volumeLow, alpha=0.2, color='C0') +ax.set_xlabel("Dose (Gy)") +ax.set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() +plt.savefig(f'{output_path}/EvaluateRobustness.png', format = 'png') +plt.show() \ No newline at end of file diff --git a/examples/PlanOptimization/run_robustOptimizationProtons.py b/examples/PlanOptimization/run_robustOptimizationProtons.py new file mode 100644 index 0000000..0b051b3 --- /dev/null +++ b/examples/PlanOptimization/run_robustOptimizationProtons.py @@ -0,0 +1,220 @@ +''' +Robust Proton Plan Optimization +========================= +author: OpenTPS team + +In this example, we create and optimize a robust proton plan. +The setup and range errors are configurable. + +running time: ~ 20 minutes +''' +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import os +import logging +import numpy as np +from matplotlib import pyplot as plt +import sys +sys.path.append('..') + +#%% +# import the needed opentps.core packages +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.data.plan._protonPlanDesign import ProtonPlanDesign +from opentps.core.data.plan import RobustnessProton +from opentps.core.data import DVH +from opentps.core.data import Patient +from opentps.core.data.plan import FidObjective +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import loadRTPlan, saveRTPlan +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.processing.planOptimization.planOptimization import IntensityModulationOptimizer + + +logger = logging.getLogger(__name__) +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Proton_Robust_Output_Example') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +#CT calibration and BDL +#---------------------- +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#%% +#Create synthetic CT and ROI +#--------------------------- + +patient = Patient() +patient.name = 'Patient' + +ctSize = 150 + +ct = CTImage() +ct.name = 'CT' +ct.patient = patient + +huAir = -1024. +huWater = ctCalibration.convertRSP2HU(1.) +data = huAir * np.ones((ctSize, ctSize, ctSize)) +data[:, 50:, :] = huWater +ct.imageArray = data + +roi = ROIMask() +roi.patient = patient +roi.name = 'TV' +roi.color = (255, 0, 0) # red +data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +data[100:120, 100:120, 100:120] = True +roi.imageArray = data + +#%% +# Design plan +#---------------- +beamNames = ["Beam1"] +gantryAngles = [0.] +couchAngles = [0.] + +#%% +# Create output folder +#---------------------- +if not os.path.isdir(output_path): + os.mkdir(output_path) + +#%% +# Configure MCsquare +#--------------------- +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.nbPrimaries = 1e3 +mc2.ctCalibration = ctCalibration + +#%% +# Load / Generate new plan +#---------------------- +plan_file = os.path.join(output_path, "RobustPlan_notCropped.tps") + +if os.path.isfile(plan_file): + plan = loadRTPlan(plan_file) + logger.info('Plan loaded') +else: + planDesign = ProtonPlanDesign() + planDesign.ct = ct + planDesign.gantryAngles = gantryAngles + planDesign.beamNames = beamNames + planDesign.couchAngles = couchAngles + planDesign.calibration = ctCalibration + # Robustness settings + planDesign.robustness = RobustnessProton() + planDesign.robustness.setupSystematicError = [1.6, 1.6, 1.6] # mm + planDesign.robustness.setupRandomError = [0.0, 0.0, 0.0] # mm (sigma) + planDesign.robustness.rangeSystematicError = 5.0 # % + + # Regular scenario sampling + planDesign.robustness.selectionStrategy = planDesign.robustness.Strategies.REDUCED_SET + + # All scenarios (includes diagonals on sphere) + # planDesign.robustness.selectionStrategy = planDesign.robustness.Strategies.ALL + + # Random scenario sampling + # planDesign.robustness.selectionStrategy = planDesign.robustness.Strategies.RANDOM + planDesign.robustness.numScenarios = 5 # specify how many random scenarios to simulate, default = 100 + + planDesign.spotSpacing = 7.0 + planDesign.layerSpacing = 6.0 + planDesign.targetMargin = max(planDesign.spotSpacing, planDesign.layerSpacing) + max(planDesign.robustness.setupSystematicError) + # scoringGridSize = [int(math.floor(i / j * k)) for i, j, k in zip(ct.gridSize, scoringSpacing, ct.spacing)] + # planDesign.objectives.setScoringParameters(ct, scoringGridSize, scoringSpacing) + planDesign.defineTargetMaskAndPrescription(target = roi, targetPrescription = 20.) # needs to be called prior spot placement + plan = planDesign.buildPlan() # Spot placement + plan.PlanName = "RobustPlan" + + nominal, scenarios = mc2.computeRobustScenarioBeamlets(ct, plan, roi=[roi], storePath=output_path) + plan.planDesign.beamlets = nominal + plan.planDesign.robustness.scenarios = scenarios + plan.planDesign.robustness.numScenarios = len(scenarios) + + + #saveRTPlan(plan, plan_file) + + + +saveRTPlan(plan, plan_file) + +#%% +# Set objectives (attribut is already initialized in planDesign object) +#---------------------- +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMAX, 20.0, 1.0, robust=True) +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMIN, 20.5, 1.0, robust=True) + +solver = IntensityModulationOptimizer(method='Scipy_L-BFGS-B', plan=plan, maxiter=50) + +#%% +# Optimize treatment plan +#---------------------- +doseImage, ps = solver.optimize() + +plan_file = os.path.join(output_path, "Plan_Proton_WaterPhantom_cropped_optimized.tps") +saveRTPlan(plan, plan_file, unloadBeamlets=False) + +#%% +# Compute DVH +#---------------------- +target_DVH = DVH(roi, doseImage) +print('D95 = ' + str(target_DVH.D95) + ' Gy') +print('D5 = ' + str(target_DVH.D5) + ' Gy') +print('D5 - D95 = {} Gy'.format(target_DVH.D5 - target_DVH.D95)) + +#%% +# Center of mass +#---------------------- +roi = resampleImage3DOnImage3D(roi, ct) +COM_coord = roi.centerOfMass +COM_index = roi.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = roi.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +#%% +# Display dose +#---------------------- +fig, ax = plt.subplots(1, 2, figsize=(12, 5)) +ax[0].axes.get_xaxis().set_visible(False) +ax[0].axes.get_yaxis().set_visible(False) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +plt.colorbar(dose, ax=ax[0]) +ax[1].plot(target_DVH.histogram[0], target_DVH.histogram[1], label=target_DVH.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +plt.grid(True) +plt.legend() +plt.savefig(os.path.join(output_path, 'Dose_RobustOptimizationProtons.png')) +plt.show() \ No newline at end of file diff --git a/examples/PlanOptimization/run_simpleOptimization_createDicomStudy.py b/examples/PlanOptimization/run_simpleOptimization_createDicomStudy.py new file mode 100644 index 0000000..9a47395 --- /dev/null +++ b/examples/PlanOptimization/run_simpleOptimization_createDicomStudy.py @@ -0,0 +1,281 @@ +''' +Simple ion plan optimization and DICOM study creation +===================================================== +author: OpenTPS team + +In this example, we will create and optimize a simple ion (Proton) plan. +The generated CT, the plan, and the dose will be saved as DICOM files. + +running time: ~ 15 minutes +''' + +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import os +import logging +import numpy as np +from matplotlib import pyplot as plt +import sys +import datetime +import pydicom +sys.path.append('..') + +#%% +#import the needed opentps.core packages + +from opentps.core.io.dicomIO import writeRTPlan, writeDicomCT, writeRTDose, writeRTStruct +from opentps.core.processing.planOptimization.tools import evaluateClinical +from opentps.core.data.images import CTImage, DoseImage +from opentps.core.data.images import ROIMask +from opentps.core.data.plan import ObjectivesList +from opentps.core.data.plan._protonPlanDesign import ProtonPlanDesign +from opentps.core.data import DVH +from opentps.core.data import Patient +from opentps.core.data import RTStruct +from opentps.core.data.plan import FidObjective +from opentps.core.io import mcsquareIO +from opentps.core.io.scannerReader import readScanner +from opentps.core.io.serializedObjectIO import saveRTPlan, loadRTPlan +from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig +from opentps.core.processing.doseCalculation.protons.mcsquareDoseCalculator import MCsquareDoseCalculator +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D, resampleImage3D +from opentps.core.processing.planOptimization.planOptimization import IntensityModulationOptimizer +from opentps.core.data.plan import ProtonPlan + +logger = logging.getLogger(__name__) + +#%% +#CT calibration and BDL +#---------------------- + +ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) +bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) + +#%% +#Create the DICOM files +#---------------------- + + # ++++Don't delete UIDs to build the simple study+++++++++++++++++++ +studyInstanceUID = pydicom.uid.generate_uid() +doseSeriesInstanceUID = pydicom.uid.generate_uid() +planSeriesInstanceUID = pydicom.uid.generate_uid() +ctSeriesInstanceUID = pydicom.uid.generate_uid() +frameOfReferenceUID = pydicom.uid.generate_uid() +# structSeriesInstanceUID = pydicom.uid.generate_uid() +dt = datetime.datetime.now() +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +#%% +#Output path +#----------- + +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) + + +#%% +#Generic CT creation +#------------------- +#we will first create a generic CT of a box fill with water and air + +patient = Patient() +patient.name = 'Simple_Patient' +Patient.id = 'Simple_Patient' +Patient.birthDate = dt.strftime('%Y%m%d') +patient.sex = "" + +ctSize = 150 +ct = CTImage(seriesInstanceUID=ctSeriesInstanceUID, frameOfReferenceUID=frameOfReferenceUID) +ct.name = 'CT' +ct.patient = patient +ct.studyInstanceUID = studyInstanceUID + +huAir = -1024. +huWater = ctCalibration.convertRSP2HU(1.) +data = huAir * np.ones((ctSize, ctSize, ctSize)) +data[:, 50:, :] = huWater +ct.imageArray = data +writeDicomCT(ct, output_path) + +#%% +#Region of interest +#------------------ +#we will now create a region of interest wich is a small 3D box of size 20*20*20 + +roi = ROIMask() +roi.patient = patient +roi.name = 'TV' +roi.color = (255, 0, 0) # red +data = np.zeros((ctSize, ctSize, ctSize)).astype(bool) +data[100:120, 100:120, 100:120] = True +roi.imageArray = data + +#%% +#Configuration of Mcsquare +#------------------------- +#To configure the MCsquare calculator we need to calibrate it with the CT calibration obtained above + +mc2 = MCsquareDoseCalculator() +mc2.beamModel = bdl +mc2.nbPrimaries = 5e4 +mc2.ctCalibration = ctCalibration + +#%% +# Plan Creation +#-------------- + + # Design plan +beamNames = ["Beam1"] +gantryAngles = [0.] +couchAngles = [0.] + +# Load / Generate new plan +plan_file = os.path.join(output_path, "Plan_WaterPhantom_cropped_resampled.tps") + +if os.path.isfile(plan_file): + plan = loadRTPlan(plan_file) + logger.info('Plan loaded') +else: + planDesign = ProtonPlanDesign() + planDesign.ct = ct + planDesign.targetMask = roi + planDesign.gantryAngles = gantryAngles + planDesign.beamNames = beamNames + planDesign.couchAngles = couchAngles + planDesign.calibration = ctCalibration + planDesign.spotSpacing = 5.0 + planDesign.layerSpacing = 5.0 + planDesign.targetMargin = 5.0 + planDesign.setScoringParameters(scoringSpacing=[2, 2, 2], adapt_gridSize_to_new_spacing=True) + planDesign.defineTargetMaskAndPrescription(target = roi, targetPrescription = 20.) # needs to be called prior spot placement + + plan = planDesign.buildPlan() # Spot placement + plan.rtPlanName = "Simple_Patient" + + beamlets = mc2.computeBeamlets(ct, plan, roi=[roi]) + plan.planDesign.beamlets = beamlets + beamlets.storeOnFS(os.path.join(output_path, "BeamletMatrix_" + plan.seriesInstanceUID + ".blm")) + # Save plan with initial spot weights in serialized format (OpenTPS format) + saveRTPlan(plan, plan_file) + writeRTPlan(plan, output_path) + +#%% +#objectives +#---------- + +# Set objectives (attribut is already initialized in planDesign object) +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMAX, 20.0, 1.0) +plan.planDesign.objectives.addFidObjective(roi, FidObjective.Metrics.DMIN, 20.5, 1.0) + +#%% +#Optimize plan +#------------- + +plan.seriesInstanceUID = planSeriesInstanceUID +plan.studyInstanceUID = studyInstanceUID +plan.frameOfReferenceUID = frameOfReferenceUID +plan.rtPlanGeometry = "TREATMENT_DEVICE" + +solver = IntensityModulationOptimizer(method='Scipy_L-BFGS-B', plan=plan, maxiter=1000) +# Optimize treatment plan +doseImage, ps = solver.optimize() + +# Save plan with updated spot weights in serialized format (OpenTPS format) +plan_file_optimized = os.path.join(output_path, "Plan_WaterPhantom_cropped_resampled_optimized.tps") +saveRTPlan(plan, plan_file_optimized) +# Save plan with updated spot weights in dicom format +plan.patient = patient +writeRTPlan(plan, output_path) + +#%% +#Dose volume histogram +#--------------------- + +target_DVH = DVH(roi, doseImage) +print('D5 - D95 = {} Gy'.format(target_DVH.D5 - target_DVH.D95)) +clinROI = [roi.name, roi.name] +clinMetric = ["Dmin", "Dmax"] +clinLimit = [19., 21.] +clinObj = {'ROI': clinROI, 'Metric': clinMetric, 'Limit': clinLimit} +print('Clinical evaluation') +evaluateClinical(doseImage, [roi], clinObj) + +doseImage.referencePlan = plan +doseImage.referenceCT = ct +doseImage.patient = patient +doseImage.studyInstanceUID = studyInstanceUID +doseImage.frameOfReferenceUID = frameOfReferenceUID +doseImage.sopClassUID = '1.2.840.10008.5.1.4.1.1.481.2' +doseImage.mediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.2' +doseImage.sopInstanceUID = pydicom.uid.generate_uid() +doseImage.studyTime = dt.strftime('%H%M%S.%f') +doseImage.studyDate = dt.strftime('%Y%m%d') +doseImage.SOPInstanceUID = doseImage.sopInstanceUID + +if not hasattr(ProtonPlan, "SOPInstanceUID"): + ProtonPlan.SOPInstanceUID = property(lambda self: self.sopInstanceUID) + + +writeRTDose(doseImage, output_path) + +#%% +#Center of mass +#-------------- +#Here we look at the part of the 3D CT image where "stuff is happening" by getting the CoM. We use the function resampleImage3DOnImage3D to the same array size for both images. + +roi = resampleImage3DOnImage3D(roi, ct) +COM_coord = roi.centerOfMass +COM_index = roi.getVoxelIndexFromPosition(COM_coord) +Z_coord = COM_index[2] + +img_ct = ct.imageArray[:, :, Z_coord].transpose(1, 0) +contourTargetMask = roi.getBinaryContourMask() +img_mask = contourTargetMask.imageArray[:, :, Z_coord].transpose(1, 0) +img_dose = resampleImage3DOnImage3D(doseImage, ct) +img_dose = img_dose.imageArray[:, :, Z_coord].transpose(1, 0) + +#%% +#Plot of the dose +#---------------- + +fig, ax = plt.subplots(1, 3, figsize=(15, 5)) +ax[0].axes.get_xaxis().set_visible(False) +ax[0].axes.get_yaxis().set_visible(False) +ax[0].imshow(img_ct, cmap='gray') +ax[0].imshow(img_mask, alpha=.2, cmap='binary') # PTV +dose = ax[0].imshow(img_dose, cmap='jet', alpha=.2) +plt.colorbar(dose, ax=ax[0]) +ax[1].plot(target_DVH.histogram[0], target_DVH.histogram[1], label=target_DVH.name) +ax[1].set_xlabel("Dose (Gy)") +ax[1].set_ylabel("Volume (%)") +ax[1].grid(True) +ax[1].legend() + +convData = solver.getConvergenceData() +ax[2].plot(np.arange(0, convData['time'], convData['time'] / convData['nIter']), convData['func_0'], 'bo-', lw=2, + label='Fidelity') +ax[2].set_xlabel('Time (s)') +ax[2].set_ylabel('Cost') +ax[2].set_yscale('symlog') +ax2 = ax[2].twiny() +ax2.set_xlabel('Iterations') +ax2.set_xlim(0, convData['nIter']) +ax[2].grid(True) + +plt.savefig(f'{output_path}/Dose_SimpleOptimization.png', format = 'png') +plt.show() \ No newline at end of file diff --git a/examples/Segmentation/README.rst b/examples/Segmentation/README.rst new file mode 100644 index 0000000..1266231 --- /dev/null +++ b/examples/Segmentation/README.rst @@ -0,0 +1,2 @@ +Segmentation +------------ diff --git a/examples/Segmentation/run_Segmentation.py b/examples/Segmentation/run_Segmentation.py new file mode 100644 index 0000000..df8c987 --- /dev/null +++ b/examples/Segmentation/run_Segmentation.py @@ -0,0 +1,103 @@ +''' +Segmentation +========================= +*Author* : OpenTPS team + +This example will present the basis of segmentation with openTPS core. +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import numpy as np +import matplotlib.pyplot as plt +import logging +import os + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images import CTImage +from opentps.core.processing.segmentation.segmentation3D import applyThreshold +from opentps.core.processing.segmentation.segmentationCT import SegmentationCT +from opentps.core.examples.syntheticData import * + +logger = logging.getLogger(__name__) + +#%% +#Output path +#----------- + +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) + + +#%% +#Genrerate synthetic CT image and segment it +#------------------------------------------------ + +# GENERATE SYNTHETIC CT IMAGE +ct = createSynthetic3DCT() + +# APPLY THRESHOLD SEGMENTATION +mask = applyThreshold(ct, -750) + +# APPLY CT BODY SEGMENTATION +seg = SegmentationCT(ct) +body = seg.segmentBody() +bones = seg.segmentBones() +lungs = seg.segmentLungs() + +# CHECK RESULTS +assert (body.imageArray[50,100,80] == True) & (body.imageArray[0,0,0] == False), f"Wrong body segmentation" +assert (bones.imageArray[85,100,50] == True) & (bones.imageArray[85,110,50] == False), f"Wrong bones segmentation" +assert (lungs.imageArray[120,100,35] == True) & (lungs.imageArray[50,100,35] == False), f"Wrong lungs segmentation" + +#%% +# DISPLAY RESULTS +#----------------- + +fig, ax = plt.subplots(2, 5) +fig.tight_layout() +y_slice = 100 +z_slice = 35 #round(ct.imageArray.shape[2] / 2) - 1 +ax[0,0].imshow(ct.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,0].title.set_text('CT') +ax[0,1].imshow(mask.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1) +ax[0,1].title.set_text('Threshold') +ax[0,2].imshow(body.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1) +ax[0,2].title.set_text('Body') +ax[0,3].imshow(bones.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1) +ax[0,3].title.set_text('Bones') +ax[0,4].imshow(lungs.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1) +ax[0,4].title.set_text('Lungs') + +ax[1,0].imshow(ct.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,0].title.set_text('CT') +ax[1,1].imshow(mask.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1) +ax[1,1].title.set_text('Threshold') +ax[1,2].imshow(body.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1) +ax[1,2].title.set_text('Body') +ax[1,3].imshow(bones.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1) +ax[1,3].title.set_text('Bones') +ax[1,4].imshow(lungs.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=0, vmax=1) +ax[1,4].title.set_text('Lungs') + +plt.savefig(os.path.join(output_path, 'Example_Segmentation.png')) + +print('Segmentation example completed') +plt.show() \ No newline at end of file diff --git a/examples/dynamicData/README.rst b/examples/dynamicData/README.rst new file mode 100644 index 0000000..2ba5c48 --- /dev/null +++ b/examples/dynamicData/README.rst @@ -0,0 +1,2 @@ +Dynamic Data +------------- \ No newline at end of file diff --git a/examples/dynamicData/exampleInterFractionChanges.py b/examples/dynamicData/exampleInterFractionChanges.py new file mode 100644 index 0000000..8e02dd7 --- /dev/null +++ b/examples/dynamicData/exampleInterFractionChanges.py @@ -0,0 +1,201 @@ +''' +Inter Fraction Changes +======================= +author: OpenTPS team + +This example shows how to apply inter-fraction changes to a dynamic 3D model, including baseline shift, translation, rotation, and shrinkage of the target organ. + +running time: ~ 10 minutes +''' +#%% +# Setting up the environment in google collab +#--------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import copy +import matplotlib.pyplot as plt +import time +import os +import sys +import cProfile as profile +import logging +pr = profile.Profile() +pr.enable() + +currentWorkingDir = os.getcwd() +print('currentWorkingDir :', currentWorkingDir) +# while not os.path.isfile(currentWorkingDir + '/main.py'): currentWorkingDir = os.path.dirname(currentWorkingDir) +sys.path.append(os.path.dirname(currentWorkingDir)) + +#%% +#import the needed opentps.core packages + +from opentps.core.io.serializedObjectIO import loadDataStructure +from opentps.core.processing.imageProcessing.syntheticDeformation import applyBaselineShift, shrinkOrgan +from opentps.core.processing.imageProcessing.resampler3D import crop3DDataAroundBox +from opentps.core.processing.segmentation.segmentation3D import getBoxAroundROI +from opentps.core.processing.deformableDataAugmentationToolBox.modelManipFunctions import * +from opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData, applyTransform3D +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.examples.syntheticData import createSynthetic4DCT + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ + +output_path = os.path.join(os.getcwd(), 'Output', 'exampleInterFractionChanges') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# paths selection +#---------------- +organ = 'lung' +studyFolder = 'FDGorFAZA_study/' +patientFolder = 'Patient_4' +patientComplement = '/1/FDG1' +basePath = '/DATA2/public/' +dataPath = basePath + organ + '/' + studyFolder + patientFolder + patientComplement + '/dynModAndROIs_bodyCropped.p' + +#dataPath = './ImageData/lung/Patient_4/1/FDG1/dynModAndROIs_bodyCropped.p' + +# ctList, roiList = createSynthetic4DCT(returnTumorMask=True) + +#%% +# parameters selection +#--------------------- +bodyContourToUse = 'Body' +targetContourToUse = 'GTV T' +lungContourToUse = 'R lung' + +contourToAddShift = targetContourToUse + +croppingContoursUsedXYZ = [targetContourToUse, bodyContourToUse, targetContourToUse] +marginInMM = [50, 0, 100] + +# interfraction changes parameters +baselineShift = [-5, 0, 10] +# baselineShift = [0, 0, 0] +translation = [-5, 3, 10] +# translation = [0, 0, 0] +rotation = [0, 5, 0] +# rotation = [0, 0, 0] +shrinkSize = [8, 5, 2] +# shrinkSize = [0, 0, 0] + +# GPU used +tryGPU = True +usedGPU = 0 + +try: + import cupy + cupy.cuda.Device(usedGPU).use() +except: + print('Module Cupy not found or selected GPU not available') + +# data loading +patient = loadDataStructure(dataPath)[0] +dynMod = patient.getPatientDataOfType("Dynamic3DModel")[0] +rtStruct = patient.getPatientDataOfType("RTStruct")[0] + +print('Available ROIs') +rtStruct.print_ROINames() + +gtvContour = rtStruct.getContourByName(targetContourToUse) +GTVMask = gtvContour.getBinaryMask(origin=dynMod.midp.origin, gridSize=dynMod.midp.gridSize, spacing=dynMod.midp.spacing) +gtvBox = getBoxAroundROI(GTVMask) +GTVCenterOfMass = gtvContour.getCenterOfMass(dynMod.midp.origin, dynMod.midp.gridSize, dynMod.midp.spacing) +GTVCenterOfMassInVoxels = getVoxelIndexFromPosition(GTVCenterOfMass, dynMod.midp) + +#%% +# get the body contour to adjust the crop in the direction of the DRR projection +bodyContour = rtStruct.getContourByName(bodyContourToUse) +bodyMask = bodyContour.getBinaryMask(origin=dynMod.midp.origin, gridSize=dynMod.midp.gridSize, spacing=dynMod.midp.spacing) +bodyBox = getBoxAroundROI(bodyMask) +print('Body Box from contour', bodyBox) + +#%% +#Define the cropping box +croppingBox = [[], [], []] +for i in range(3): + if croppingContoursUsedXYZ[i] == bodyContourToUse: + croppingBox[i] = bodyBox[i] + elif croppingContoursUsedXYZ[i] == targetContourToUse: + croppingBox[i] = gtvBox[i] + +#%% +# crop the model data using the box +crop3DDataAroundBox(dynMod, croppingBox, marginInMM=marginInMM) +GTVCenterOfMass = gtvContour.getCenterOfMass(dynMod.midp.origin, dynMod.midp.gridSize, dynMod.midp.spacing) +GTVCenterOfMassInVoxels = getVoxelIndexFromPosition(GTVCenterOfMass, dynMod.midp) + +#%% +# get the mask in cropped version (the dynMod.midp is now cropped so its origin and gridSize has changed) +GTVMask = gtvContour.getBinaryMask(origin=dynMod.midp.origin, gridSize=dynMod.midp.gridSize, + spacing=dynMod.midp.spacing) +dynModCopy = copy.deepcopy(dynMod) +GTVMaskCopy = copy.deepcopy(GTVMask) + +startTime = time.time() + +print('-' * 50) +if contourToAddShift == targetContourToUse: + print('Apply baseline shift of', baselineShift, 'to', contourToAddShift) + dynMod, GTVMask = applyBaselineShift(dynMod, GTVMask, baselineShift, tryGPU=tryGPU) +else: + print('Not implemented in this script --> must use the get contour by name function') + +print('-' * 50) +translateData(dynMod, translationInMM=translation, tryGPU=tryGPU) +translateData(GTVMask, translationInMM=translation, tryGPU=tryGPU) + +print('-'*50) +rotateData(dynMod, rotAnglesInDeg=rotation) +rotateData(GTVMask, rotAnglesInDeg=rotation) + +print('-' * 50) +shrinkedDynMod, shrinkedOrganMask = shrinkOrgan(dynMod, GTVMask, shrinkSize=shrinkSize, tryGPU=tryGPU) +shrinkedDynMod.name = 'MidP_ShrinkedGTV' + +resampleImage3DOnImage3D(shrinkedDynMod.midp, dynModCopy.midp, inPlace=True, tryGPU=tryGPU) +resampleImage3DOnImage3D(shrinkedOrganMask, GTVMaskCopy, inPlace=True, tryGPU=tryGPU) + +print('-' * 50) + +stopTime = time.time() +print('time:', stopTime-startTime) + +patient.appendPatientData(shrinkedDynMod) +patient.appendPatientData(shrinkedOrganMask) + +fig, ax = plt.subplots(1, 4) +fig.suptitle('Example of baseline shift, translate, rotate and shrink') +ax[0].imshow(np.rot90(dynModCopy.midp.imageArray[:, GTVCenterOfMassInVoxels[1], :]), cmap='gray') +# ax[0].imshow(GTVMaskCopy.imageArray[:, GTVCenterOfMassInVoxels[1], :], alpha=0.5, cmap='Reds') +ax[0].set_title('Initial image') +ax[1].imshow(np.rot90(shrinkedDynMod.midp.imageArray[:, GTVCenterOfMassInVoxels[1], :]), cmap='gray') +# ax[1].imshow(shrinkedOrganMask.imageArray[:, GTVCenterOfMassInVoxels[1], :], alpha=0.5, cmap='Reds') +ax[1].set_title('After inter fraction changes') +ax[2].imshow(np.rot90(dynModCopy.midp.imageArray[:, GTVCenterOfMassInVoxels[1], :] - shrinkedDynMod.midp.imageArray[:, GTVCenterOfMassInVoxels[1], :]), cmap='gray') +ax[2].set_title('Image difference') +ax[3].imshow(np.rot90(GTVMaskCopy.imageArray[:, GTVCenterOfMassInVoxels[1], :] ^ shrinkedOrganMask.imageArray[:, GTVCenterOfMassInVoxels[1], :]), cmap='gray') +ax[3].set_title('Mask difference') +plt.savefig(os.path.join(output_path, 'exampleInterFractionChanges.png')) + +pr.disable() +# pr.print_stats(sort='time') +plt.show() \ No newline at end of file diff --git a/examples/dynamicData/run_exampleApplyBaselineShiftToModel.py b/examples/dynamicData/run_exampleApplyBaselineShiftToModel.py new file mode 100644 index 0000000..56a8008 --- /dev/null +++ b/examples/dynamicData/run_exampleApplyBaselineShiftToModel.py @@ -0,0 +1,158 @@ +''' +Applying Baseline Shift to a Model +================================== +author: OpenTPS team + +This example demonstrates how to apply a baseline shift to a model and visualize the results. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import numpy as np +import matplotlib.pyplot as plt +import logging +import os + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.processing.imageProcessing.syntheticDeformation import applyBaselineShift +from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel +from opentps.core.data.dynamicData._dynamic3DSequence import Dynamic3DSequence +from opentps.core.examples.syntheticData import * + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Output', 'ExampleApplyBaselineShiftToModel') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Generate synthetic 4DCT, mask, and MidP +#---------------------------------------- + +# GENERATE SYNTHETIC 4DCT +CT4D = createSynthetic4DCT() + +# GENERATE MASK +mask = np.full(CT4D.dyn3DImageList[0].gridSize, 0) +mask[38:52, 87:103, 39:54] = 1 +roi = ROIMask(imageArray=mask, origin=[0, 0, 0], spacing=[1, 1, 1.5]) + +# GENERATE MIDP +Model = Dynamic3DModel() +Model.computeMidPositionImage(CT4D, 0, tryGPU=True) + +#%% +# Apply baseline shift +#--------------------- + +ModelShifted, maskShifted = applyBaselineShift(Model, roi, [5, 0, 10]) + +#%% +# Regenerate 4D sequences from models +#------------------------------------ + +CT4DRegen = Dynamic3DSequence() +for i in range(len(CT4D.dyn3DImageList)): + CT4DRegen.dyn3DImageList.append(Model.generate3DImage(i / len(CT4D.dyn3DImageList), amplitude=1)) +CT4DShifted = Dynamic3DSequence() +for i in range(len(CT4D.dyn3DImageList)): + CT4DShifted.dyn3DImageList.append(ModelShifted.generate3DImage(i/len(CT4D.dyn3DImageList), amplitude=1)) + +#%% +# Display results +#---------------- +fig, ax = plt.subplots(3, 7) +fig.tight_layout() +y_slice = 95 +ax[1, 0].imshow(Model.midp.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1, 0].title.set_text('MidP') +ax[2, 0].imshow(ModelShifted.midp.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[2, 0].title.set_text('MidP shifted') + +average = CT4D.dyn3DImageList[0].copy() +for i in range(len(CT4D.dyn3DImageList)-1): + average._imageArray += CT4D.dyn3DImageList[i+1]._imageArray +average._imageArray = average.imageArray/len(CT4D.dyn3DImageList) +averageRegen = CT4DRegen.dyn3DImageList[0].copy() +for i in range(len(CT4DRegen.dyn3DImageList) - 1): + averageRegen._imageArray += CT4DRegen.dyn3DImageList[i + 1]._imageArray +averageRegen._imageArray = averageRegen.imageArray / len(CT4DRegen.dyn3DImageList) +averageShifted = CT4DShifted.dyn3DImageList[0].copy() +for i in range(len(CT4DShifted.dyn3DImageList) - 1): + averageShifted._imageArray += CT4DShifted.dyn3DImageList[i + 1]._imageArray +averageShifted._imageArray = averageShifted.imageArray / len(CT4DShifted.dyn3DImageList) + +ax[0, 1].imshow(average.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0, 1].title.set_text('Average') +ax[1, 1].imshow(averageRegen.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1, 1].title.set_text('Gen average') +ax[2, 1].imshow(averageShifted.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[2, 1].title.set_text('Gen average shifted') + +averageRegen._imageArray -= average._imageArray +averageShifted._imageArray -= average._imageArray +average._imageArray -= average._imageArray +ax[0, 0].imshow(average.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0, 0].title.set_text('-') +ax[0, 2].imshow(average.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0, 2].title.set_text('-') +ax[1, 2].imshow(averageRegen.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1, 2].title.set_text('Gen average diff') +ax[2, 2].imshow(averageShifted.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[2, 2].title.set_text('Gen average shifted diff') + +ax[0, 3].imshow(CT4D.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0, 3].title.set_text('Phase 0') +ax[1, 3].imshow(CT4DRegen.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1, 3].title.set_text('Gen phase 0') +ax[2, 3].imshow(CT4DShifted.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[2, 3].title.set_text('Gen phase 0 shifted') + +ax[0, 4].imshow(CT4D.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0, 4].title.set_text('Phase 1') +ax[1, 4].imshow(CT4DRegen.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1, 4].title.set_text('Gen phase 1') +ax[2, 4].imshow(CT4DShifted.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[2, 4].title.set_text('Gen phase 1 shifted') + +ax[0, 5].imshow(CT4D.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0, 5].title.set_text('Phase 2') +ax[1, 5].imshow(CT4DRegen.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1, 5].title.set_text('Gen phase 2') +ax[2, 5].imshow(CT4DShifted.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[2, 5].title.set_text('Gen phase 2 shifted') + +ax[0, 6].imshow(CT4D.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0, 6].title.set_text('Phase 3') +ax[1, 6].imshow(CT4DRegen.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1, 6].title.set_text('Gen phase 3') +ax[2, 6].imshow(CT4DShifted.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[2, 6].title.set_text('Gen phase 3 shifted') + +plt.savefig(os.path.join(output_path, 'BaselinesSHift.png')) +print('done') +plt.show() + diff --git a/examples/dynamicData/run_exampleDeformableBreathigDataAugmentation.py b/examples/dynamicData/run_exampleDeformableBreathigDataAugmentation.py new file mode 100644 index 0000000..5c97305 --- /dev/null +++ b/examples/dynamicData/run_exampleDeformableBreathigDataAugmentation.py @@ -0,0 +1,164 @@ +''' +Deformable Breathing Data Augmentation +====================================== +author: OpenTPS team + +This example shows how to create a synthetic 4DCT, generate a mid-position CT, and create a dynamic sequence from breathing signals and the mid-position CT. The example also demonstrates how to visualize the generated dynamic sequence. + +running time: ~ 7 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import os +import sys +currentWorkingDir = os.getcwd() +sys.path.append(currentWorkingDir) +import numpy as np +from pathlib import Path +import math +import logging +import matplotlib.pyplot as plt + +#%% +#import the needed opentps.core packages +from matplotlib.animation import FuncAnimation +from opentps.core.data.dynamicData._breathingSignals import SyntheticBreathingSignal +from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel +from opentps.core.processing.deformableDataAugmentationToolBox.generateDynamicSequencesFromModel import generateDynSeqFromBreathingSignalsAndModel +from opentps.core.processing.imageProcessing.imageTransform3D import getVoxelIndexFromPosition +from opentps.core.processing.imageProcessing.resampler3D import resample +from opentps.core.examples.syntheticData import* + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ + +output_path = os.path.join(os.getcwd(), 'Output', 'ExampleDeformableBreathingDataAugmentation') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Generate synthetic 4DCT +#------------------------ + +CT4D = createSynthetic4DCT(numberOfPhases=10) + # CT4D = resample(CT4D, gridSize=(80, 50, 50)) + +plt.figure() +fig = plt.gcf() +def updateAnim(imageIndex): + + fig.clear() + plt.imshow(np.rot90(CT4D.dyn3DImageList[imageIndex].imageArray[:, 95, :])) + +anim = FuncAnimation(fig, updateAnim, frames=len(CT4D.dyn3DImageList), interval=300) +anim.save(os.path.join(output_path, 'anim.gif')) +plt.show() + +#%% +# Generate MidP +#-------------- + +dynMod = Dynamic3DModel() +dynMod.computeMidPositionImage(CT4D, 0, tryGPU=True) + +print(dynMod.midp.origin, dynMod.midp.spacing, dynMod.midp.gridSize) +print('Resample model image') +dynMod = resample(dynMod, gridSize=(80, 50, 50)) +print('after resampling', dynMod.midp.origin, dynMod.midp.spacing, dynMod.midp.gridSize) + +# option 3 +for field in dynMod.deformationList: + print('Resample model field') + field.resample(spacing=dynMod.midp.spacing, gridSize=dynMod.midp.gridSize, origin=dynMod.midp.origin) + print('after resampling', field.origin, field.spacing, field.gridSize) + +simulationTime = 10 +amplitude = 10 + +newSignal = SyntheticBreathingSignal(amplitude=amplitude, + breathingPeriod=4, + meanNoise=0, + varianceNoise=0, + samplingPeriod=0.2, + simulationTime=simulationTime, + coeffMin=0, + coeffMax=0, + meanEvent=0/30, + meanEventApnea=0) + +newSignal.generate1DBreathingSignal() +linearIncrease = np.linspace(0.8, 10, newSignal.breathingSignal.shape[0]) + +newSignal.breathingSignal = newSignal.breathingSignal * linearIncrease + +newSignal2 = SyntheticBreathingSignal() +newSignal2.breathingSignal = -newSignal.breathingSignal + +signalList = [newSignal.breathingSignal, newSignal2.breathingSignal] + +pointRLung = np.array([50, 100, 50]) +pointLLung = np.array([120, 100, 50]) + +## get points in voxels --> for the plot, not necessary for the process example +pointRLungInVoxel = getVoxelIndexFromPosition(pointRLung, dynMod.midp) +pointLLungInVoxel = getVoxelIndexFromPosition(pointLLung, dynMod.midp) + +pointList = [pointRLung, pointLLung] +pointVoxelList = [pointRLungInVoxel, pointLLungInVoxel] + +## to show signals and ROIs +prop_cycle = plt.rcParams['axes.prop_cycle'] +colors = prop_cycle.by_key()['color'] +plt.figure(figsize=(12, 6)) +signalAx = plt.subplot(2, 1, 2) +for pointIndex, point in enumerate(pointList): + ax = plt.subplot(2, 2 * len(pointList), 2 * pointIndex + 1) + ax.set_title('Slice Y:' + str(pointVoxelList[pointIndex][1])) + ax.imshow(np.rot90(dynMod.midp.imageArray[:, pointVoxelList[pointIndex][1], :])) + ax.scatter([pointVoxelList[pointIndex][0]], [dynMod.midp.imageArray.shape[2] - pointVoxelList[pointIndex][2]], c=colors[pointIndex], marker="x", s=100) + ax2 = plt.subplot(2, 2 * len(pointList), 2 * pointIndex + 2) + ax2.set_title('Slice Z:' + str(pointVoxelList[pointIndex][2])) + ax2.imshow(np.rot90(dynMod.midp.imageArray[:, :, pointVoxelList[pointIndex][2]], 3)) + ax2.scatter([pointVoxelList[pointIndex][0]], [pointVoxelList[pointIndex][1]], c=colors[pointIndex], marker="x", s=100) + signalAx.plot(newSignal.timestamps / 1000, signalList[pointIndex], c=colors[pointIndex]) + +signalAx.set_xlabel('Time (s)') +signalAx.set_ylabel('Deformation amplitude in Z direction (mm)') +plt.show() + +#%% + +dynSeq = generateDynSeqFromBreathingSignalsAndModel(dynMod, signalList, pointList, dimensionUsed='Z', outputType=np.int16) +dynSeq.breathingPeriod = newSignal.breathingPeriod +dynSeq.timingsList = newSignal.timestamps + +print('/'*80, '\n', '/'*80) + +plt.figure() +fig = plt.gcf() +def updateAnim(imageIndex): + + fig.clear() + plt.imshow(np.rot90(dynSeq.dyn3DImageList[imageIndex].imageArray[:, 29, :])) + +anim = FuncAnimation(fig, updateAnim, frames=len(dynSeq.dyn3DImageList), interval=300) +anim.save(os.path.join(output_path, 'anim3.gif')) +plt.show() \ No newline at end of file diff --git a/examples/dynamicData/run_exampleDeformationFromWeightMaps.py b/examples/dynamicData/run_exampleDeformationFromWeightMaps.py new file mode 100644 index 0000000..9353a03 --- /dev/null +++ b/examples/dynamicData/run_exampleDeformationFromWeightMaps.py @@ -0,0 +1,152 @@ +''' +Deformation from Weight Maps +========================= +author: OpenTPS team + +This example demonstrates how to apply a deformation to a model using weight maps and visualize the results. + +running time: ~ 7 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import numpy as np +import matplotlib.pyplot as plt +import logging +import os + +#%% +#import the needed opentps.core packages + +from opentps.core.processing.imageProcessing import resampler3D +from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel +from opentps.core.examples.syntheticData import createSynthetic4DCT +from opentps.core.processing.deformableDataAugmentationToolBox.weightMaps import generateDeformationFromTrackers, generateDeformationFromTrackersAndWeightMaps + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Output', 'ExampleDeformationFromWeightMaps') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Generate synthetic 4DCT and MidP +#--------------------------------- + +# GENERATE SYNTHETIC 4D INPUT SEQUENCE +CT4D = createSynthetic4DCT(numberOfPhases=10) + +# CREATE TRACKER POSITIONS +trackers = [[30, 75, 40], + [70, 75, 40], + [100, 75, 40], + [140, 75, 40]] + +# GENERATE MIDP +Model4D = Dynamic3DModel() +Model4D.computeMidPositionImage(CT4D, 0, tryGPU=True) + +# GENERATE ADDITIONAL PHASES +df1, wm = generateDeformationFromTrackers(Model4D, [0, 0, 2/4, 2/4], [1, 1, 1, 1], trackers) +im1 = df1.deformImage(Model4D.midp, fillValue='closest') +df2, wm = generateDeformationFromTrackers(Model4D, [0.5/4, 0.5/4, 1.5/4, 1.5/4], [1, 1, 1, 1], trackers) +im2 = df2.deformImage(Model4D.midp, fillValue='closest') +df3 = generateDeformationFromTrackersAndWeightMaps(Model4D, [0, 0, 2/4, 2/4], [2, 2, 2, 2], wm) +im3 = df3.deformImage(Model4D.midp, fillValue='closest') + +# RESAMPLE WEIGHT MAPS TO IMAGE RESOLUTION +for i in range(len(trackers)): + resampler3D.resampleImage3DOnImage3D(wm[i], Model4D.midp, inPlace=True, fillValue=-1024.) + +#%% +# Display results +#---------------- +fig, ax = plt.subplots(2, 5) +ax[0,0].imshow(Model4D.midp.imageArray[:, 50, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +s0 = wm[0].imageArray[:, 50, :].T[::-1, ::1] +s1 = wm[1].imageArray[:, 50, :].T[::-1, ::1] +s2 = wm[2].imageArray[:, 50, :].T[::-1, ::1] +s3 = wm[3].imageArray[:, 50, :].T[::-1, ::1] +ax[0,1].imshow(s0, cmap='Reds', origin='upper', vmin=0, vmax=1) +ax[0,2].imshow(s1, cmap='Reds', origin='upper', vmin=0, vmax=1) +ax[0,3].imshow(s2, cmap='Blues', origin='upper', vmin=0, vmax=1) +ax[0,4].imshow(s3, cmap='Blues', origin='upper', vmin=0, vmax=1) +ax[0,0].plot(trackers[0][0],100-trackers[0][2],'ro') +ax[0,0].plot(trackers[1][0],100-trackers[1][2],'ro') +ax[0,0].plot(trackers[2][0],100-trackers[2][2],'bo') +ax[0,0].plot(trackers[3][0],100-trackers[3][2],'bo') +ax[0,1].plot(trackers[0][0],100-trackers[0][2],'ro') +ax[0,2].plot(trackers[1][0],100-trackers[1][2],'ro') +ax[0,3].plot(trackers[2][0],100-trackers[2][2],'bo') +ax[0,4].plot(trackers[3][0],100-trackers[3][2],'bo') + +ax[1,0].imshow(Model4D.midp.imageArray[:, :, 50].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +s0 = wm[0].imageArray[:, :, 50].T[::-1, ::1] +s1 = wm[1].imageArray[:, :, 50].T[::-1, ::1] +s2 = wm[2].imageArray[:, :, 50].T[::-1, ::1] +s3 = wm[3].imageArray[:, :, 50].T[::-1, ::1] +ax[1,1].imshow(s0, cmap='Reds', origin='upper', vmin=0, vmax=1) +ax[1,2].imshow(s1, cmap='Reds', origin='upper', vmin=0, vmax=1) +ax[1,3].imshow(s2, cmap='Blues', origin='upper', vmin=0, vmax=1) +ax[1,4].imshow(s3, cmap='Blues', origin='upper', vmin=0, vmax=1) +ax[1,0].plot(trackers[0][0],trackers[0][1],'ro') +ax[1,0].plot(trackers[1][0],trackers[1][1],'ro') +ax[1,0].plot(trackers[2][0],trackers[2][1],'bo') +ax[1,0].plot(trackers[3][0],trackers[3][1],'bo') +ax[1,1].plot(trackers[0][0],trackers[0][1],'ro') +ax[1,2].plot(trackers[1][0],trackers[1][1],'ro') +ax[1,3].plot(trackers[2][0],trackers[2][1],'bo') +ax[1,4].plot(trackers[3][0],trackers[3][1],'bo') +ax[0,0].title.set_text('MidP and trackers') +ax[0,1].title.set_text('Tracker 1') +ax[0,2].title.set_text('Tracker 2') +ax[0,3].title.set_text('Tracker 3') +ax[0,4].title.set_text('Tracker 4') + +fig, ax = plt.subplots(2, 4) +fig.tight_layout() +y_slice = round(Model4D.midp.imageArray.shape[1]/2)-1 +ax[0,0].imshow(CT4D.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,0].title.set_text('Phase 0') +ax[0,1].imshow(CT4D.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,1].title.set_text('Phase 1') +ax[0,2].imshow(CT4D.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,2].title.set_text('Phase 2') +ax[0,3].imshow(CT4D.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,3].title.set_text('Phase 3') +ax[1,0].imshow(Model4D.midp.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,0].imshow(wm[0].imageArray[:, y_slice, :].T[::-1, ::1] + wm[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='Reds', origin='upper', vmin=0, vmax=1, alpha=0.3) +ax[1,0].imshow(wm[2].imageArray[:, y_slice, :].T[::-1, ::1] + wm[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='Blues', origin='upper', vmin=0, vmax=1, alpha=0.3) +ax[1, 0].plot(trackers[0][0],100-trackers[0][2], 'ro') +ax[1, 0].plot(trackers[1][0],100-trackers[1][2], 'ro') +ax[1, 0].plot(trackers[2][0],100-trackers[2][2], 'bo') +ax[1, 0].plot(trackers[3][0],100-trackers[3][2], 'bo') +ax[1,0].title.set_text('MidP and weight maps') +ax[1,1].imshow(im1.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,1].title.set_text('phases [0,2] - amplitude 1') +ax[1,2].imshow(im2.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,2].title.set_text('phases [0.5,1.5] - amplitude 1') +ax[1,3].imshow(im3.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,3].title.set_text('phases [0,2] - amplitude 2') + +plt.savefig(os.path.join(output_path, 'DeformationFromWeightMaps.png')) +print('done') +print(' ') +plt.show() diff --git a/examples/dynamicData/run_exampleMidP.py b/examples/dynamicData/run_exampleMidP.py new file mode 100644 index 0000000..39fbc30 --- /dev/null +++ b/examples/dynamicData/run_exampleMidP.py @@ -0,0 +1,96 @@ +''' +Midp +==== +author: OpenTPS team + +This example shows how to create a mid-position CT from a 4DCT and visualize it. + +running time: ~ 6 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import numpy as np +import matplotlib.pyplot as plt +import time +import logging +import os + +#%% +#import the needed opentps.core packages + +from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel +from opentps.core.data.dynamicData._dynamic3DSequence import Dynamic3DSequence +from opentps.core.data.images import CTImage +from opentps.core.examples.syntheticData import * + +logger = logging.getLogger(__name__) +#%% +# Output path +#------------ + +output_path = os.path.join(os.getcwd(), 'Output', 'ExampleMidP') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Generate synthetic 4DCT And MidP +#--------------------------------- + +# GENERATE SYNTHETIC 4D INPUT SEQUENCE +CT4D = createSynthetic4DCT() + +# GENERATE MIDP +Model4D = Dynamic3DModel() +startTime = time.time() +Model4D.computeMidPositionImage(CT4D, 0, tryGPU=True) +stopTime = time.time() +print('midP computed in ', np.round(stopTime - startTime, 2), 'seconds') + +# GENERATE ADDITIONAL PHASES +im1 = Model4D.generate3DImage(0.5/4, amplitude=1, tryGPU=False) +im2 = Model4D.generate3DImage(2/4, amplitude=2.0, tryGPU=False) +im3 = Model4D.generate3DImage(2/4, amplitude=0.5, tryGPU=False) + +#%% +# Display results +#---------------- + +fig, ax = plt.subplots(2, 4) +fig.tight_layout() +y_slice = 95 +ax[0,0].imshow(CT4D.dyn3DImageList[0].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,0].title.set_text('Phase 0') +ax[0,1].imshow(CT4D.dyn3DImageList[1].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,1].title.set_text('Phase 1') +ax[0,2].imshow(CT4D.dyn3DImageList[2].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,2].title.set_text('Phase 2') +ax[0,3].imshow(CT4D.dyn3DImageList[3].imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,3].title.set_text('Phase 3') +ax[1,0].imshow(Model4D.midp.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,0].title.set_text('MidP image') +ax[1,1].imshow(im1.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,1].title.set_text('phase 0.5 - amplitude 1') +ax[1,2].imshow(im2.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,2].title.set_text('phase 2 - amplitude 2') +ax[1,3].imshow(im3.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,3].title.set_text('phase 2 - amplitude 0.5') + +plt.savefig(os.path.join(output_path, 'ExampleMidp.png')) +print('MidP example completed') +plt.show() + diff --git a/examples/examples1/DoseOptimizationRealPatient.py b/examples/examples1/DoseOptimizationRealPatient.py deleted file mode 100644 index 0884f67..0000000 --- a/examples/examples1/DoseOptimizationRealPatient.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -OpenTPS CORE : Real case optimization -------------------------------------- -*Author* : Eliot Peeters - -""" - -############################################## -#I am writing stuff -############################################## - -import numpy as np -import os -from matplotlib import pyplot as plt - -#Import the needed opentps.core packages - -from opentps.core.data.plan import PlanDesign -from opentps.core.data import DVH -from opentps.core.io import mcsquareIO -from opentps.core.io.scannerReader import readScanner -from opentps.core.processing.doseCalculation.doseCalculationConfig import DoseCalculationConfig -from opentps.core.processing.doseCalculation.mcsquareDoseCalculator import MCsquareDoseCalculator -from opentps.core.io.dataLoader import readData -from opentps.core.data.plan import ObjectivesList -from opentps.core.data.plan import FidObjective -from opentps.core.io.serializedObjectIO import saveBeamlets, saveRTPlan, loadBeamlets, loadRTPlan - - - -# In the next cell we configure the CT scan model used for the dose calculation and the bdl model. The ones used in this example are the default configuration of openTPS which may lead to some imprecision. - -ctCalibration = readScanner(DoseCalculationConfig().scannerFolder) -bdl = mcsquareIO.readBDL(DoseCalculationConfig().bdlFile) \ No newline at end of file diff --git a/examples/examples1/README.rst b/examples/examples1/README.rst deleted file mode 100644 index 073814d..0000000 --- a/examples/examples1/README.rst +++ /dev/null @@ -1,3 +0,0 @@ -Example 1 Gallery -================= - diff --git a/examples/examples2/README.rst b/examples/examples2/README.rst deleted file mode 100644 index 0085374..0000000 --- a/examples/examples2/README.rst +++ /dev/null @@ -1,3 +0,0 @@ -Example 2 Gallery -================= - diff --git a/examples/examples2/plot_testExample.py b/examples/examples2/plot_testExample.py deleted file mode 100644 index f1a1e02..0000000 --- a/examples/examples2/plot_testExample.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Example Title -============= - -This is an example script that demonstrates XYZ. -""" - -import matplotlib.pyplot as plt -import numpy as np - -#%% -# This is a test cell. - -x = np.linspace(0, 2 * np.pi, 100) -y = np.sin(x) - -plt.plot(x, y) -plt.xlabel(r"$x$") -plt.ylabel(r"$\sin(x)$") -# To avoid matplotlib text output -plt.show() \ No newline at end of file diff --git a/examples/examples2/test_output.py b/examples/examples2/test_output.py deleted file mode 100644 index 652d7dc..0000000 --- a/examples/examples2/test_output.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Example Title -============= - -If it prints, it works. -""" - -print(1+1) \ No newline at end of file diff --git a/examples/imageProcessing/README.rst b/examples/imageProcessing/README.rst new file mode 100644 index 0000000..39e206a --- /dev/null +++ b/examples/imageProcessing/README.rst @@ -0,0 +1,2 @@ +Image Processing +---------------- \ No newline at end of file diff --git a/examples/imageProcessing/cupyVSsitkTransforms.py b/examples/imageProcessing/cupyVSsitkTransforms.py new file mode 100644 index 0000000..304a3bd --- /dev/null +++ b/examples/imageProcessing/cupyVSsitkTransforms.py @@ -0,0 +1,264 @@ +''' +Cupy vs SitkTransforms +======================= +author: OpenTPS team + +This example shows how to use the OpenTPS transforms with cupy and compare the results with the SimpleITK transforms. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#--------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import copy + +import matplotlib.pyplot as plt +import logging +import os + +#%% +#import the needed opentps.core packages +from opentps.core.data.images import VectorField3D +from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel +from opentps.core.data._transform3D import Transform3D +from opentps.core.examples.showStuff import showModelWithAnimatedFields +from opentps.core.examples.syntheticData import * +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData +from opentps.core.processing.imageProcessing.resampler3D import resample + +logger = logging.getLogger(__name__) +#%% +# Output path +# ----------- +output_path = os.path.join(os.getcwd(), 'Output', 'CupyVsSitk') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Generate synthetic input immages +# --------------------------------- + +imgSize = [40, 40, 40] +imgSpacing = [1, 1, 2] +objectBorder = [[21, 33], [int(imgSize[1]/4), 3*int(imgSize[1]/4)], [21, 34]] + +translation = np.array([-10.22, 0, -14.56]) +rotation = np.array([0, 30, 0]) +rotCenter = 'imgCenter' +outputBox = 'same' +# interpOrder = 1 + +showImage = True +showField = True +showMask = True + +fixed = CTImage() +fixed.spacing = np.array(imgSpacing) +fixed.imageArray = np.full(imgSize, -1000) +fixed.imageArray[objectBorder[0][0]: objectBorder[0][1], + objectBorder[1][0]: objectBorder[1][1], + objectBorder[2][0]: objectBorder[2][1]] = 100.0 + +y_slice = int(imgSize[1]/2) +pointList = [[objectBorder[0][0], y_slice, objectBorder[2][1]-1], + [objectBorder[0][1]-1, y_slice, objectBorder[2][0]], + [objectBorder[0][0]+1, y_slice, objectBorder[2][0]+1], + [objectBorder[0][0], y_slice, objectBorder[2][0]]] + +fieldFixed = VectorField3D() +fieldFixed.imageArray = np.zeros((imgSize[0], imgSize[1], imgSize[2], 3)) +fieldFixed.spacing = np.array(imgSpacing) +vectorList = [np.array([4, 6, 8]), np.array([0, 6, 8]), np.array([14, 6, 6]), np.array([6, 0, 0])] +for pointIdx in range(len(pointList)): + fieldFixed.imageArray[pointList[pointIdx][0], pointList[pointIdx][1], pointList[pointIdx][2]] = vectorList[ + pointIdx] + +maskFixed = ROIMask() +maskFixed.spacing = np.array(imgSpacing) +maskFixed.imageArray = np.zeros(imgSize).astype(bool) +maskFixed.imageArray[objectBorder[0][0]: objectBorder[0][1], + objectBorder[1][0]: objectBorder[1][1], + objectBorder[2][0]: objectBorder[2][1]] = True + +#%% +# this function is just to see the results +def showImagesAndFieldAndMask(fixed, movingCupy1, movingCupy3, movingSitk, fieldFixed, fieldMovingCupy1, fieldMovingCupy3, fieldMovingSitk, + maskFixed, maskMovingCupy1, maskMovingCupy3, maskMovingSitk, y_slice, figTitle, showImage=True, + showField=True, showMask=True, ): + + compXFixed = fieldFixed.imageArray[:, y_slice, :, 0] + compZFixed = fieldFixed.imageArray[:, y_slice, :, 2] + compXMovingCupy1 = fieldMovingCupy1.imageArray[:, y_slice, :, 0] + compZMovingCupy1 = fieldMovingCupy1.imageArray[:, y_slice, :, 2] + compXMovingCupy3 = fieldMovingCupy3.imageArray[:, y_slice, :, 0] + compZMovingCupy3 = fieldMovingCupy3.imageArray[:, y_slice, :, 2] + compXMovingSITK = fieldMovingSitk.imageArray[:, y_slice, :, 0] + compZMovingSITK = fieldMovingSitk.imageArray[:, y_slice, :, 2] + + fig, ax = plt.subplots(3, 4) + fig.suptitle(figTitle) + + if showImage: + ax[0, 0].imshow(fixed.imageArray[:, y_slice, :]) + ax[0, 1].imshow(movingCupy1.imageArray[:, y_slice, :]) + ax[0, 2].imshow(movingCupy3.imageArray[:, y_slice, :]) + ax[0, 3].imshow(movingSitk.imageArray[:, y_slice, :]) + ax[1, 0].imshow(movingCupy1.imageArray[:, y_slice, :] - movingSitk.imageArray[:, y_slice, :]) + ax[1, 0].set_xlabel('Img diff cupy1-sitk') + ax[1, 1].imshow(movingCupy1.imageArray[:, y_slice, :] - movingCupy3.imageArray[:, y_slice, :]) + ax[1, 1].set_xlabel('Img diff cupy1-cupy3') + if showField: + ax[0, 0].quiver(compZFixed, compXFixed, alpha=0.5, color='red', angles='xy', scale_units='xy', scale=2, width=.010) + ax[0, 1].quiver(compZMovingCupy1, compXMovingCupy1, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010) + ax[0, 2].quiver(compZMovingCupy3, compXMovingCupy3, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010) + ax[0, 3].quiver(compZMovingSITK, compXMovingSITK, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010) + ax[1, 2].quiver(compZMovingCupy1 - compZMovingSITK, compXMovingCupy1 - compXMovingSITK, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010) + ax[1, 2].set_xlabel('Field diff cupy1-sitk') + ax[1, 3].quiver(compZMovingCupy1 - compZMovingCupy3, compXMovingCupy1 - compXMovingCupy3, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010) + ax[1, 3].set_xlabel('Field diff cupy1-cupy3') + if showMask: + ax[0, 0].imshow(maskFixed.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds') + ax[0, 1].imshow(maskMovingCupy1.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds') + ax[0, 2].imshow(maskMovingCupy3.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds') + ax[0, 3].imshow(maskMovingSitk.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds') + ax[2, 0].imshow(maskMovingCupy1.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :] ^ maskMovingSitk.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds') + ax[2, 0].set_xlabel('Mask diff cupy1-sitk') + ax[2, 1].imshow(maskMovingCupy1.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice,:] ^ maskMovingCupy3.getBinaryContourMask(internalBorder=True).imageArray[:, y_slice, :], alpha=0.5, cmap='Reds') + ax[2, 1].set_xlabel('Mask diff cupy1-cupy3') + + ax[0, 0].set_title('Fixed') + ax[0, 0].set_xlabel(f"{fixed.origin}\n{fixed.spacing}\n{fixed.gridSize}") + ax[0, 1].set_title('Moving Cupy1') + ax[0, 1].set_xlabel(f"{movingCupy1.origin}\n{movingCupy1.spacing}\n{movingCupy1.gridSize}") + ax[0, 2].set_title('Moving Cupy3') + ax[0, 2].set_xlabel(f"{movingCupy3.origin}\n{movingCupy3.spacing}\n{movingCupy3.gridSize}") + ax[0, 3].set_title('Moving SITK') + ax[0, 3].set_xlabel(f"{movingSitk.origin}\n{movingSitk.spacing}\n{movingSitk.gridSize}") + + plt.savefig(os.path.join(output_path, 'cupy_VS_sitk_Transforms.png')) + plt.show() + +#%% +# Test using a Transform3D +# ------------------------ +print('-' * 40) + +movingCupy1 = copy.deepcopy(fixed) +movingCupy3 = copy.deepcopy(fixed) +movingSitk = copy.deepcopy(fixed) +fieldMovingCupy1 = copy.deepcopy(fieldFixed) +fieldMovingCupy3 = copy.deepcopy(fieldFixed) +fieldMovingSitk = copy.deepcopy(fieldFixed) +maskMovingCupy1 = copy.deepcopy(maskFixed) +maskMovingCupy3 = copy.deepcopy(maskFixed) +maskMovingSitk = copy.deepcopy(maskFixed) + +## Create a transform 3D +print('Create a transform 3D') +transform3D = Transform3D() +transform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation) +transform3D.setCenter(rotCenter) +print('Translation', transform3D.getTranslation()) +print('Rotation', transform3D.getRotationAngles(inDegrees=True)) + +print('Moving with transform3D') +movingCupy1 = transform3D.deformData(movingCupy1, outputBox=outputBox, fillValue=-1000, tryGPU=True, interpOrder=1) +fieldMovingCupy1 = transform3D.deformData(fieldMovingCupy1, outputBox=outputBox, tryGPU=True, interpOrder=1) +maskMovingCupy1 = transform3D.deformData(maskMovingCupy1, outputBox=outputBox, tryGPU=True, interpOrder=1) + +movingCupy3 = transform3D.deformData(movingCupy3, outputBox=outputBox, fillValue=-1000, tryGPU=True, interpOrder=3) +fieldMovingCupy3 = transform3D.deformData(fieldMovingCupy3, outputBox=outputBox, tryGPU=True, interpOrder=3) +maskMovingCupy3 = transform3D.deformData(maskMovingCupy3, outputBox=outputBox, tryGPU=True, interpOrder=3) + +movingSitk = transform3D.deformData(movingSitk, outputBox=outputBox, fillValue=-1000) +fieldMovingSitk = transform3D.deformData(fieldMovingSitk, outputBox=outputBox) +maskMovingSitk = transform3D.deformData(maskMovingSitk, outputBox=outputBox) + +showImagesAndFieldAndMask(fixed, movingCupy1, movingCupy3, movingSitk, fieldFixed, fieldMovingCupy1, + fieldMovingCupy3, fieldMovingSitk, + maskFixed, maskMovingCupy1, maskMovingCupy3, maskMovingSitk, y_slice, + figTitle='Test using a Transform3D', showField=showField, showImage=showImage, + showMask=showMask) + +#%% +# Test using translateData +# ------------------------ +print('-' * 40) + +movingCupy1 = copy.deepcopy(fixed) +movingCupy3 = copy.deepcopy(fixed) +movingSitk = copy.deepcopy(fixed) +fieldMovingCupy1 = copy.deepcopy(fieldFixed) +fieldMovingCupy3 = copy.deepcopy(fieldFixed) +fieldMovingSitk = copy.deepcopy(fieldFixed) +maskMovingCupy1 = copy.deepcopy(maskFixed) +maskMovingCupy3 = copy.deepcopy(maskFixed) +maskMovingSitk = copy.deepcopy(maskFixed) + +print('Moving with translateData') +translateData(movingCupy1, translationInMM=translation, outputBox=outputBox, fillValue=-1000, tryGPU=True, interpOrder=1) +translateData(fieldMovingCupy1, translationInMM=translation, outputBox=outputBox, tryGPU=True, interpOrder=1) +translateData(maskMovingCupy1, translationInMM=translation, outputBox=outputBox, tryGPU=True, interpOrder=1) + +translateData(movingCupy3, translationInMM=translation, outputBox=outputBox, fillValue=-1000, tryGPU=True, interpOrder=3) +translateData(fieldMovingCupy3, translationInMM=translation, outputBox=outputBox, tryGPU=True, interpOrder=3) +translateData(maskMovingCupy3, translationInMM=translation, outputBox=outputBox, tryGPU=True, interpOrder=3) + +translateData(movingSitk, translationInMM=translation, outputBox=outputBox, fillValue=-1000) +translateData(fieldMovingSitk, translationInMM=translation, outputBox=outputBox) +translateData(maskMovingSitk, translationInMM=translation, outputBox=outputBox) + +showImagesAndFieldAndMask(fixed, movingCupy1, movingCupy3, movingSitk, fieldFixed, fieldMovingCupy1, + fieldMovingCupy3, fieldMovingSitk, + maskFixed, maskMovingCupy1, maskMovingCupy3, maskMovingSitk, y_slice, + figTitle='Test using translateData', showField=showField, showImage=showImage, + showMask=showMask) + +#%% +# Test using rotateData +# --------------------- +print('-' * 40) + +movingCupy1 = copy.deepcopy(fixed) +movingCupy3 = copy.deepcopy(fixed) +movingSitk = copy.deepcopy(fixed) +fieldMovingCupy1 = copy.deepcopy(fieldFixed) +fieldMovingCupy3 = copy.deepcopy(fieldFixed) +fieldMovingSitk = copy.deepcopy(fieldFixed) +maskMovingCupy1 = copy.deepcopy(maskFixed) +maskMovingCupy3 = copy.deepcopy(maskFixed) +maskMovingSitk = copy.deepcopy(maskFixed) + +print('Moving with rotateData') +rotateData(movingCupy1, rotAnglesInDeg=rotation, outputBox=outputBox, fillValue=-1000, rotCenter=rotCenter, tryGPU=True, interpOrder=1) +rotateData(fieldMovingCupy1, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter, tryGPU=True, interpOrder=1) +rotateData(maskMovingCupy1, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter, tryGPU=True, interpOrder=1) + +rotateData(movingCupy3, rotAnglesInDeg=rotation, outputBox=outputBox, fillValue=-1000, rotCenter=rotCenter, tryGPU=True, interpOrder=3) +rotateData(fieldMovingCupy3, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter, tryGPU=True, interpOrder=3) +rotateData(maskMovingCupy3, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter, tryGPU=True, interpOrder=3) + +rotateData(movingSitk, rotAnglesInDeg=rotation, outputBox=outputBox, fillValue=-1000, rotCenter=rotCenter) +rotateData(fieldMovingSitk, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter) +rotateData(maskMovingSitk, rotAnglesInDeg=rotation, outputBox=outputBox, rotCenter=rotCenter) + +showImagesAndFieldAndMask(fixed, movingCupy1, movingCupy3, movingSitk, fieldFixed, fieldMovingCupy1, + fieldMovingCupy3, fieldMovingSitk, + maskFixed, maskMovingCupy1, maskMovingCupy3, maskMovingSitk, y_slice, + figTitle='Test using rotateData', showField=showField, showImage=showImage, + showMask=showMask) + diff --git a/examples/imageProcessing/exampleDRRwithTigre.py b/examples/imageProcessing/exampleDRRwithTigre.py new file mode 100644 index 0000000..23b8f77 --- /dev/null +++ b/examples/imageProcessing/exampleDRRwithTigre.py @@ -0,0 +1,103 @@ +''' +DRR with Tigre +============== +author: OpenTPS team + +This example demonstrates how to generate Digital Reconstructed Radiographs (DRR) using the TIGRE library in OpenTPS. + +running time: ~ 10 minutes +''' +#%% +# setting up the environment in google colab +#------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install cupy-cuda12x') + get_ipython().system('git clone https://github.com/CERN/TIGRE.git') + get_ipython().system('pip install ./TIGRE') + get_ipython().system('pip install scipy==1.10.1') + import opentps + +#%% +#imports +import math +import numpy as np +import matplotlib.pyplot as plt +import logging +import os + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images import CTImage +from opentps.core.processing.imageSimulation.ForwardProjectorTigre import forwardProjectionTigre + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Output', 'ExampleDRRwithTigre') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# GENERATE SYNTHETIC CT IMAGE +#---------------------------- +im = np.full((170, 170, 100), -1000) +im[20:150, 70:130, :] = 0 +im[30:70, 80:120, 20:] = -800 +im[100:140, 80:120, 20:] = -800 +im[45:55, 95:105, 30:40] = 0 +im[80:90, 115:125, :] = 800 +im[:, 130:140, :] = 100 # couch +ct = CTImage(imageArray=im, name='fixed', origin=[0, 0, 0], spacing=[2, 2.5, 3]) + +#%% +# Compute projections +#-------------------- +angles = np.array([0,90,180])*2*math.pi/360 +DRR_no_noise = forwardProjectionTigre(ct, angles, axis='Z', poissonNoise=None, gaussianNoise=None) +DRR_realistic = forwardProjectionTigre(ct, angles, axis='Z') +DRR_high_noise = forwardProjectionTigre(ct, angles, axis='Z', poissonNoise=3e4, gaussianNoise=30) + +#%% +# Compute error +#-------------- +error_realistic_projections = np.abs(DRR_realistic-DRR_no_noise) +error_realistic_projections_high_noise = np.abs(DRR_high_noise-DRR_no_noise) + +#%% +# Display results +#---------------- +fig, ax = plt.subplots(3, 5) +ax[0,0].imshow(DRR_no_noise[0][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise)) +ax[0,1].imshow(DRR_realistic[0][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise)) +ax[0,2].imshow(error_realistic_projections[0][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100) +ax[0,3].imshow(DRR_high_noise[0][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise)) +ax[0,4].imshow(error_realistic_projections_high_noise[0][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100) +ax[1,0].imshow(DRR_no_noise[1][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise)) +ax[1,1].imshow(DRR_realistic[1][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise)) +ax[1,2].imshow(error_realistic_projections[1][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100) +ax[1,3].imshow(DRR_high_noise[1][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise)) +ax[1,4].imshow(error_realistic_projections_high_noise[1][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100) +ax[2,0].imshow(DRR_no_noise[2][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise)) +ax[2,1].imshow(DRR_realistic[2][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise)) +ax[2,2].imshow(error_realistic_projections[2][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100) +ax[2,3].imshow(DRR_high_noise[2][::-1, ::1], cmap='gray', origin='upper', vmin=np.min(DRR_no_noise), vmax=np.max(DRR_no_noise)) +ax[2,4].imshow(error_realistic_projections_high_noise[2][::-1, ::1], cmap='gray', origin='upper', vmin=0, vmax=np.max(DRR_no_noise)/100) +ax[0,0].title.set_text('Perfect DRR') +ax[0,1].title.set_text('DRR with moderate noise') +ax[0,2].title.set_text('Moderate noise') +ax[0,3].title.set_text('DRR with high noise') +ax[0,4].title.set_text('High noise') + +plt.savefig(os.path.join(output_path, 'ExampleDRRwithTigre.png')) + +print('TIGRE DRR example completed') +plt.show() \ No newline at end of file diff --git a/examples/imageProcessing/exampleTransform3DCupy.py b/examples/imageProcessing/exampleTransform3DCupy.py new file mode 100644 index 0000000..ac1cc23 --- /dev/null +++ b/examples/imageProcessing/exampleTransform3DCupy.py @@ -0,0 +1,234 @@ +''' +Transform 3D Image with CuPy +============================ +author: OpenTPS team + +This example demonstrates how to apply a 3D transformation to a synthetic CT image using the OpenTPS library with CuPy for efficient computation. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import copy + +import matplotlib.pyplot as plt +import logging +import os + +#%% +#import the needed opentps.core packages +from opentps.core.data.images import VectorField3D +from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel +from opentps.core.data._transform3D import Transform3D +from opentps.core.examples.showStuff import showModelWithAnimatedFields +from opentps.core.examples.syntheticData import * +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData +from opentps.core.processing.imageProcessing.resampler3D import resample + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------- +output_path = os.path.join(os.getcwd(), 'Output', 'ExampleTransform3DCupy') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# GENERATE SYNTHETIC INPUT IMAGES +#-------------------------------- +fixed = CTImage() +fixed.spacing = np.array([1, 1, 1]) +fixed.imageArray = np.full((20, 20, 20), -1000) +fixed.imageArray[11:16, 5:14, 11:14] = 100.0 + +moving = copy.deepcopy(fixed) +movingTrans = copy.deepcopy(fixed) +movingRot = copy.deepcopy(fixed) +movingBoth = copy.deepcopy(fixed) + +translation = np.array([0, 0, 0]) +rotation = np.array([0, 45, 0]) +rotCenter='imgCenter' + +#%% +# Create a transform 3D +#---------------------- +print('Create a transform 3D') +transform3D = Transform3D() +transform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation) +transform3D.setCenter(rotCenter) +print('Translation', transform3D.getTranslation()) +print('Rotation', transform3D.getRotationAngles(inDegrees=True)) + +print('moving with transform3D') +moving = transform3D.deformData(moving, outputBox='same', fillValue=-1000, tryGPU=True) + +print('moving translation') +translateData(movingTrans, translationInMM=translation, outputBox='same', fillValue=-1000, tryGPU=True) +print('moving rotation') +rotateData(movingRot, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=-1000, tryGPU=True) +# movingRot = resampleImage3DOnImage3D(movingRot, fixedImage=fixed, fillValue=-1000) +print('moving both') +translateData(movingBoth, translationInMM=translation, outputBox='same', fillValue=-1000, tryGPU=True) +rotateData(movingBoth, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=-1000, tryGPU=True) + +y_slice = 10 + +fig, ax = plt.subplots(1, 6) +ax[0].set_title('fixed') +ax[0].imshow(fixed.imageArray[:, y_slice, :]) +ax[0].set_xlabel(f"{fixed.origin}\n{fixed.spacing}\n{fixed.gridSize}") + +ax[1].set_title('translateData') +ax[1].imshow(movingTrans.imageArray[:, y_slice, :]) +ax[1].set_xlabel(f"{movingTrans.origin}\n{movingTrans.spacing}\n{movingTrans.gridSize}") + +ax[2].set_title('rotateData') +ax[2].imshow(movingRot.imageArray[:, y_slice, :]) +ax[2].set_xlabel(f"{movingRot.origin}\n{movingRot.spacing}\n{movingRot.gridSize}") + +ax[3].set_title('both') +ax[3].imshow(movingBoth.imageArray[:, y_slice, :]) +ax[3].set_xlabel(f"{movingBoth.origin}\n{movingBoth.spacing}\n{movingBoth.gridSize}") + +ax[4].set_title('transform3D') +ax[4].imshow(moving.imageArray[:, y_slice, :]) +ax[4].set_xlabel(f"{moving.origin}\n{moving.spacing}\n{moving.gridSize}") + +ax[5].set_title('transform3D-both') +ax[5].imshow(moving.imageArray[:, y_slice, :] - movingBoth.imageArray[:, y_slice, :]) + +plt.savefig(os.path.join(output_path, 'ExampleTransform3DCupy.png')) +plt.show() + +#%% +# Create a dynamic model with the transform +#------------------------------------------ +print(' --------------------- start test with model -----------------------------') + +CT4D = createSynthetic4DCT(numberOfPhases=4) +# GENERATE MIDP +fixedDynMod = Dynamic3DModel() +fixedDynMod.computeMidPositionImage(CT4D, 0, tryGPU=True) + +print(fixedDynMod.midp.origin, fixedDynMod.midp.spacing, fixedDynMod.midp.gridSize) +print('Resample model image') +fixedDynMod = resample(fixedDynMod, gridSize=(80, 50, 50)) +print('after resampling', fixedDynMod.midp.origin, fixedDynMod.midp.spacing, fixedDynMod.midp.gridSize) + +# option 3 +for field in fixedDynMod.deformationList: + print('Resample model field') + field.resample(spacing=fixedDynMod.midp.spacing, gridSize=fixedDynMod.midp.gridSize, origin=fixedDynMod.midp.origin) + print('after resampling', field.origin, field.spacing, field.gridSize) + +showModelWithAnimatedFields(fixedDynMod) + +movingDynMod = copy.copy(fixedDynMod) + +rotateData(movingDynMod, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=-1000, tryGPU=True) + +showModelWithAnimatedFields(movingDynMod) + +#%% +#Generate synthetic input images +#------------------------------- +fixed = CTImage() +fixed.imageArray = np.full((20, 20, 20), -1000) +y_slice = 10 + +pointList = [[15, y_slice, 15], [15, y_slice, 10], [12, y_slice, 12], [10, y_slice, 10]] +for point in pointList: + fixed.imageArray[point[0], point[1], point[2]] = 200 + +fieldFixed = VectorField3D() +fieldFixed.imageArray = np.zeros((20, 20, 20, 3)) +vectorList = [np.array([2, 3, 4]), np.array([0, 3, 4]), np.array([7, 3, 3]), np.array([2, 0, 0])] +for pointIdx in range(len(pointList)): + fieldFixed.imageArray[pointList[pointIdx][0], pointList[pointIdx][1], pointList[pointIdx][2]] = vectorList[ + pointIdx] + +moving = copy.copy(fixed) +fieldMoving = copy.copy(fieldFixed) + + +#%% +# Create a transform 3D +#---------------------- +print('Create a transform 3D') +transform3D = Transform3D() +transform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation) +transform3D.setCenter(rotCenter) +print('Translation', transform3D.getTranslation()) +print('Rotation', transform3D.getRotationAngles(inDegrees=True)) + +print('moving with transform3D') +# moving = transform3D.deformData(moving, fillValue=-1000, outputBox='same', tryGPU=True) +# fieldMoving = transform3D.deformData(fieldMoving, fillValue=0, outputBox='same', tryGPU=True) + +rotateData(moving, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=-1000, tryGPU=True) +rotateData(fieldMoving, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same', fillValue=0, tryGPU=True) + +moving = resampleImage3DOnImage3D(moving, fixedImage=fixed, fillValue=-1000) +print('fixed.origin', fixed.origin, 'moving.origin', moving.origin) +fieldMoving = resampleImage3DOnImage3D(fieldMoving, fixedImage=fixed, fillValue=0) +print('fieldFixed.origin', fieldFixed.origin, 'fieldMoving.origin', fieldMoving.origin) + +print('ici ', fieldMoving.imageArray[10, y_slice, 10]) + +compXFixed = fieldFixed.imageArray[:, y_slice, :, 0] +compZFixed = fieldFixed.imageArray[:, y_slice, :, 2] +compXMoving = fieldMoving.imageArray[:, y_slice, :, 0] +compZMoving = fieldMoving.imageArray[:, y_slice, :, 2] + +#%% +# Display results +#---------------- + +fig, ax = plt.subplots(1, 2) +ax[0].imshow(fixed.imageArray[:, y_slice, :]) +ax[0].quiver(compZFixed, compXFixed, alpha=0.5, color='red', angles='xy', scale_units='xy', scale=2, width=.010) +ax[1].imshow(moving.imageArray[:, y_slice, :]) +ax[1].quiver(compZMoving, compXMoving, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010) +plt.show() + + +print('start ROIMask test') +fixedMask = ROIMask.fromImage3D(fixed) +fixedMask.imageArray = np.zeros(fixedMask.gridSize).astype(bool) +fixedMask.imageArray[12:15, 8:12, 8:18] = True +plt.figure() +plt.imshow(fixedMask.imageArray[:, y_slice, :]) +plt.show() + +print(fixedMask.origin, fixedMask.gridSize, fixedMask.spacing, fixedMask.imageArray.dtype) + +movingMask = copy.copy(fixedMask) +movingMask = transform3D.deformData(movingMask, outputBox='same') + +print(movingMask.origin, movingMask.gridSize, movingMask.spacing, movingMask.imageArray.dtype) + +plt.figure() +plt.subplot(1, 2, 1) +plt.imshow(fixedMask.imageArray[:, y_slice, :]) +plt.savefig(os.path.join(output_path, 'ExampleTransform3DCupy_fixexMask.png')) +plt.subplot(1, 2, 2) +plt.imshow(movingMask.imageArray[:, y_slice, :]) +plt.savefig(os.path.join(output_path, 'ExampleTransform3DCupy_movingMask.png')) +plt.show() \ No newline at end of file diff --git a/examples/imageProcessing/run_exampleApplyBaselineShift.py b/examples/imageProcessing/run_exampleApplyBaselineShift.py new file mode 100644 index 0000000..7cc992b --- /dev/null +++ b/examples/imageProcessing/run_exampleApplyBaselineShift.py @@ -0,0 +1,97 @@ +''' +Applying Baseline Shift +========================= +author: OpenTPS team + +This example demonstrates how to apply a baseline shift to a synthetic CT image and its corresponding tumor mask using the OpenTPS library. The example generates a synthetic 3D CT image, applies different baseline shifts, and visualizes the results. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import numpy as np +import matplotlib.pyplot as plt +import logging +import os + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images import CTImage +from opentps.core.data.images import ROIMask +from opentps.core.processing.imageProcessing.syntheticDeformation import applyBaselineShift +from opentps.core.examples.syntheticData import * + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------- + +output_path = os.path.join(os.getcwd(), 'Output', 'ExampleApplyBasilineShift') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# GENERATE SYNTHETIC CT IMAGE AND TUMOR MASK +#------------------------------------------- + +ct, roi = createSynthetic3DCT(returnTumorMask=True) # roi = [45, 54], [95, 104], [30, 39] + +#%% +# APPLY BASELINE SHIFT +#---------------------- +ctDef1, maskDef1 = applyBaselineShift(ct, roi, [4, 4, 4]) +ctDef2, maskDef2 = applyBaselineShift(ct, roi, [-4, -4, -4]) +ctDef3, maskDef3 = applyBaselineShift(ct, roi, [0, 0, -16]) + +#%% +# CHECK RESULTS +#-------------- +assert (np.all(ctDef1.imageArray[50:57, 100:107, 36:42] > -700)), f"Error for baseline shift +4,+4,+4" +assert (np.all(ctDef2.imageArray[42:49, 92:99, 28:34] > -700)), f"Error for baseline shift -4,-4,-4" +assert (np.all(ctDef3.imageArray[46:53, 96:103, 22:32] > -700)), f"Error for baseline shift 0,0,-16" + +#%% +# DISPLAY RESULTS +#----------------- +fig, ax = plt.subplots(2, 4) +fig.tight_layout() +y_slice = 100 +z_slice = 35 #round(ct.imageArray.shape[2] / 2) - 1 +ax[0,0].imshow(ct.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,0].title.set_text('CT') +ax[0,1].imshow(ctDef1.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,1].title.set_text('baseline shift 4,4,4') +ax[0,2].imshow(ctDef2.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,2].title.set_text('baseline shift -4,-4,-4') +ax[0,3].imshow(ctDef3.imageArray[:, y_slice, :].T[::-1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[0,3].title.set_text('baseline shift 0,0,-16') + +ax[1,0].imshow(ct.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,0].title.set_text('CT') +ax[1,1].imshow(ctDef1.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,1].title.set_text('baseline shift 4,4,4') +ax[1,2].imshow(ctDef2.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,2].title.set_text('baseline shift -4,-4,-4') +ax[1,3].imshow(ctDef3.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=-1000, vmax=1000) +ax[1,3].title.set_text('baseline shift 0,0,-16') + +plt.savefig(os.path.join(output_path, 'ExampleApplyBaselinesShift.png')) +print('Baseline shift example completed') +plt.show() diff --git a/examples/imageProcessing/run_exampleDilateBinaryMask.py b/examples/imageProcessing/run_exampleDilateBinaryMask.py new file mode 100644 index 0000000..0f9322b --- /dev/null +++ b/examples/imageProcessing/run_exampleDilateBinaryMask.py @@ -0,0 +1,95 @@ +''' +Dilate Binary Mask +========================= +author: OpenTPS team + +This example shows how to dilate a binary mask using the OpenTPS library. + +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#--------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import numpy as np +import matplotlib.pyplot as plt + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images._roiMask import ROIMask +from opentps.core.processing.imageProcessing.roiMasksProcessing import buildStructElem, dilateMaskScipy +import os +import logging + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Output', 'ExampleDilateBinaryMask') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Create a synthetic 3D ROI mask +#-------------------------------- +roi = ROIMask(name='TV') +roi.color = (255, 0, 0)# red +data = np.zeros((100, 100, 100)).astype(bool) +data[50:60, 50:60, 50:60] = True +roi.imageArray = data +roi.spacing = np.array([1, 1, 2]) + +radius = np.array([4, 4, 6]) +struct = buildStructElem(radius / np.array(roi.spacing)) + +roi_scipy = roi.copy() +dilateMaskScipy(roi_scipy, radius=radius) # scipy +print(radius, 'before roi_sitk') +roi_sitk = roi.copy() +roi_sitk.dilateMask(radius=radius) + +#%% +# Visualize the results +#---------------------- + +plt.figure() +plt.subplot(2, 4, 1) +plt.imshow(roi.imageArray[55, :, :], cmap='gray') +plt.title("Original") + +plt.subplot(2, 4, 2) +plt.imshow(roi_scipy.imageArray[55, :, :], cmap='gray') +plt.title("Scipy") + +plt.subplot(2, 4, 3) +plt.imshow(roi_sitk.imageArray[55, :, :], cmap='gray') +plt.title("SITK") + +plt.subplot(2, 4, 5) +plt.imshow(roi_scipy.imageArray[55, :, :] ^ roi_sitk.imageArray[55, :, :], cmap='gray') +plt.title("diff Scipy-SITK") + +plt.subplot(2, 4, 6) +plt.imshow(roi_scipy.imageArray[55, :, :] ^ roi.imageArray[55, :, :], cmap='gray') +plt.title("diff Scipy-ori") + +plt.subplot(2, 4, 7) +plt.imshow(roi_sitk.imageArray[55, :, :] ^ roi.imageArray[55, :, :], cmap='gray') +plt.title("diff SITK-ori") + +plt.savefig(os.path.join(output_path, 'ExampleDilateBinary.png')) +plt.show() diff --git a/examples/imageProcessing/run_exampleTransform3D.py b/examples/imageProcessing/run_exampleTransform3D.py new file mode 100644 index 0000000..fa96c27 --- /dev/null +++ b/examples/imageProcessing/run_exampleTransform3D.py @@ -0,0 +1,227 @@ +''' +Transform 3D +============ +author: OpenTPS team + +This example demonstrates how to apply a 3D transformation to a synthetic CT image using the OpenTPS library. + +running time: ~ 6 minutes +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import copy + +import matplotlib.pyplot as plt +import logging +import os + +#%% +#import the needed opentps.core packages +from opentps.core.data.images import VectorField3D +from opentps.core.data.dynamicData._dynamic3DModel import Dynamic3DModel +from opentps.core.data._transform3D import Transform3D +from opentps.core.examples.showStuff import showModelWithAnimatedFields +from opentps.core.examples.syntheticData import * +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData +from opentps.core.processing.imageProcessing.resampler3D import resample + +logger = logging.getLogger(__name__) + +#%% +# Output path +#------------ +output_path = os.path.join(os.getcwd(), 'Output', 'ExampleTransform3D') +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# GENERATE SYNTHETIC INPUT IMAGES +#-------------------------------- +fixed = CTImage() +fixed.imageArray = np.full((20, 20, 20), -1000) +fixed.imageArray[11:16, 5:14, 11:14] = 100.0 + +moving = copy.copy(fixed) +movingTrans = copy.copy(fixed) +movingRot = copy.copy(fixed) +movingBoth = copy.copy(fixed) + +translation = np.array([-5, 0, -2]) +rotation = np.array([0, -20, 0]) +rotCenter='imgCenter' + +#%% +# Create a transform 3D +#---------------------- +print('Create a transform 3D') +transform3D = Transform3D() +transform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation) +transform3D.setCenter(rotCenter) +print('Translation', transform3D.getTranslation()) +print('Rotation', transform3D.getRotationAngles(inDegrees=True)) + +print('moving with transform3D') +moving = transform3D.deformData(moving, outputBox='same') + +print('moving translation') +translateData(movingTrans, translationInMM=translation) +print('moving rotation') +rotateData(movingRot, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same') +# movingRot = resampleImage3DOnImage3D(movingRot, fixedImage=fixed, fillValue=-1000) +print('moving both') +translateData(movingBoth, translationInMM=translation, outputBox='same') +rotateData(movingBoth, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same') + +y_slice = 10 + +fig, ax = plt.subplots(1, 6) +ax[0].set_title('fixed') +ax[0].imshow(fixed.imageArray[:, y_slice, :]) +ax[0].set_xlabel(f"{fixed.origin}\n{fixed.spacing}\n{fixed.gridSize}") + +ax[1].set_title('translateData') +ax[1].imshow(movingTrans.imageArray[:, y_slice, :]) +ax[1].set_xlabel(f"{movingTrans.origin}\n{movingTrans.spacing}\n{movingTrans.gridSize}") + +ax[2].set_title('rotateData') +ax[2].imshow(movingRot.imageArray[:, y_slice, :]) +ax[2].set_xlabel(f"{movingRot.origin}\n{movingRot.spacing}\n{movingRot.gridSize}") + +ax[3].set_title('both') +ax[3].imshow(movingBoth.imageArray[:, y_slice, :]) +ax[3].set_xlabel(f"{movingBoth.origin}\n{movingBoth.spacing}\n{movingBoth.gridSize}") + +ax[4].set_title('transform3D') +ax[4].imshow(moving.imageArray[:, y_slice, :]) +ax[4].set_xlabel(f"{moving.origin}\n{moving.spacing}\n{moving.gridSize}") + +ax[5].set_title('transform3D-both') +ax[5].imshow(moving.imageArray[:, y_slice, :] - movingBoth.imageArray[:, y_slice, :]) + +plt.savefig(os.path.join(output_path, 'ExampleTransform3D.png')) +plt.show() + +#%% +# Create a dynamic model with the transform +#------------------------------------------- + +print(' --------------------- start test with model -----------------------------') + +CT4D = createSynthetic4DCT(numberOfPhases=4) +# GENERATE MIDP +fixedDynMod = Dynamic3DModel() +fixedDynMod.computeMidPositionImage(CT4D, 0, tryGPU=True) + +print(fixedDynMod.midp.origin, fixedDynMod.midp.spacing, fixedDynMod.midp.gridSize) +print('Resample model image') +fixedDynMod = resample(fixedDynMod, gridSize=(80, 50, 50)) +print('after resampling', fixedDynMod.midp.origin, fixedDynMod.midp.spacing, fixedDynMod.midp.gridSize) + +# option 3 +for field in fixedDynMod.deformationList: + print('Resample model field') + field.resample(spacing=fixedDynMod.midp.spacing, gridSize=fixedDynMod.midp.gridSize, origin=fixedDynMod.midp.origin) + print('after resampling', field.origin, field.spacing, field.gridSize) + +showModelWithAnimatedFields(fixedDynMod) + +movingDynMod = copy.copy(fixedDynMod) + +rotateData(movingDynMod, rotAnglesInDeg=rotation, rotCenter=rotCenter, outputBox='same') + +showModelWithAnimatedFields(movingDynMod) + +#%% +# Generate synthetic input images +#--------------------------------- +fixed = CTImage() +fixed.imageArray = np.full((20, 20, 20), -1000) +y_slice = 10 + +pointList = [[15, y_slice, 15], [15, y_slice, 10], [12, y_slice, 12], [10, y_slice, 10]] +for point in pointList: + fixed.imageArray[point[0], point[1], point[2]] = 200 + +fieldFixed = VectorField3D() +fieldFixed.imageArray = np.zeros((20, 20, 20, 3)) +vectorList = [np.array([2, 3, 4]), np.array([0, 3, 4]), np.array([7, 3, 3]), np.array([2, 0, 0])] +for pointIdx in range(len(pointList)): + fieldFixed.imageArray[pointList[pointIdx][0], pointList[pointIdx][1], pointList[pointIdx][2]] = vectorList[ + pointIdx] + +moving = copy.copy(fixed) +fieldMoving = copy.copy(fieldFixed) + +#%% +# Create a transform 3D +#---------------------- + +print('Create a transform 3D') +transform3D = Transform3D() +transform3D.initFromTranslationAndRotationVectors(transVec=translation, rotVec=rotation) +transform3D.setCenter(rotCenter) +print('Translation', transform3D.getTranslation()) +print('Rotation', transform3D.getRotationAngles(inDegrees=True)) + +print('moving with transform3D') +moving = transform3D.deformData(moving, outputBox='same') +fieldMoving = transform3D.deformData(fieldMoving, outputBox='same') +moving = resampleImage3DOnImage3D(moving, fixedImage=fixed, fillValue=-1000) +print('fixed.origin', fixed.origin, 'moving.origin', moving.origin) +fieldMoving = resampleImage3DOnImage3D(fieldMoving, fixedImage=fixed, fillValue=0) +print('fieldFixed.origin', fieldFixed.origin, 'fieldMoving.origin', fieldMoving.origin) + +print('ici ', fieldMoving.imageArray[10, y_slice, 10]) + +compXFixed = fieldFixed.imageArray[:, y_slice, :, 0] +compZFixed = fieldFixed.imageArray[:, y_slice, :, 2] +compXMoving = fieldMoving.imageArray[:, y_slice, :, 0] +compZMoving = fieldMoving.imageArray[:, y_slice, :, 2] + +#%% +# Display results +#---------------- +fig, ax = plt.subplots(1, 2) +ax[0].imshow(fixed.imageArray[:, y_slice, :]) +ax[0].quiver(compZFixed, compXFixed, alpha=0.5, color='red', angles='xy', scale_units='xy', scale=2, width=.010) +ax[1].imshow(moving.imageArray[:, y_slice, :]) +ax[1].quiver(compZMoving, compXMoving, alpha=0.5, color='green', angles='xy', scale_units='xy', scale=2, width=.010) +plt.show() + + +print('start ROIMask test') +fixedMask = ROIMask.fromImage3D(fixed) +fixedMask.imageArray = np.zeros(fixedMask.gridSize).astype(bool) +fixedMask.imageArray[12:15, 8:12, 8:18] = True +plt.figure() +plt.imshow(fixedMask.imageArray[:, y_slice, :]) +plt.show() + +print(fixedMask.origin, fixedMask.gridSize, fixedMask.spacing, fixedMask.imageArray.dtype) + +movingMask = copy.copy(fixedMask) +movingMask = transform3D.deformData(movingMask, outputBox='same') + +print(movingMask.origin, movingMask.gridSize, movingMask.spacing, movingMask.imageArray.dtype) + +plt.figure() +plt.subplot(1, 2, 1) +plt.imshow(fixedMask.imageArray[:, y_slice, :]) +plt.subplot(1, 2, 2) +plt.imshow(movingMask.imageArray[:, y_slice, :]) +plt.show() diff --git a/examples/multiplePythonEnv/README.rst b/examples/multiplePythonEnv/README.rst new file mode 100644 index 0000000..df91ab8 --- /dev/null +++ b/examples/multiplePythonEnv/README.rst @@ -0,0 +1,2 @@ +Multiple Python Environements +----------------------------- \ No newline at end of file diff --git a/examples/multiplePythonEnv/backAndForthChild.py b/examples/multiplePythonEnv/backAndForthChild.py new file mode 100644 index 0000000..a41576c --- /dev/null +++ b/examples/multiplePythonEnv/backAndForthChild.py @@ -0,0 +1,72 @@ +''' +Back and Forth Child +========================= +author: OpenTPS team + +This script is the child script that will be launched by the parent script. +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps +#%% +import numpy as np +import sys +from multiprocessing import shared_memory +import matplotlib.pyplot as plt + +#%% +class AIModel(): + + def __init__(self, image=np.zeros((30, 40, 50)), name='test'): + + self.img = image + self.testVariable = 0 + + def processImage(self): + + self.testVariable += 1 + targetPos = [120, 100, 45] + self.img[targetPos[0] - 10:targetPos[0] + 10, targetPos[1] - 10:targetPos[1] + 10, + targetPos[2] - 10:targetPos[2] + 10] += 500 + + +#%% +if __name__ == "__main__": + + for line in sys.stdin: + command = line.strip() + # print(command) + # Process the command + # You can perform specific tasks based on the received command + # For example: + if command == 'init': + existing_shm = shared_memory.SharedMemory(name='sharedArray') + img = np.ndarray((170, 170, 100), dtype=int, buffer=existing_shm.buf) + aimodel = AIModel(image=img) + ## !!! Without this print the response is never sent and the script is blocked + print('Initialize AI Model. testVariable =', aimodel.testVariable) + else: + method = getattr(aimodel, command) + method() + ## !!! Without this print the response is never sent and the script is blocked + print(f'Executing {command} of AI Model. testVariable =', aimodel.testVariable) + + # Flush the output to ensure the parent script receives it + sys.stdout.flush() + + + # print("Start script 2") + # existing_shm = shared_memory.SharedMemory(name='sharedArray') + # arrayInScript2 = np.ndarray((30, 40, 50), dtype=float, buffer=existing_shm.buf) + # processedArray = script2(arrayInScript2) + # del arrayInScript2 + # existing_shm.close() \ No newline at end of file diff --git a/examples/multiplePythonEnv/run_backAndForthParent.py b/examples/multiplePythonEnv/run_backAndForthParent.py new file mode 100644 index 0000000..6102441 --- /dev/null +++ b/examples/multiplePythonEnv/run_backAndForthParent.py @@ -0,0 +1,129 @@ +''' +Back and Forth +========================= +author: OpenTPS team + +This example shows how two different python environements can be used together. +A parent script launches a child script which uses a different python +environement but shares the same image data. It is possible to pass commands back and forth between the two scripts. +The child script in this example simulates the use of an AI model. The first command passed to the child script +is to initialise the model (the neural network structure is created and its weights are loaded for example). +Then, later in the parent script, another command is passed to the child script +to use the AI model, multiple times in a row if necessary. + +Important to note: the code executed in the child script must end with a print to send a response, else the script is +stuck waiting for the response + +Key features: +- Use of multiple python envs in communicating scripts. +- Share RAM memory space between 2 scripts without the need to save and load data on/from hard drives. +- The possibility to initialise first, then later in the parent script, use the AI model. + +running time: ~ 5 minutes + +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import matplotlib.pyplot as plt +import numpy as np +from multiprocessing import shared_memory +from subprocess import Popen, PIPE +from pathlib import Path +import subprocess + +from opentps.core.examples.syntheticData import createSynthetic3DCT + +#%% +# Set the child script environnement path and child scrip file path +childEnvPath = sys.executable # absolute path to current python +childScriptPath = str(Path.cwd() / "backAndForthChild.py") +#%% +# Create test image to share between scripts +ct = createSynthetic3DCT() +sliceToShow = 100 + +#%% +# Initialize shared memory and copy image array to this space +print(ct.imageArray.shape) ## These must be either passed to the child script as arguments or fixed and known +print(ct.imageArray.dtype) ## These must be either passed to the child script as arguments or fixed and known +print(ct.imageArray.nbytes) ## These must be either passed to the child script as arguments or fixed and known +shm = shared_memory.SharedMemory(create=True, size=ct.imageArray.nbytes, name='sharedArray') +sharedTestArray = np.ndarray(ct.imageArray.shape, dtype=ct.imageArray.dtype, buffer=shm.buf) +sharedTestArray[:] = ct.imageArray[:] + +#%% +## Plot initial image +plt.figure() +plt.title("Before initialize") +plt.imshow(sharedTestArray[:, sliceToShow, :]) +plt.show() + +#%% +# Launch child process +process = Popen([childEnvPath, childScriptPath], + stdin=PIPE, stdout=PIPE, encoding='utf-8', text=True) + +#%% +# Send the command 'init' to second process +process.stdin.write('init' + '\n') +process.stdin.flush() + +#%% +# Get the response from the second script +response = process.stdout.readline().strip() +print(f'Back in script 1 after init command: Response: {response}') + +print('Do something else in script 1') + +#%% +# Plot image after init command +ct.imageArray[:] = sharedTestArray[:] +plt.figure() +plt.title("After initialize") +plt.imshow(ct.imageArray[:, sliceToShow, :]) +plt.show() + +for i in range(3): + + ## Send command 'processImage' + process.stdin.write('processImage' + '\n') + process.stdin.flush() + + ## Get the response from the second script + response = process.stdout.readline().strip() + print(f'Back in script 1 after command "processImage": Response: {response}') + + ## Plot image after process image command + ct.imageArray[:] = sharedTestArray[:] + plt.figure() + plt.title("After process image") + plt.imshow(ct.imageArray[:, sliceToShow, :]) + plt.show() + +#%% +# Close the communication +process.stdin.close() +process.stdout.close() + +#%% +# Close the shared memory +shm.close() +try: + shm.unlink() +except FileNotFoundError: + print("Shared memory already unlinked, skipping.") + +print('End of script 1') diff --git a/examples/multiplePythonEnv/run_sharedMemoryParent.py b/examples/multiplePythonEnv/run_sharedMemoryParent.py new file mode 100644 index 0000000..85fb57b --- /dev/null +++ b/examples/multiplePythonEnv/run_sharedMemoryParent.py @@ -0,0 +1,92 @@ +''' +Shared Memory +============= +author: OpenTPS team + +This example shows how two different python environnements can be used together. +A parent script launches a child script which uses a different python +environement but shares the same image data. + +Key features: +- The parent script launches a child script which use a different python environnement +- Share RAM memory space between 2 scripts without the need to save and load data on/from hard drives. + +running time: ~ 5 minutes + +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import matplotlib.pyplot as plt +import numpy as np +import subprocess +from multiprocessing import shared_memory +from pathlib import Path + +from opentps.core.examples.syntheticData import createSynthetic3DCT + +#%% +# set the child script environnement path and child scrip file path +script2EnvPath = sys.executable # absolute path to current python +script2Path = str(Path.cwd() / "sharedMemoryChild.py") + +#%% +# create test image to share between scripts +ct = createSynthetic3DCT() +sliceToShow = 100 + +#%% +# initialize shared memory and copy image array to this space +print(ct.imageArray.shape) ## these must be either passed to the child script as arguments or fixed and known +print(ct.imageArray.dtype) +print(ct.imageArray.nbytes) +shm = shared_memory.SharedMemory(create=True, size=ct.imageArray.nbytes, name='sharedArray') +sharedTestArray = np.ndarray(ct.imageArray.shape, dtype=ct.imageArray.dtype, buffer=shm.buf) +sharedTestArray[:] = ct.imageArray[:] + +#%% +# plot image before child script call +plt.figure() +plt.title("Before subprocess") +plt.imshow(ct.imageArray[:, sliceToShow, :]) +plt.show() + +#%% +# Call to child script +subprocess.call([script2EnvPath, script2Path]) + +#%% +# Copy shared memory space to test image array +ct.imageArray[:] = sharedTestArray[:] + +#%% +# Plot image after child script call +plt.figure() +plt.title("After subprocess") +plt.imshow(ct.imageArray[:, sliceToShow, :]) +plt.show() + +#%% +# Close the shared memory +shm.close() +try: + shm.unlink() +except FileNotFoundError: + print("Shared memory already unlinked, skipping.") + + + + + diff --git a/examples/multiplePythonEnv/sharedMemoryChild.py b/examples/multiplePythonEnv/sharedMemoryChild.py new file mode 100644 index 0000000..c732cdb --- /dev/null +++ b/examples/multiplePythonEnv/sharedMemoryChild.py @@ -0,0 +1,42 @@ +''' +Shared Memory child +=================== +author: OpenTPS team + +This script is the child script that will be launched by the parent script. +''' +#%% +# Setting up the environment in google collab +#-------------------------------------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +import numpy as np +from multiprocessing import shared_memory + +#%% +def processImage(img): + ## This is the function that will be applied to the shared image + targetPos = [120, 100, 45] + img[targetPos[0] - 10:targetPos[0] + 10, targetPos[1] - 10:targetPos[1] + 10, targetPos[2] - 10:targetPos[2] + 10] = 0 + +#%% +if __name__ == "__main__": + + print("Start script 2") + existing_shm = shared_memory.SharedMemory(name='sharedArray') + try: + # Must match parent array shape and dtype + arrayInScript2 = np.ndarray((170, 170, 100), dtype=np.int64, buffer=existing_shm.buf) + processImage(arrayInScript2) + finally: + del arrayInScript2 + existing_shm.close() diff --git a/examples/registration/README.rst b/examples/registration/README.rst new file mode 100644 index 0000000..c462a39 --- /dev/null +++ b/examples/registration/README.rst @@ -0,0 +1,2 @@ +Registration +------------ \ No newline at end of file diff --git a/examples/registration/run_exampleMorphons.py b/examples/registration/run_exampleMorphons.py new file mode 100644 index 0000000..b8b10b3 --- /dev/null +++ b/examples/registration/run_exampleMorphons.py @@ -0,0 +1,148 @@ +''' +Morphons Registration +========================= +author: OpenTPS team + +This example will present the basis of morphons registration with openTPS core. +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports + +import numpy as np +import matplotlib.pyplot as plt +import time +import logging +import os + +#%% +#import the needed opentps.core packages + +from opentps.core.data.images import CTImage +from opentps.core.processing.registration.registrationMorphons import RegistrationMorphons +from opentps.core.examples.syntheticData import * + +logger = logging.getLogger(__name__) + +#%% +#Output path +#----------- +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Generate synthetic images +#-------------------------- +fixed_img = np.full((100, 100, 100), -1000) +fixed_img[25:75, 25:75, 25:75] = 0 +fixed = CTImage(imageArray=fixed_img, name='fixed', origin=[0, 0, 0], spacing=[1, 1, 1]) +moving_img = np.full((100, 100, 100), -1000) +moving_img[30:75, 35:75, 40:75] = 0 +moving = CTImage(imageArray=moving_img, name='moving', origin=[0, 0, 0], spacing=[1, 1, 1]) + +#%% +# Perform the Morphons registration +#---------------------------------- + +start_time = time.time() +reg = RegistrationMorphons(fixed, moving, baseResolution=2.0, nbProcesses=1) +df = reg.compute() +processing_time = time.time() - start_time +print('Registration processing time was', processing_time, '\n') + +#%% +# Compute the difference between the 2 images and check results of the registration +#---------------------------------------------------------------------------------- + +# COMPUTE IMAGE DIFFERENCE +df.resampleOn(moving, fillValue=-1024.) +diff_before = fixed.copy() +diff_before._imageArray = moving.imageArray - fixed.imageArray +diff_after = fixed.copy() +diff_after._imageArray = reg.deformed.imageArray - fixed.imageArray + +# CHECK RESULTS +diff_before_sum = abs(diff_before.imageArray.sum()) +diff_after_sum = abs(diff_after.imageArray.sum()) +assert diff_before_sum-diff_after_sum > 0, f"Image difference is larger after registration" +assert abs(diff_after.imageArray[27,27,27])==0, f"Wrong target voxel difference after registration {diff_after.imageArray[27,27,27]} (expected 0)" + +#%% +# Plot results +#---------------- + +fig, ax = plt.subplots(3, 3) +vmin = -1000 +vmax = 1000 +x_slice = round(fixed.imageArray.shape[0] / 2) - 1 +y_slice = round(fixed.imageArray.shape[1] / 2) - 1 +z_slice = round(fixed.imageArray.shape[2] / 2) - 1 + +# Plot X-Y field +u = df.velocity.imageArray[:, :, z_slice, 0] +v = df.velocity.imageArray[:, :, z_slice, 1] +u[0,0] = 1 +ax[0, 0].imshow(reg.deformed.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=vmin, vmax=vmax) +ax[0, 0].quiver(u.T[::1, ::1], v.T[::1, ::1], alpha=0.2, color='red', angles='xy', scale_units='xy', scale=1) +ax[0, 0].set_xlabel('x') +ax[0, 0].set_ylabel('y') +ax[0, 1].imshow(diff_before.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax) +ax[0, 1].set_xlabel('x') +ax[0, 1].set_ylabel('y') +ax[0, 2].imshow(diff_after.imageArray[:, :, z_slice].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax) +ax[0, 2].set_xlabel('x') +ax[0, 2].set_ylabel('y') + +# Plot X-Z field +compX = df.velocity.imageArray[:, y_slice, :, 0] +compZ = df.velocity.imageArray[:, y_slice, :, 2] +compZ[0,0] = 1 +ax[1, 0].imshow(reg.deformed.imageArray[:, y_slice, :].T[::1, ::1], cmap='gray', origin='upper', vmin=vmin, vmax=vmax) +ax[1, 0].quiver(compX.T[::1, ::1], compZ.T[::1, ::1], alpha=0.2, color='red', angles='xy', scale_units='xy', scale=1) +ax[1, 0].set_xlabel('x') +ax[1, 0].set_ylabel('z') +ax[1, 1].imshow(diff_before.imageArray[:, y_slice, :].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax) +ax[1, 1].set_xlabel('x') +ax[1, 1].set_ylabel('z') +ax[1, 2].imshow(diff_after.imageArray[:, y_slice, :].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax) +ax[1, 2].set_xlabel('x') +ax[1, 2].set_ylabel('z') + +# Plot Y-Z field +compY = df.velocity.imageArray[x_slice, :, :, 1] +compZ = df.velocity.imageArray[x_slice, :, :, 2] +compZ[0,0] = 1 +ax[2, 0].imshow(reg.deformed.imageArray[x_slice, :, :].T[::1, ::1], cmap='gray', origin='upper', vmin=vmin, vmax=vmax) +ax[2, 0].quiver(compY.T[::1, ::1], compZ.T[::1, ::1], alpha=0.2, color='red', angles='xy', scale_units='xy', scale=1) +ax[2, 0].set_xlabel('y') +ax[2, 0].set_ylabel('z') +ax[2, 1].imshow(diff_before.imageArray[x_slice, :, :].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax) +ax[2, 1].set_xlabel('y') +ax[2, 1].set_ylabel('z') +ax[2, 2].imshow(diff_after.imageArray[x_slice, :, :].T[::1, ::1], cmap='gray', origin='upper', vmin=2*vmin, vmax=2*vmax) +ax[2, 2].set_xlabel('y') +ax[2, 2].set_ylabel('z') + +ax[0, 0].title.set_text('Deformed image and deformation field') +ax[0, 1].title.set_text('Difference before registration') +ax[0, 2].title.set_text('Difference after registration') + +plt.savefig(os.path.join(output_path, 'Example_Morphons.png')) + +print('Morphons example completed') +plt.show() diff --git a/examples/registration/run_exampleRigid.py b/examples/registration/run_exampleRigid.py new file mode 100644 index 0000000..906899e --- /dev/null +++ b/examples/registration/run_exampleRigid.py @@ -0,0 +1,124 @@ +''' +Rigid Registration +========================= +author: OpenTPS team + +This example will present the basis of rigid registration with openTPS core. +running time: ~ 5 minutes +''' +#%% +# Setting up the environment in google collab +#-------------- +# First you need to change the type of execution in the bottom left from processor to GPU. Then you can run the example. +import sys +if "google.colab" in sys.modules: + from IPython import get_ipython + get_ipython().system('git clone https://gitlab.com/openmcsquare/opentps.git') + get_ipython().system('pip install ./opentps') + get_ipython().system('pip install scipy==1.10.1') + get_ipython().system('pip install cupy-cuda12x') + import opentps + +#%% +#imports +import copy + +import numpy as np +import matplotlib.pyplot as plt +import time +import logging +import os + +#%% +#import the needed opentps.core packages + +from opentps.core.processing.registration.registrationRigid import RegistrationRigid +from opentps.core.examples.syntheticData import * +from opentps.core.processing.imageProcessing.resampler3D import resampleImage3DOnImage3D +from opentps.core.processing.imageProcessing.imageTransform3D import rotateData, translateData + +logger = logging.getLogger(__name__) + +#%% +#Output path +#----------- +output_path = 'Output' +if not os.path.exists(output_path): + os.makedirs(output_path) +logger.info('Files will be stored in {}'.format(output_path)) + +#%% +# Generate synthetic images +#-------------------------- + +fixed = createSynthetic3DCT() +moving = copy.copy(fixed) + +translation = np.array([15, 0, 10]) +rotation = np.array([0, 5, 2]) + +translateData(moving, translation, outputBox='same') +rotateData(moving, rotation, outputBox='same') + +#%% +# Perform the Rigid registration +#------------------------------- + +start_time = time.time() +reg = RegistrationRigid(fixed, moving) +transform = reg.compute() + +processing_time = time.time() - start_time +print('Registration processing time was', processing_time, '\n') +print('Translation', transform.getTranslation()) +print('Rotation in deg', transform.getRotationAngles(inDegrees=True), '\n') + +## Two ways of getting the deformed moving image +deformedImage = reg.deformed +# deformedImage = transform.deformImage(moving) + +## Resample it to the same grid as the fixed image +resampledOnFixedGrid = resampleImage3DOnImage3D(deformedImage, fixedImage=fixed, fillValue=-1000) + +#%% +# Compute the difference between the 2 images and check results of the registration +#---------------------------------------------------------------------------------- + +# COMPUTE IMAGE DIFFERENCE +diff_before = fixed.copy() +diff_before._imageArray = fixed.imageArray - moving.imageArray +diff_after = fixed.copy() +diff_after._imageArray = fixed.imageArray - resampledOnFixedGrid.imageArray + +# CHECK RESULTS +diff_before_sum = abs(diff_before.imageArray).sum() +diff_after_sum = abs(diff_after.imageArray).sum() +assert diff_before_sum - diff_after_sum > 0, f"Image difference is larger after registration" + +#%% +#Plot results +#------------- +y_slice = 95 +fig, ax = plt.subplots(2, 3) +ax[0, 0].imshow(fixed.imageArray[:, y_slice, :]) +ax[0, 0].set_title('Fixed') +ax[0, 0].set_xlabel('Origin: '+f'{fixed.origin[0]}'+','+f'{fixed.origin[1]}'+','+f'{fixed.origin[2]}') +ax[0, 1].imshow(moving.imageArray[:, y_slice, :]) +ax[0, 1].set_title('Moving') +ax[0, 1].set_xlabel('Origin: ' + f'{moving.origin[0]}' + ',' + f'{moving.origin[1]}' + ',' + f'{moving.origin[2]}') +diffBef = ax[0, 2].imshow(diff_before.imageArray[:, y_slice, :], vmin=-2000, vmax=2000) +ax[0, 2].set_title('Diff before') +fig.colorbar(diffBef, ax=ax[0, 2]) +ax[1, 0].imshow(deformedImage.imageArray[:, y_slice, :]) +ax[1, 0].set_title('DeformedMoving') +ax[1, 0].set_xlabel('Origin: ' + f'{deformedImage.origin[0]:.1f}' + ',' + f'{deformedImage.origin[1]:.1f}' + ',' + f'{deformedImage.origin[2]:.1f}') +ax[1, 1].imshow(resampledOnFixedGrid.imageArray[:, y_slice, :]) +ax[1, 1].set_title('resampledOnFixedGrid') +ax[1, 1].set_xlabel('Origin: ' + f'{resampledOnFixedGrid.origin[0]:.1f}' + ',' + f'{resampledOnFixedGrid.origin[1]:.1f}' + ',' + f'{resampledOnFixedGrid.origin[2]:.1f}') +diffAft = ax[1, 2].imshow(diff_after.imageArray[:, y_slice, :], vmin=-2000, vmax=2000) +ax[1, 2].set_title('Diff after') +fig.colorbar(diffAft, ax=ax[1, 2]) +plt.savefig(os.path.join(output_path, 'Example_Registration.png')) + +print('Rigid registration example completed') +plt.show() \ No newline at end of file diff --git a/examples/testData/RP1.2.840.10008.5.1.4.1.1.481.8.dcm b/examples/testData/RP1.2.840.10008.5.1.4.1.1.481.8.dcm new file mode 100644 index 0000000..dabae2f Binary files /dev/null and b/examples/testData/RP1.2.840.10008.5.1.4.1.1.481.8.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10041326494522582671624196143451054749_0091.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10041326494522582671624196143451054749_0091.dcm new file mode 100644 index 0000000..c0c9300 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10041326494522582671624196143451054749_0091.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10111080999109615288172589223056297273_0010.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10111080999109615288172589223056297273_0010.dcm new file mode 100644 index 0000000..0c71c9b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10111080999109615288172589223056297273_0010.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10199899739779158256172041195906033441_0096.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10199899739779158256172041195906033441_0096.dcm new file mode 100644 index 0000000..536a6ca Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10199899739779158256172041195906033441_0096.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10201967247492771625593780379939631790_0059.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10201967247492771625593780379939631790_0059.dcm new file mode 100644 index 0000000..d33e28b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10201967247492771625593780379939631790_0059.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10215767518673582276621665474140184903_0074.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10215767518673582276621665474140184903_0074.dcm new file mode 100644 index 0000000..03dedc1 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10215767518673582276621665474140184903_0074.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10233578176870112159077996194682142563_0159.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10233578176870112159077996194682142563_0159.dcm new file mode 100644 index 0000000..161a7fc Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10233578176870112159077996194682142563_0159.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10320502967917528449677326588425651052_0122.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10320502967917528449677326588425651052_0122.dcm new file mode 100644 index 0000000..b07a4fc Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10320502967917528449677326588425651052_0122.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10369103920983703022921890724488148496_0111.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10369103920983703022921890724488148496_0111.dcm new file mode 100644 index 0000000..61fcbe6 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10369103920983703022921890724488148496_0111.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10435368731747934396299449724121649700_0162.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10435368731747934396299449724121649700_0162.dcm new file mode 100644 index 0000000..de9f8d9 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10435368731747934396299449724121649700_0162.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10463150434324408570582308249346602077_0167.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10463150434324408570582308249346602077_0167.dcm new file mode 100644 index 0000000..810e718 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10463150434324408570582308249346602077_0167.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10465073762752224520442532225778636070_0056.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10465073762752224520442532225778636070_0056.dcm new file mode 100644 index 0000000..86a8530 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10465073762752224520442532225778636070_0056.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10520470646671024169119309423907938964_0165.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10520470646671024169119309423907938964_0165.dcm new file mode 100644 index 0000000..242c498 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10520470646671024169119309423907938964_0165.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10574549031309641271053332253085564880_0032.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10574549031309641271053332253085564880_0032.dcm new file mode 100644 index 0000000..72ce8ec Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10574549031309641271053332253085564880_0032.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10594117836550180684276239200292226917_0012.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10594117836550180684276239200292226917_0012.dcm new file mode 100644 index 0000000..cb84076 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10594117836550180684276239200292226917_0012.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10623571283666287894296516683017222035_0102.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10623571283666287894296516683017222035_0102.dcm new file mode 100644 index 0000000..5418fe5 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10623571283666287894296516683017222035_0102.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10691953461874442946985865557654215418_0173.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10691953461874442946985865557654215418_0173.dcm new file mode 100644 index 0000000..d2a7745 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10691953461874442946985865557654215418_0173.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10756779369411951679220122216911776452_0089.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10756779369411951679220122216911776452_0089.dcm new file mode 100644 index 0000000..257840b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10756779369411951679220122216911776452_0089.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10794075421490187051273564240960206608_0172.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10794075421490187051273564240960206608_0172.dcm new file mode 100644 index 0000000..32bc04f Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10794075421490187051273564240960206608_0172.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10796838011944866583529476440135311664_0116.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10796838011944866583529476440135311664_0116.dcm new file mode 100644 index 0000000..a7ea808 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10796838011944866583529476440135311664_0116.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10821463484926317585779044560149338191_0142.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10821463484926317585779044560149338191_0142.dcm new file mode 100644 index 0000000..b4dc881 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10821463484926317585779044560149338191_0142.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10829992108216178774484147778462997730_0171.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10829992108216178774484147778462997730_0171.dcm new file mode 100644 index 0000000..307f359 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10829992108216178774484147778462997730_0171.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10854611427286397407931456164016480970_0084.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10854611427286397407931456164016480970_0084.dcm new file mode 100644 index 0000000..0a4db2b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10854611427286397407931456164016480970_0084.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10889888168053249598309833158273344515_0063.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10889888168053249598309833158273344515_0063.dcm new file mode 100644 index 0000000..c8eb574 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10889888168053249598309833158273344515_0063.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10906109935367752879759266016943011484_0090.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10906109935367752879759266016943011484_0090.dcm new file mode 100644 index 0000000..c270d3c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.10906109935367752879759266016943011484_0090.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11067738432002428345490191226805212979_0190.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11067738432002428345490191226805212979_0190.dcm new file mode 100644 index 0000000..17675bd Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11067738432002428345490191226805212979_0190.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11121985523312355678171545493286947297_0163.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11121985523312355678171545493286947297_0163.dcm new file mode 100644 index 0000000..292cd5a Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11121985523312355678171545493286947297_0163.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11216794512349145923423124772438870623_0017.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11216794512349145923423124772438870623_0017.dcm new file mode 100644 index 0000000..22cf29e Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11216794512349145923423124772438870623_0017.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11335439388292936440210735078487206771_0052.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11335439388292936440210735078487206771_0052.dcm new file mode 100644 index 0000000..e59b384 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11335439388292936440210735078487206771_0052.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11346434721450437371315124301320565409_0133.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11346434721450437371315124301320565409_0133.dcm new file mode 100644 index 0000000..565102c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11346434721450437371315124301320565409_0133.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11561228176924771777709527399010416886_0003.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11561228176924771777709527399010416886_0003.dcm new file mode 100644 index 0000000..2707935 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11561228176924771777709527399010416886_0003.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11816041623573714399851916934378782339_0108.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11816041623573714399851916934378782339_0108.dcm new file mode 100644 index 0000000..fe949ab Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11816041623573714399851916934378782339_0108.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11833414500835097013011489846671253909_0113.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11833414500835097013011489846671253909_0113.dcm new file mode 100644 index 0000000..c0d88ba Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.11833414500835097013011489846671253909_0113.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12045135124663123886653013507165163049_0143.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12045135124663123886653013507165163049_0143.dcm new file mode 100644 index 0000000..d962012 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12045135124663123886653013507165163049_0143.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12119516455959377585980489471933014195_0168.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12119516455959377585980489471933014195_0168.dcm new file mode 100644 index 0000000..8a9838b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12119516455959377585980489471933014195_0168.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12157609890408588315022465624115573141_0033.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12157609890408588315022465624115573141_0033.dcm new file mode 100644 index 0000000..0e17339 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12157609890408588315022465624115573141_0033.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12379404284205338067109303181009746182_0114.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12379404284205338067109303181009746182_0114.dcm new file mode 100644 index 0000000..50616b8 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12379404284205338067109303181009746182_0114.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12446816161318845272084020385629637032_0134.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12446816161318845272084020385629637032_0134.dcm new file mode 100644 index 0000000..e7802c6 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12446816161318845272084020385629637032_0134.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12499667637640076755193002692597842862_0192.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12499667637640076755193002692597842862_0192.dcm new file mode 100644 index 0000000..259a72b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12499667637640076755193002692597842862_0192.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12552894485739616062740176408663843644_0028.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12552894485739616062740176408663843644_0028.dcm new file mode 100644 index 0000000..70e16eb Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12552894485739616062740176408663843644_0028.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12558031330968289978080988123323974141_0105.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12558031330968289978080988123323974141_0105.dcm new file mode 100644 index 0000000..ebfe091 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12558031330968289978080988123323974141_0105.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12676413901229937843664798880341891783_0069.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12676413901229937843664798880341891783_0069.dcm new file mode 100644 index 0000000..7b3e4e2 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12676413901229937843664798880341891783_0069.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12798846486788442144463451596312204015_0002.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12798846486788442144463451596312204015_0002.dcm new file mode 100644 index 0000000..ef55e9b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12798846486788442144463451596312204015_0002.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12838183815707697211469576963438697218_0062.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12838183815707697211469576963438697218_0062.dcm new file mode 100644 index 0000000..35ea75e Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12838183815707697211469576963438697218_0062.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12846965883890101346739332205863896176_0123.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12846965883890101346739332205863896176_0123.dcm new file mode 100644 index 0000000..015bbdb Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12846965883890101346739332205863896176_0123.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12885564110641124124780348172120675482_0106.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12885564110641124124780348172120675482_0106.dcm new file mode 100644 index 0000000..eb7ff05 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12885564110641124124780348172120675482_0106.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12914852137719528213106151382076426521_0027.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12914852137719528213106151382076426521_0027.dcm new file mode 100644 index 0000000..7d9aced Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.12914852137719528213106151382076426521_0027.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13026882189553070110014645819905598501_0013.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13026882189553070110014645819905598501_0013.dcm new file mode 100644 index 0000000..b837309 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13026882189553070110014645819905598501_0013.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13088226460129055975239515831350209025_0042.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13088226460129055975239515831350209025_0042.dcm new file mode 100644 index 0000000..52c5546 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13088226460129055975239515831350209025_0042.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13177068824135771346541702609296716264_0066.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13177068824135771346541702609296716264_0066.dcm new file mode 100644 index 0000000..c1839e7 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13177068824135771346541702609296716264_0066.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13180558193595746352876866012229695927_0041.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13180558193595746352876866012229695927_0041.dcm new file mode 100644 index 0000000..48faf37 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13180558193595746352876866012229695927_0041.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13279506995854811530288260941141097825_0101.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13279506995854811530288260941141097825_0101.dcm new file mode 100644 index 0000000..775011b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13279506995854811530288260941141097825_0101.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13325879776755284115561768257231716879_0054.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13325879776755284115561768257231716879_0054.dcm new file mode 100644 index 0000000..2c38fb0 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13325879776755284115561768257231716879_0054.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13351750473628813325731887048202809973_0095.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13351750473628813325731887048202809973_0095.dcm new file mode 100644 index 0000000..4ea33cc Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13351750473628813325731887048202809973_0095.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13516832076716195744220411502566771590_0177.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13516832076716195744220411502566771590_0177.dcm new file mode 100644 index 0000000..9b113b0 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.13516832076716195744220411502566771590_0177.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14016567237715966583717995748013778587_0185.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14016567237715966583717995748013778587_0185.dcm new file mode 100644 index 0000000..9fd7042 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14016567237715966583717995748013778587_0185.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14517314468596919151751080970172998693_0126.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14517314468596919151751080970172998693_0126.dcm new file mode 100644 index 0000000..a1037e2 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14517314468596919151751080970172998693_0126.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14655583803976921763742204076963559729_0011.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14655583803976921763742204076963559729_0011.dcm new file mode 100644 index 0000000..d73e653 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14655583803976921763742204076963559729_0011.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14861678773739300401544721990041001379_0080.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14861678773739300401544721990041001379_0080.dcm new file mode 100644 index 0000000..bf1e158 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14861678773739300401544721990041001379_0080.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14939144640314503908997028404724242203_0147.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14939144640314503908997028404724242203_0147.dcm new file mode 100644 index 0000000..3c7ea71 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.14939144640314503908997028404724242203_0147.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.15191415529700024827126745684933095938_0128.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.15191415529700024827126745684933095938_0128.dcm new file mode 100644 index 0000000..ad0eac1 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.15191415529700024827126745684933095938_0128.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.16649080915454745108451335193449192117_0182.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.16649080915454745108451335193449192117_0182.dcm new file mode 100644 index 0000000..6874dd1 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.16649080915454745108451335193449192117_0182.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.18275153002872999329101412031413072709_0158.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.18275153002872999329101412031413072709_0158.dcm new file mode 100644 index 0000000..4022dff Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.18275153002872999329101412031413072709_0158.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.19235303867720835454741238256961412013_0161.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.19235303867720835454741238256961412013_0161.dcm new file mode 100644 index 0000000..858124b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.19235303867720835454741238256961412013_0161.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.19556581182592551742377886296687360843_0149.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.19556581182592551742377886296687360843_0149.dcm new file mode 100644 index 0000000..efc7d92 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.19556581182592551742377886296687360843_0149.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.19689301318585088069985972527719732244_0179.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.19689301318585088069985972527719732244_0179.dcm new file mode 100644 index 0000000..178b5a7 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.19689301318585088069985972527719732244_0179.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.20824494061583723788452886260142888334_0184.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.20824494061583723788452886260142888334_0184.dcm new file mode 100644 index 0000000..906165c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.20824494061583723788452886260142888334_0184.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.21523990033383418415509069402285387554_0048.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.21523990033383418415509069402285387554_0048.dcm new file mode 100644 index 0000000..9a7e00b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.21523990033383418415509069402285387554_0048.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.22224134942718414415799911361209627390_0071.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.22224134942718414415799911361209627390_0071.dcm new file mode 100644 index 0000000..0de431b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.22224134942718414415799911361209627390_0071.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.22225242587115201994567398335167126568_0038.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.22225242587115201994567398335167126568_0038.dcm new file mode 100644 index 0000000..749488c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.22225242587115201994567398335167126568_0038.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.22409968754918766905764012115109905046_0196.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.22409968754918766905764012115109905046_0196.dcm new file mode 100644 index 0000000..6047bb1 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.22409968754918766905764012115109905046_0196.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.23319223360516969306261972525594941562_0169.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.23319223360516969306261972525594941562_0169.dcm new file mode 100644 index 0000000..3b07d7d Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.23319223360516969306261972525594941562_0169.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.23463520018354429876096586738429506359_0109.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.23463520018354429876096586738429506359_0109.dcm new file mode 100644 index 0000000..4237196 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.23463520018354429876096586738429506359_0109.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.24112209291195890802583254242204854214_0130.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.24112209291195890802583254242204854214_0130.dcm new file mode 100644 index 0000000..ae537e9 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.24112209291195890802583254242204854214_0130.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.24642399616806894111827757657843149612_0200.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.24642399616806894111827757657843149612_0200.dcm new file mode 100644 index 0000000..2c4264c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.24642399616806894111827757657843149612_0200.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.26000751719366751322374583705194661209_0137.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.26000751719366751322374583705194661209_0137.dcm new file mode 100644 index 0000000..374f259 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.26000751719366751322374583705194661209_0137.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.26088975929580293861337957493457127508_0079.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.26088975929580293861337957493457127508_0079.dcm new file mode 100644 index 0000000..5e3cd95 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.26088975929580293861337957493457127508_0079.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.27807570372108126386074791363112391619_0021.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.27807570372108126386074791363112391619_0021.dcm new file mode 100644 index 0000000..1d6d230 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.27807570372108126386074791363112391619_0021.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.27977703993723801547231985062417662601_0189.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.27977703993723801547231985062417662601_0189.dcm new file mode 100644 index 0000000..e14c6ad Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.27977703993723801547231985062417662601_0189.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.30599613941365191763050592359202444038_0020.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.30599613941365191763050592359202444038_0020.dcm new file mode 100644 index 0000000..ce33a30 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.30599613941365191763050592359202444038_0020.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.31266449919728646024116877408510646321_0018.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.31266449919728646024116877408510646321_0018.dcm new file mode 100644 index 0000000..f74b6a0 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.31266449919728646024116877408510646321_0018.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.31604805982204822179651966338855866899_0009.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.31604805982204822179651966338855866899_0009.dcm new file mode 100644 index 0000000..3440f22 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.31604805982204822179651966338855866899_0009.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.32766547912138407969712104478108405866_0148.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.32766547912138407969712104478108405866_0148.dcm new file mode 100644 index 0000000..82beff3 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.32766547912138407969712104478108405866_0148.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.33608599717622037156512101735823186583_0047.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.33608599717622037156512101735823186583_0047.dcm new file mode 100644 index 0000000..cd41b41 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.33608599717622037156512101735823186583_0047.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.34735621737749806558664230587225695360_0004.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.34735621737749806558664230587225695360_0004.dcm new file mode 100644 index 0000000..f2a7a4c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.34735621737749806558664230587225695360_0004.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.34797670276785353667469904334538932118_0060.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.34797670276785353667469904334538932118_0060.dcm new file mode 100644 index 0000000..4b05ff9 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.34797670276785353667469904334538932118_0060.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.35363031636694818970185831741062581285_0131.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.35363031636694818970185831741062581285_0131.dcm new file mode 100644 index 0000000..98a0434 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.35363031636694818970185831741062581285_0131.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.35373151878689324930840944809591919353_0040.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.35373151878689324930840944809591919353_0040.dcm new file mode 100644 index 0000000..588e3ba Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.35373151878689324930840944809591919353_0040.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.36591552915889029209591376241231864977_0124.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.36591552915889029209591376241231864977_0124.dcm new file mode 100644 index 0000000..3a18002 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.36591552915889029209591376241231864977_0124.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.37504317292699098683561963687143809011_0094.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.37504317292699098683561963687143809011_0094.dcm new file mode 100644 index 0000000..f163448 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.37504317292699098683561963687143809011_0094.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.38172888189319753170668696161889636609_0132.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.38172888189319753170668696161889636609_0132.dcm new file mode 100644 index 0000000..c54d750 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.38172888189319753170668696161889636609_0132.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.38732063152165494537866361757637035852_0036.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.38732063152165494537866361757637035852_0036.dcm new file mode 100644 index 0000000..65625ad Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.38732063152165494537866361757637035852_0036.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.41584741764700024505806596578552087619_0145.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.41584741764700024505806596578552087619_0145.dcm new file mode 100644 index 0000000..162411a Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.41584741764700024505806596578552087619_0145.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.41619216837614517002914198713198824425_0092.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.41619216837614517002914198713198824425_0092.dcm new file mode 100644 index 0000000..b10012c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.41619216837614517002914198713198824425_0092.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.42219554535477509326010768527150508084_0181.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.42219554535477509326010768527150508084_0181.dcm new file mode 100644 index 0000000..f938eed Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.42219554535477509326010768527150508084_0181.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.43046959664713528208096855866440999697_0024.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.43046959664713528208096855866440999697_0024.dcm new file mode 100644 index 0000000..87e39a7 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.43046959664713528208096855866440999697_0024.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.43687214078819872461532726133587582074_0125.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.43687214078819872461532726133587582074_0125.dcm new file mode 100644 index 0000000..06e8857 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.43687214078819872461532726133587582074_0125.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.44070317731376634640202197580115337482_0107.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.44070317731376634640202197580115337482_0107.dcm new file mode 100644 index 0000000..fc8211d Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.44070317731376634640202197580115337482_0107.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.44707075169744243444817614766825613480_0138.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.44707075169744243444817614766825613480_0138.dcm new file mode 100644 index 0000000..8f58815 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.44707075169744243444817614766825613480_0138.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.45149785166160489036464725874194643457_0043.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.45149785166160489036464725874194643457_0043.dcm new file mode 100644 index 0000000..fdc8259 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.45149785166160489036464725874194643457_0043.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.46011459339827655475364244937892868563_0068.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.46011459339827655475364244937892868563_0068.dcm new file mode 100644 index 0000000..021a12f Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.46011459339827655475364244937892868563_0068.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.46475132691667019128774944076618871078_0100.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.46475132691667019128774944076618871078_0100.dcm new file mode 100644 index 0000000..2f26aa9 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.46475132691667019128774944076618871078_0100.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.47156410811214966567793291753358255956_0029.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.47156410811214966567793291753358255956_0029.dcm new file mode 100644 index 0000000..b14ca4f Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.47156410811214966567793291753358255956_0029.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.47496442340097372397453805416602873288_0180.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.47496442340097372397453805416602873288_0180.dcm new file mode 100644 index 0000000..0d16a31 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.47496442340097372397453805416602873288_0180.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.47519282047103411016286162183617304434_0005.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.47519282047103411016286162183617304434_0005.dcm new file mode 100644 index 0000000..fcc748d Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.47519282047103411016286162183617304434_0005.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.50804880342297706836146157343060583375_0151.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.50804880342297706836146157343060583375_0151.dcm new file mode 100644 index 0000000..0474255 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.50804880342297706836146157343060583375_0151.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.50952590694265535269766074734765241137_0073.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.50952590694265535269766074734765241137_0073.dcm new file mode 100644 index 0000000..7264c0d Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.50952590694265535269766074734765241137_0073.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.51656102760547847030856584305570211569_0099.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.51656102760547847030856584305570211569_0099.dcm new file mode 100644 index 0000000..96676d8 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.51656102760547847030856584305570211569_0099.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.52719632518969958438100587100404865437_0117.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.52719632518969958438100587100404865437_0117.dcm new file mode 100644 index 0000000..e683068 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.52719632518969958438100587100404865437_0117.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.54872986627215421189419727209933267850_0195.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.54872986627215421189419727209933267850_0195.dcm new file mode 100644 index 0000000..59d940d Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.54872986627215421189419727209933267850_0195.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.54884825622261665677932031825055746444_0078.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.54884825622261665677932031825055746444_0078.dcm new file mode 100644 index 0000000..09695eb Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.54884825622261665677932031825055746444_0078.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.55112657124386473297886311530997917145_0152.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.55112657124386473297886311530997917145_0152.dcm new file mode 100644 index 0000000..676d528 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.55112657124386473297886311530997917145_0152.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.56582612022317954649962603303769943294_0070.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.56582612022317954649962603303769943294_0070.dcm new file mode 100644 index 0000000..939afd3 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.56582612022317954649962603303769943294_0070.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.56582886729628787807260904394284796951_0072.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.56582886729628787807260904394284796951_0072.dcm new file mode 100644 index 0000000..32c24b0 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.56582886729628787807260904394284796951_0072.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.57094824582536845449937355300587614261_0164.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.57094824582536845449937355300587614261_0164.dcm new file mode 100644 index 0000000..7c51eb7 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.57094824582536845449937355300587614261_0164.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.57195049107763091780695061417159911157_0191.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.57195049107763091780695061417159911157_0191.dcm new file mode 100644 index 0000000..6c1ebf0 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.57195049107763091780695061417159911157_0191.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.58340044725781315887893607099248283319_0064.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.58340044725781315887893607099248283319_0064.dcm new file mode 100644 index 0000000..47bbf9b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.58340044725781315887893607099248283319_0064.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.58579857231245960754974687438650962341_0098.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.58579857231245960754974687438650962341_0098.dcm new file mode 100644 index 0000000..92b7c9b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.58579857231245960754974687438650962341_0098.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.58685450339530636880740796546824691238_0050.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.58685450339530636880740796546824691238_0050.dcm new file mode 100644 index 0000000..6190f0a Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.58685450339530636880740796546824691238_0050.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.59708114256360165467184660236733019345_0014.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.59708114256360165467184660236733019345_0014.dcm new file mode 100644 index 0000000..7e0f716 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.59708114256360165467184660236733019345_0014.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.60552025126515146263183609054938917314_0081.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.60552025126515146263183609054938917314_0081.dcm new file mode 100644 index 0000000..34e75ca Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.60552025126515146263183609054938917314_0081.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.60556172113167737661904539630783535531_0197.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.60556172113167737661904539630783535531_0197.dcm new file mode 100644 index 0000000..bddae15 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.60556172113167737661904539630783535531_0197.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.61004772635719488581847675327127793301_0175.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.61004772635719488581847675327127793301_0175.dcm new file mode 100644 index 0000000..f2bd537 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.61004772635719488581847675327127793301_0175.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.62503774449731888376300341182233460582_0088.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.62503774449731888376300341182233460582_0088.dcm new file mode 100644 index 0000000..f688792 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.62503774449731888376300341182233460582_0088.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.62746631314035983707934774989562767942_0104.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.62746631314035983707934774989562767942_0104.dcm new file mode 100644 index 0000000..d550bbf Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.62746631314035983707934774989562767942_0104.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.63330041589725742645551855425923355502_0146.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.63330041589725742645551855425923355502_0146.dcm new file mode 100644 index 0000000..bd92c43 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.63330041589725742645551855425923355502_0146.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.64564345934417554774763474929069086169_0034.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.64564345934417554774763474929069086169_0034.dcm new file mode 100644 index 0000000..3477c7c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.64564345934417554774763474929069086169_0034.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.66257646679652660110230052354692313748_0115.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.66257646679652660110230052354692313748_0115.dcm new file mode 100644 index 0000000..5676218 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.66257646679652660110230052354692313748_0115.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.66672302211724967075574332052905787683_0194.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.66672302211724967075574332052905787683_0194.dcm new file mode 100644 index 0000000..2ebe6d4 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.66672302211724967075574332052905787683_0194.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.67466432712635733239338720274643912807_0186.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.67466432712635733239338720274643912807_0186.dcm new file mode 100644 index 0000000..48aed82 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.67466432712635733239338720274643912807_0186.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.69655823479168692873321460377222603983_0076.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.69655823479168692873321460377222603983_0076.dcm new file mode 100644 index 0000000..edf2d08 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.69655823479168692873321460377222603983_0076.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.69742999074472885497246657635530845141_0160.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.69742999074472885497246657635530845141_0160.dcm new file mode 100644 index 0000000..e55641e Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.69742999074472885497246657635530845141_0160.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.69881781889287128895805581890741226838_0025.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.69881781889287128895805581890741226838_0025.dcm new file mode 100644 index 0000000..51ade8b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.69881781889287128895805581890741226838_0025.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70437413427493947062937090328508530329_0086.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70437413427493947062937090328508530329_0086.dcm new file mode 100644 index 0000000..c2895b9 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70437413427493947062937090328508530329_0086.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70458275474060365461577912441520380872_0120.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70458275474060365461577912441520380872_0120.dcm new file mode 100644 index 0000000..bdfe8c1 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70458275474060365461577912441520380872_0120.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70541310212266079433961165925946595704_0039.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70541310212266079433961165925946595704_0039.dcm new file mode 100644 index 0000000..9dea5d9 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70541310212266079433961165925946595704_0039.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70700206091613907816539660438656585674_0103.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70700206091613907816539660438656585674_0103.dcm new file mode 100644 index 0000000..4c353ee Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70700206091613907816539660438656585674_0103.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70981127946925036844621358701282501131_0110.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70981127946925036844621358701282501131_0110.dcm new file mode 100644 index 0000000..3bca0a4 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.70981127946925036844621358701282501131_0110.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.71360043853440202230184775128580628359_0187.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.71360043853440202230184775128580628359_0187.dcm new file mode 100644 index 0000000..2622766 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.71360043853440202230184775128580628359_0187.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.71744222273307842977489582170897375194_0097.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.71744222273307842977489582170897375194_0097.dcm new file mode 100644 index 0000000..83bab8e Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.71744222273307842977489582170897375194_0097.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.71872049398423603878060820274208160354_0127.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.71872049398423603878060820274208160354_0127.dcm new file mode 100644 index 0000000..8e6beb6 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.71872049398423603878060820274208160354_0127.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.72194541105642210996352467733473415927_0188.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.72194541105642210996352467733473415927_0188.dcm new file mode 100644 index 0000000..db88c5d Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.72194541105642210996352467733473415927_0188.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.72286629998852962167999728564287471451_0199.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.72286629998852962167999728564287471451_0199.dcm new file mode 100644 index 0000000..da6f2b9 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.72286629998852962167999728564287471451_0199.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.72703935753844209214710752808234197690_0087.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.72703935753844209214710752808234197690_0087.dcm new file mode 100644 index 0000000..a496a13 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.72703935753844209214710752808234197690_0087.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.73534227436225346213909613773359559832_0001.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.73534227436225346213909613773359559832_0001.dcm new file mode 100644 index 0000000..4ecf104 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.73534227436225346213909613773359559832_0001.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.73962549619378353607443463809682559618_0019.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.73962549619378353607443463809682559618_0019.dcm new file mode 100644 index 0000000..6bca7ed Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.73962549619378353607443463809682559618_0019.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.74753345815699225941377000825944538973_0022.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.74753345815699225941377000825944538973_0022.dcm new file mode 100644 index 0000000..836c6f2 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.74753345815699225941377000825944538973_0022.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.75274197396838206088642049103064629599_0150.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.75274197396838206088642049103064629599_0150.dcm new file mode 100644 index 0000000..b7fa42c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.75274197396838206088642049103064629599_0150.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.75679525789749011323789797723835733555_0157.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.75679525789749011323789797723835733555_0157.dcm new file mode 100644 index 0000000..5ada03a Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.75679525789749011323789797723835733555_0157.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76062407113509702486552996841359174891_0112.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76062407113509702486552996841359174891_0112.dcm new file mode 100644 index 0000000..1a56d4b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76062407113509702486552996841359174891_0112.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76107463393208653734025360666126965420_0136.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76107463393208653734025360666126965420_0136.dcm new file mode 100644 index 0000000..718077b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76107463393208653734025360666126965420_0136.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76108043550661585630092481681119844669_0008.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76108043550661585630092481681119844669_0008.dcm new file mode 100644 index 0000000..99f2185 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76108043550661585630092481681119844669_0008.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76878162509366599539536047834153654925_0129.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76878162509366599539536047834153654925_0129.dcm new file mode 100644 index 0000000..33ff782 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76878162509366599539536047834153654925_0129.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76927385740388559741279958859752042369_0057.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76927385740388559741279958859752042369_0057.dcm new file mode 100644 index 0000000..92278e5 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.76927385740388559741279958859752042369_0057.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.77027650488290477114826710008013322428_0155.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.77027650488290477114826710008013322428_0155.dcm new file mode 100644 index 0000000..541cd77 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.77027650488290477114826710008013322428_0155.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.77864313666963257943750869508395079849_0077.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.77864313666963257943750869508395079849_0077.dcm new file mode 100644 index 0000000..ec61d16 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.77864313666963257943750869508395079849_0077.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.77940372634125369384079877507903077360_0053.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.77940372634125369384079877507903077360_0053.dcm new file mode 100644 index 0000000..04fdd9d Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.77940372634125369384079877507903077360_0053.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.78798857495667447999450412502650286066_0049.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.78798857495667447999450412502650286066_0049.dcm new file mode 100644 index 0000000..c9d43fe Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.78798857495667447999450412502650286066_0049.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.80121361408220291192393474152763164631_0154.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.80121361408220291192393474152763164631_0154.dcm new file mode 100644 index 0000000..272dfc5 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.80121361408220291192393474152763164631_0154.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.80241423531106048775835390226563268497_0045.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.80241423531106048775835390226563268497_0045.dcm new file mode 100644 index 0000000..b1a53a1 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.80241423531106048775835390226563268497_0045.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.81400396690548551124170159623739679913_0065.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.81400396690548551124170159623739679913_0065.dcm new file mode 100644 index 0000000..0bb7cd8 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.81400396690548551124170159623739679913_0065.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.81416516693839205889739990039785533786_0166.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.81416516693839205889739990039785533786_0166.dcm new file mode 100644 index 0000000..5cf1372 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.81416516693839205889739990039785533786_0166.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.81518093902703980076483051397213455513_0031.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.81518093902703980076483051397213455513_0031.dcm new file mode 100644 index 0000000..ac4427e Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.81518093902703980076483051397213455513_0031.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.82355487385415946281971731845687107688_0144.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.82355487385415946281971731845687107688_0144.dcm new file mode 100644 index 0000000..ca5589a Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.82355487385415946281971731845687107688_0144.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.82556077242561872970356086468644319689_0083.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.82556077242561872970356086468644319689_0083.dcm new file mode 100644 index 0000000..7de2a4c Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.82556077242561872970356086468644319689_0083.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.83961296886937518307987525236175765261_0044.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.83961296886937518307987525236175765261_0044.dcm new file mode 100644 index 0000000..ba2cbf9 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.83961296886937518307987525236175765261_0044.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85124456505928919310696998515734055740_0006.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85124456505928919310696998515734055740_0006.dcm new file mode 100644 index 0000000..bf159b2 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85124456505928919310696998515734055740_0006.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85230074389416973667012380556957906668_0176.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85230074389416973667012380556957906668_0176.dcm new file mode 100644 index 0000000..9e26f38 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85230074389416973667012380556957906668_0176.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85634156481661376528324807868150491513_0135.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85634156481661376528324807868150491513_0135.dcm new file mode 100644 index 0000000..6157899 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85634156481661376528324807868150491513_0135.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85831942758359056765517552978793910448_0174.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85831942758359056765517552978793910448_0174.dcm new file mode 100644 index 0000000..1554ffc Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85831942758359056765517552978793910448_0174.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85946580605406611907594496460967159764_0118.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85946580605406611907594496460967159764_0118.dcm new file mode 100644 index 0000000..1c7177b Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.85946580605406611907594496460967159764_0118.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.86362850120209710825858158757628232505_0061.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.86362850120209710825858158757628232505_0061.dcm new file mode 100644 index 0000000..1f47918 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.86362850120209710825858158757628232505_0061.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.86608056182958882003234777555294371643_0153.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.86608056182958882003234777555294371643_0153.dcm new file mode 100644 index 0000000..9dc5019 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.86608056182958882003234777555294371643_0153.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.86624067718198696372412580492820575035_0075.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.86624067718198696372412580492820575035_0075.dcm new file mode 100644 index 0000000..4536eb8 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.86624067718198696372412580492820575035_0075.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87304944897631749171710605620001081498_0046.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87304944897631749171710605620001081498_0046.dcm new file mode 100644 index 0000000..0c18a63 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87304944897631749171710605620001081498_0046.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87421212308562612135713565552110967055_0198.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87421212308562612135713565552110967055_0198.dcm new file mode 100644 index 0000000..fdd224f Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87421212308562612135713565552110967055_0198.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87466641235503840215338244816839010227_0015.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87466641235503840215338244816839010227_0015.dcm new file mode 100644 index 0000000..bafb142 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87466641235503840215338244816839010227_0015.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87720424883870345418127216632107969877_0051.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87720424883870345418127216632107969877_0051.dcm new file mode 100644 index 0000000..9ae08a6 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87720424883870345418127216632107969877_0051.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87988776506847792350280343510486193706_0193.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87988776506847792350280343510486193706_0193.dcm new file mode 100644 index 0000000..f7bb356 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.87988776506847792350280343510486193706_0193.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.88260535410311511624544452852551608224_0016.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.88260535410311511624544452852551608224_0016.dcm new file mode 100644 index 0000000..0ba31c0 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.88260535410311511624544452852551608224_0016.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.88922424740846302082833337552080520472_0023.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.88922424740846302082833337552080520472_0023.dcm new file mode 100644 index 0000000..fb66816 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.88922424740846302082833337552080520472_0023.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.89204256585343331332577009295601154229_0141.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.89204256585343331332577009295601154229_0141.dcm new file mode 100644 index 0000000..304a1b8 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.89204256585343331332577009295601154229_0141.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.89869355212817852833403789602817581488_0170.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.89869355212817852833403789602817581488_0170.dcm new file mode 100644 index 0000000..2c7a2b0 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.89869355212817852833403789602817581488_0170.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.90608786435662464525522889091842040487_0007.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.90608786435662464525522889091842040487_0007.dcm new file mode 100644 index 0000000..926d0de Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.90608786435662464525522889091842040487_0007.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.91735608693975744810576393647608414242_0026.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.91735608693975744810576393647608414242_0026.dcm new file mode 100644 index 0000000..7b24471 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.91735608693975744810576393647608414242_0026.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.92487850644889593077556246702773589764_0121.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.92487850644889593077556246702773589764_0121.dcm new file mode 100644 index 0000000..56fecf3 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.92487850644889593077556246702773589764_0121.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93308078862609587818350203072451902127_0037.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93308078862609587818350203072451902127_0037.dcm new file mode 100644 index 0000000..1152ce9 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93308078862609587818350203072451902127_0037.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93398026149147419302693125170252588512_0178.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93398026149147419302693125170252588512_0178.dcm new file mode 100644 index 0000000..f908529 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93398026149147419302693125170252588512_0178.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93471129130156335573131707707751325007_0183.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93471129130156335573131707707751325007_0183.dcm new file mode 100644 index 0000000..e5225cc Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93471129130156335573131707707751325007_0183.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93957938516587121743969691162831935352_0030.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93957938516587121743969691162831935352_0030.dcm new file mode 100644 index 0000000..da57459 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.93957938516587121743969691162831935352_0030.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.94442538236049744126776506265679394697_0058.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.94442538236049744126776506265679394697_0058.dcm new file mode 100644 index 0000000..38f2442 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.94442538236049744126776506265679394697_0058.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.94514520964491748093626173162123832536_0093.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.94514520964491748093626173162123832536_0093.dcm new file mode 100644 index 0000000..def87e0 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.94514520964491748093626173162123832536_0093.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.95521333958471138526325887812478776741_0085.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.95521333958471138526325887812478776741_0085.dcm new file mode 100644 index 0000000..cf464c7 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.95521333958471138526325887812478776741_0085.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.95593206116417343611419448354772651986_0067.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.95593206116417343611419448354772651986_0067.dcm new file mode 100644 index 0000000..45072a6 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.95593206116417343611419448354772651986_0067.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.95759537217101808726029642120685187227_0055.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.95759537217101808726029642120685187227_0055.dcm new file mode 100644 index 0000000..5a6ab4a Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.95759537217101808726029642120685187227_0055.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.96018057036866905525164849227101125285_0139.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.96018057036866905525164849227101125285_0139.dcm new file mode 100644 index 0000000..c4a2d20 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.96018057036866905525164849227101125285_0139.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.96209810557035908979383165802818493783_0035.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.96209810557035908979383165802818493783_0035.dcm new file mode 100644 index 0000000..d382ebf Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.96209810557035908979383165802818493783_0035.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.97133580607834826924284455789138927087_0140.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.97133580607834826924284455789138927087_0140.dcm new file mode 100644 index 0000000..debc500 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.97133580607834826924284455789138927087_0140.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.97336857202875375983621153016145759192_0082.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.97336857202875375983621153016145759192_0082.dcm new file mode 100644 index 0000000..269d5c1 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.97336857202875375983621153016145759192_0082.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.98623106733079893439133136631078037560_0156.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.98623106733079893439133136631078037560_0156.dcm new file mode 100644 index 0000000..658d453 Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.98623106733079893439133136631078037560_0156.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.98831649352669721269597532531379122420_0119.dcm b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.98831649352669721269597532531379122420_0119.dcm new file mode 100644 index 0000000..0cee24d Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/CT_1.2.826.0.1.3680043.8.498.98831649352669721269597532531379122420_0119.dcm differ diff --git a/examples/testData/SimpleFantomWithStruct/RS1.2.826.0.1.3680043.8.498.84013257063663267327474594711005032139.dcm b/examples/testData/SimpleFantomWithStruct/RS1.2.826.0.1.3680043.8.498.84013257063663267327474594711005032139.dcm new file mode 100644 index 0000000..233522a Binary files /dev/null and b/examples/testData/SimpleFantomWithStruct/RS1.2.826.0.1.3680043.8.498.84013257063663267327474594711005032139.dcm differ diff --git a/examples/testData/lightDynSeq.p b/examples/testData/lightDynSeq.p new file mode 100644 index 0000000..394d66f Binary files /dev/null and b/examples/testData/lightDynSeq.p differ diff --git a/examples/testData/veryLightDynMod.p b/examples/testData/veryLightDynMod.p new file mode 100644 index 0000000..26df5c8 Binary files /dev/null and b/examples/testData/veryLightDynMod.p differ diff --git a/index.rst b/index.rst index b0f9a7f..7dd1ead 100644 --- a/index.rst +++ b/index.rst @@ -3,16 +3,13 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -OpenTPS documentation -===================== - -Add your content using ``reStructuredText`` syntax. See the -`reStructuredText `_ -documentation for details. - +OpenTPS examples +================ .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: Examples: auto_examples/index + auto_community/index + diff --git a/requirements.txt b/requirements.txt index db5d81e..fe8e1a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ matplotlib numpy +scipy==1.10.1 +psutil +cupy-cuda12x \ No newline at end of file diff --git a/sg_execution_times.rst b/sg_execution_times.rst index 9fd660e..4f6f295 100644 --- a/sg_execution_times.rst +++ b/sg_execution_times.rst @@ -6,7 +6,7 @@ Computation times ================= -**00:00.595** total execution time for 3 files **from all galleries**: +**00:14.683** total execution time for 44 files **from all galleries**: .. container:: @@ -32,12 +32,135 @@ Computation times * - Example - Time - Mem (MB) - * - :ref:`sphx_glr_auto_examples_examples2_plot_testExample.py` (``examples\examples2\plot_testExample.py``) - - 00:00.595 + * - :ref:`sphx_glr_auto_examples_DoseComputation_run_SimpleDoseCalculation.py` (``examples/DoseComputation/run_SimpleDoseCalculation.py``) + - 00:02.595 - 0.0 - * - :ref:`sphx_glr_auto_examples_examples1_DoseOptimizationRealPatient.py` (``examples\examples1\DoseOptimizationRealPatient.py``) + * - :ref:`sphx_glr_auto_examples_PlanOptimization_run_simpleOptimization_createDicomStudy.py` (``examples/PlanOptimization/run_simpleOptimization_createDicomStudy.py``) + - 00:02.053 + - 0.0 + * - :ref:`sphx_glr_auto_examples_DoseComputation_run_UseOfRangeShifterForProtonPlan.py` (``examples/DoseComputation/run_UseOfRangeShifterForProtonPlan.py``) + - 00:02.005 + - 0.0 + * - :ref:`sphx_glr_auto_examples_PlanOptimization_run_boundConstraintsOpti.py` (``examples/PlanOptimization/run_boundConstraintsOpti.py``) + - 00:01.844 + - 0.0 + * - :ref:`sphx_glr_auto_examples_PlanOptimization_run_SimpleOptimizationProton.py` (``examples/PlanOptimization/run_SimpleOptimizationProton.py``) + - 00:01.842 + - 0.0 + * - :ref:`sphx_glr_auto_examples_PlanOptimization_run_4DprotonEvaluation.py` (``examples/PlanOptimization/run_4DprotonEvaluation.py``) + - 00:01.376 + - 0.0 + * - :ref:`sphx_glr_auto_examples_PlanOptimization_run_beamletFreeOpti.py` (``examples/PlanOptimization/run_beamletFreeOpti.py``) + - 00:01.193 + - 0.0 + * - :ref:`sphx_glr_auto_examples_PlanOptimization_run_SimpleOptimizationPhoton.py` (``examples/PlanOptimization/run_SimpleOptimizationPhoton.py``) + - 00:00.533 + - 0.0 + * - :ref:`sphx_glr_auto_examples_PlanOptimization_run_evaluatePhotonRobustness.py` (``examples/PlanOptimization/run_evaluatePhotonRobustness.py``) + - 00:00.528 + - 0.0 + * - :ref:`sphx_glr_auto_examples_DoseComputation_run_protonPlanCreationAndDoseCalculation.py` (``examples/DoseComputation/run_protonPlanCreationAndDoseCalculation.py``) + - 00:00.445 + - 0.0 + * - :ref:`sphx_glr_auto_examples_DoseComputation_run_photonPlanCreationAndDoseCalculation.py` (``examples/DoseComputation/run_photonPlanCreationAndDoseCalculation.py``) + - 00:00.268 + - 0.0 + * - :ref:`sphx_glr_auto_community_CommunityExample_SimpleDoseComputationAndOptOnCT.py` (``community/CommunityExample/SimpleDoseComputationAndOptOnCT.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_community_Template_run_template.py` (``community/Template/run_template.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_DoseDeliverySimulation_PlanDeliverySimulation.py` (``examples/DoseDeliverySimulation/PlanDeliverySimulation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_DoseDeliverySimulation_run_PBSDeliveryTimings.py` (``examples/DoseDeliverySimulation/run_PBSDeliveryTimings.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_OthersExample_create3DSeqFromDicom.py` (``examples/OthersExample/create3DSeqFromDicom.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_OthersExample_create3DSeqFromImages.py` (``examples/OthersExample/create3DSeqFromImages.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_OthersExample_createDynamic3DModelFromDicomFields.py` (``examples/OthersExample/createDynamic3DModelFromDicomFields.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_OthersExample_createModelWithROIsFromDicomImages.py` (``examples/OthersExample/createModelWithROIsFromDicomImages.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_OthersExample_crop3DDataAroundROI.py` (``examples/OthersExample/crop3DDataAroundROI.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_OthersExample_exampleImageResampling.py` (``examples/OthersExample/exampleImageResampling.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_OthersExample_generateDRRAndGTVMasks.py` (``examples/OthersExample/generateDRRAndGTVMasks.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_OthersExample_run_generateRandomSamplesFromModel.py` (``examples/OthersExample/run_generateRandomSamplesFromModel.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_PlanOptimization_4DProtonOptimization.py` (``examples/PlanOptimization/4DProtonOptimization.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_PlanOptimization_evaluateProtonRobustness.py` (``examples/PlanOptimization/evaluateProtonRobustness.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_PlanOptimization_robustOptimizationProtons.py` (``examples/PlanOptimization/robustOptimizationProtons.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_Segmentation_run_Segmentation.py` (``examples/Segmentation/run_Segmentation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_dynamicData_exampleInterFractionChanges.py` (``examples/dynamicData/exampleInterFractionChanges.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_dynamicData_run_exampleApplyBaselineShiftToModel.py` (``examples/dynamicData/run_exampleApplyBaselineShiftToModel.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_dynamicData_run_exampleDeformableBreathigDataAugmentation.py` (``examples/dynamicData/run_exampleDeformableBreathigDataAugmentation.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_dynamicData_run_exampleDeformationFromWeightMaps.py` (``examples/dynamicData/run_exampleDeformationFromWeightMaps.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_dynamicData_run_exampleMidP.py` (``examples/dynamicData/run_exampleMidP.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_imageProcessing_cupyVSsitkTransforms.py` (``examples/imageProcessing/cupyVSsitkTransforms.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_imageProcessing_exampleDRRwithTigre.py` (``examples/imageProcessing/exampleDRRwithTigre.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_imageProcessing_exampleTransform3DCupy.py` (``examples/imageProcessing/exampleTransform3DCupy.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_imageProcessing_run_exampleApplyBaselineShift.py` (``examples/imageProcessing/run_exampleApplyBaselineShift.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_imageProcessing_run_exampleDilateBinaryMask.py` (``examples/imageProcessing/run_exampleDilateBinaryMask.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_imageProcessing_run_exampleTransform3D.py` (``examples/imageProcessing/run_exampleTransform3D.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_multiplePythonEnv_backAndForthChild.py` (``examples/multiplePythonEnv/backAndForthChild.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_multiplePythonEnv_run_backAndForthParent.py` (``examples/multiplePythonEnv/run_backAndForthParent.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_multiplePythonEnv_run_sharedMemoryParent.py` (``examples/multiplePythonEnv/run_sharedMemoryParent.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_multiplePythonEnv_sharedMemoryChild.py` (``examples/multiplePythonEnv/sharedMemoryChild.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_auto_examples_registration_run_exampleMorphons.py` (``examples/registration/run_exampleMorphons.py``) - 00:00.000 - 0.0 - * - :ref:`sphx_glr_auto_examples_examples2_test_output.py` (``examples\examples2\test_output.py``) + * - :ref:`sphx_glr_auto_examples_registration_run_exampleRigid.py` (``examples/registration/run_exampleRigid.py``) - 00:00.000 - 0.0