--- title: "espadon overview" author: "Cathy & Jean-Marc Fontbonne" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{espadon overview} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` Easy Study of Patient DICOM Data in Oncology -------------------------------------------------------------------------------- Espadon is a package simplifying the use and exploitation of DICOM data in radiotherapy. Espadon works in a native way (user friendly): - on images (CT, MR, PT) and fully manages changes of frame of reference (REG) - on dosimetry (rt-dose, rt-plan) - on structures (rt-struct) It is also able to use any DICOM format file, as long as the user knows where to look for the information.In addition to the simplified use of the above-mentioned files, espadon contains many functions that allow the user to rework documents to produce new features that can be integrated into machine learning. Among other features, espadon is suitable for pseudonymizing DICOM, decoding them, visualizing them and producing dosimetry data in relation to clinical data. Also see https://espadon.cnrs.fr. ## Input data The espadon package acts directly on the DICOM files or the folders containing them, thanks to espadon functions whose names contain the word "dicom". For example, to load an imaging volume from a CT or MR, simply execute the following instruction: ```{r, eval = FALSE} vol <- load.obj.from.dicom (image_path) ``` image_path represents either the image folder path, or a vector of all the image DICOM file path. To speed up the loading of data when studying a patient's images in depth, espadon offers the ***dicom.to.Rdcm.converter*** data pre-formatting function. This function creates files that are smaller than the original files, without loss of information, and prepares the data for use by espadon's analysis and display functions. The new data format is a Rdcm format. ```{r, eval = FALSE} dicom.to.Rdcm.converter("D:/dcm/patient001", "D:/Rdcm/patient001“) ``` ## DICOM Handling The espadon package allows direct analysis of DICOM files. The following instructions, for example, create a table (R dataframe), and thus to have a synthetic view of the DICOM file content. ```{r, eval = FALSE} dcm.filename <- file.choose () df <- dicom.parser (dicom.raw.data.loader (dcm.filename), as.txt = TRUE, try.parse = TRUE) ``` A file in .xlsx format can also be created with the ***xlsx.from.dcm*** function. Thanks to the ***dicom.set.tag.value*** function, the content of the various tags can be modified or deleted, as far as they are in ascii format. The ***dicom.raw.data.anonymizer*** function provides pseudonymization by shifting dates, changing the patient's PIN and name, removing all patient data except age, weight, and height, and also removing any other name, phone, operator, service, and undocumented tag content. ## Patient overview Espadon provides a unified view of the different imaging objects, by assigning them a name that takes into account their relationship. The following functions (depending on whether the patient's directory contains DICOM or .Rdcm files) create these links. The ***load.patient.from.dicom*** and ***load.patient.from.Rdcm*** functions (depending on whether the patient's folder contains DICOM or .Rdcm files) create an R-list, in which all the DICOM objects of the patient are identified. In this patient list, there is, among others, the element T.MAT, computed from the DICOM reg objects. This one provides all the necessary information to overlay two imaging objects acquired on different machines, or at different times. The ***display.obj.links*** function allows a nice and convenient display of these links. ```{r, eval = FALSE} library(espadon) pat.dir <- choose.dir () # patient folder containing mr, ct, rt-struct, rt-dose, rt-plan and reg… pat <- load.patient.from.dicom (pat.dir) display.obj.links (pat) ``` When the patient object list is loaded into memory, the ***load.obj.data*** function loads the full object information, i.e. voxels content for MR, CT or PT, rt-dose or contour coordinates for rt-struct. ```{r, eval = FALSE} S <- load.obj.data (pat$rtstruct[[1]]) CT <- load.obj.data (pat$ct[[1]]) MR <- load.obj.data (pat$mr[[1]]) D <- load.obj.data (pat$rtdose[[1]]) ``` ## 2D Display Visualization of imaging volumes is essential for controlling the loaded DICOM objects. The espadon package allows this display, with the possibility of using the color palettes of your choice. The ***display.kplane*** function displays a slice plane of the imaging volume in the slice plane geometric frame of reference ```{r, eval = FALSE} CT <- load.obj.from.dicom(ct_dicom_file_path) display.kplane (CT, k =10, col = pal.RVV(1000), breaks = seq(-1000, 1000, length.out = 1001), ord.flip = TRUE, interpolate = TRUE) ``` The ***display.plane*** function displays transverse, frontal or sagittal views in the patient's frame of reference. It allows the superposition of images and contours. ## 3D Display The espadon package has several 3D visualization features: - the ***display.3D.contour*** function displays the contours of the selected region of interest as described in the rt-struct. - the ***display.3D.stack*** function displays selected slices of the imaging volume. - the ***display.3D.sections*** function displays the frontal, sagittal and transverse view at a point. These last 2 functions can use the same color palettes as the 2D visualization functions, except that a color can only be totally transparent or visible. Thanks to the internal management of geometrical frames of reference, the different displays can be mixed, as shown in the example below: ```{r, eval = FALSE} library(espadon) library(rgl) pat.dir <- choose.dir () # patient folder containing mr, ct, rt-struct, and reg pat <- load.patient.from.dicom (pat.dir) S <- load.obj.data(pat$rtstruct[[1]]) CT <- load.obj.data(pat$ct[[1]]) MR <- load.obj.data(pat$mr[[1]]) bg3d ("black") par3d (userMatrix = matrix (c(0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1), ncol = 4), windowRect = c(0, 50, 300, 300), zoom = 0.7) display.3D.contour (S, roi.sname = "eye", display.ref = CT$ref.pseudo, T.MAT = pat$T.MAT) display.3D.stack (MR, k.idx = c(10,35,70, 105, 140,165), display.ref = CT$ref.pseudo, T.MAT = pat$T.MAT, ktext = FALSE) display.3D.sections (CT, cross.pt = c(0, 150, 0), col = pal.RVV (200, alpha = c(rep (0, 90), rep (1, 110))), breaks = seq(-1000, 1000, length.out = 201), border.col = "#844A39") play3d (spin3d (rpm = 4), duration = 15) ``` ## Geometry tools Several espadon functions have been created to transform imaging volumes. - Functions such as ***add.margin***, ***nesting.cube***, ***nesting.roi***, ***nesting.bin*** increase or decrease the number of voxels in the volume. ```{r, eval = FALSE} nesting.MR <- nesting.roi (MR, S, roi.sname = "brain", T.MAT = pat$T.MAT, vol.restrict = TRUE, mar) display.3D.stack (nesting.MR, ktext = FALSE, line.lw d= 2) display.3D.contour (S, roi.sname = "brain", display.ref = MR$ref.pseudo, T.MAT = pat$T.MAT) ``` - The ***vol.regrid*** function resamples the volume to the grid of another imaging volume. ```{r, eval = FALSE} MR.on.CT <- vol.regrid (MR, CT, T.MAT=pat$T.MAT, interpolate = TRUE) display.3D.stack (MR.on.CT, display.ref = CT$ref.pseudo , T.MAT = pat$T.MAT, ktext = FALSE, line.lwd = 2, line.col = "#844A39") ``` - More generally the ***get.plane*** function creates a cut of the volume in any direction. The ***get.direction*** function creates the direction vector from 3 points in space. ```{r, eval = FALSE} # Points determination : centers of the eyes and the chiasm found in rtstruct data roi.idx <- select.names (S$roi.info$roi.pseudo, roi.name c("eye", "chiasm")) pt <- S$roi.info[roi.idx, c("Gx", "Gy", "Gz")] origin.pt <- as.numeric (apply (pt, 2, mean)) # Plane creation in the CT frame of reference new.plane <- get.plane (CT, origin = origin.pt, plane.orientation = orientation.create (pt), interpolate = TRUE) # Display of the new plane, in the cut plane frame of reference tmat <- ref.cutplane.add (new.plane, origin.pt = origin.pt, ref.cutplane = "ref.plane", T.MAT = pat$T.MAT) plan <- vol.in.new.ref (new.plane, "ref.plane", tmat) display.plane (plan, struct= S, roi.idx = idx, T.MAT = tmat, view.coord = plan$patient.xyz0[3], bottom.col = pal.RVV(200), bottom.breaks = seq(-1000, 1000, length.out = 201), main = "cut plane", bg = "#379DA2", sat.transp = T, legend.plot = FALSE, interpolate = TRUE) ``` ## Binary objects Binary objects are volumes whose voxel values are either TRUE, FALSE or NA (i.e. undetermined). They act as a mask, and are used to select only the desired part of a volume. The example below show how to select the brain voxels described in the rtstruct object. ```{r, eval = FALSE} nesting.MR <- nesting.roi (MR, S, roi.sname = "brain", T.MAT = pat$T.MAT, vol.restrict = TRUE, xyz.margin = c(20, 20, 20)) # to reduce the computation time bin.brain <- bin.from.roi (nesting.MR, struct = S, roi.sname = "brain", T.MAT = pat$T.MAT) MR. brain <- vol.from.bin (nesting.MR, bin.brain) ``` The ***bin.from.vol*** function creates binary volumes by selecting voxels that are inside or outside an interval [min, max]. The example below selects voxels with a value greater than 150.* ```{r, eval = FALSE} bin.min.150 <- bin.from.vol (MR.brain, min = 150) display.plane (bin.min.150, view.coord = 0, view.type = "sagi", main = "bin.min.150", bg = "#379DA2", legend.plot = FALSE, sat.transp = TRUE, interpolate = TRUE) ``` Other image processing functions are available: - ***bin.erosion*** decreases the volume of a radius r - ***bin.dilation*** increases the volume of a radius r - ***bin.closing*** is usefull for filling holes that are smaller than the radius and merging two shapes close to each other. - ***bin.opening*** is usefull for removing volumes that are smaller than the radius and smoothing shapes. - ***bin.clustering*** groups and labels TRUE voxels that have a 6-connectivity (i.e. sharing a common side). These functions can be used to separate multiple volumes in the binary selection, as shown below: ```{r, eval = FALSE} bin.smooth <- bin.opening (bin.min.150, radius = 1.5)) bin.cluster <- bin.clustering (bin.smooth) # Creation of ventricle contours bin.ventricle <- bin.from.vol (bin.cluster, min = 1, max = 1) S.ventricle <- struct.from.bin (bin.ventricle , roi.name = "ventricle", roi.color = "#FF0000" ) # Display of the first 3 largest sub-volumes, and ventricle contours library (rgl) bg3d ("black") par3d (userMatrix = matrix (c(0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1), ncol = 4), windowRect = c(0, 50, 300, 300), zoom = 0.7) display.3D.stack(bin.cluster, k.idx = bin.cluster$k.idx, col = c ("#00000000", rainbow (3, alpha = 1)), breaks = seq (0, 3, length.out = 5), border = FALSE, ktext = FALSE) play3d (spin3d (rpm = 4), duration = 15) bg3d ("black") par3d (userMatrix = matrix (c(0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1), ncol = 4), windowRect = c(0, 50, 300, 300), zoom = 1) display.3D.contour (S.ventricle) play3d (spin3d (rpm = 4), duration = 15) ``` ## Meshes The espadon package has the ***mesh.from.bin*** function to create a triangle mesh from a binary volume and the ***display.3D.mesh*** function to display it in any frame of reference. In the example below, the patient folder contains a CT-scan files and a DICOM rtstruct file in which lung and heart are outlined : ```{r, eval = FALSE} library (espadon) patient.folder <- choose.dir () pat <- load.patient.from.dicom (patient.folder, data = TRUE) bin.lung <- bin.sum (bin.from.roi (pat$ct[[1]], struct = pat$rtstruct[[1]], roi.name = "lungl"), bin.from.roi (pat$ct[[1]], struct = pat$rtstruct[[1]], roi.name = "lungr")) bin.heart <- bin.from.roi (pat$ct[[1]], struct = pat$rtstruct[[1]], roi.name = "heart") mesh.lung <- mesh.from.bin (bin.lung) mesh.heart <- mesh.from.bin (bin.heart) display.3D.mesh (mesh.lung, color = "burlywood2", specular = "black", alpha = 0.8) display.3D.mesh (mesh.heart, color = "red", specular = "black", alpha = 1) ``` Sometimes, the mesh contains holes. The ***mesh.repair*** function can be used to repair the mesh by removing the degenerated triangles. ## Histograms Histograms computing is a basic function proposed by R. The espadon package adds the feature to compute these differential histograms over an imaging volume such as CT, MR, PT, rt-dose, and within a predefined area: - either by a region of interest (RoI) of an rt-struct file : ***histo.from.roi*** function - or by a selection by binary volume : ***histo.from.bin**** function. To be able to simulate random shifts of the selection, or variations of contours, the ***histo.from.roi*** function includes Monte Carlo calculations, according to a normal distribution. If the imaging volume is a dose distribution (from an rtdose file), it is then easy to calculate a cumulative histogram, called in this case dose-volume histograms or DVH, using the ***histo.DVH*** function. The functions ***display.dV_dx***, ***display.DVH***, ***display.DVH.pc*** allow to display pretty graphs, including the quantile zones 100%, 95% and 50% of the Monte-Carlo data. In the following example, the patient folder contains a CT-scan files, a rt-dose file and a rt-struct file in which optic chiasm is outlined : ```{r, eval = FALSE} library (espadon) patient.folder <- choose.dir () pat <- load.patient.from.dicom (patient.folder, data = FALSE) S <- load.obj.data (pat$rtstruct[[1]]) CT <- load.obj.data (pat$ct[[1]]) D <- load.obj.data (pat$rtdose[[1]]) # resample D on the cut planes on which the chiasm has been outlined D.on.CT <- vol.regrid (D, CT) # Calculation of histogram with simulation of random displacements according to # a normal distribution of standard deviation 1 mm in all directions: h <- histo.from.roi (D.on.CT, S, roi.name="chiasm", breaks = seq(0, 80, 0.1), alias = "chiasm", MC = 100 ) DVH <- histo.DVH (h, alias = "Chiasm DVH") display.DVH (DVH, MC.plot = TRUE, col = "#ff0000", lwd = 2) ``` ## Radiotherapy indices The espadon package offers the possibility of computing many radiotherapy indices. After specifying the target volumes to be treated (if available), the healthy volumes to be protected (if available), the dose distribution from rt-dose file, the functions ***rt.indices.from.roi*** and ***rt.indices.from.bin*** can calculate, on demand : - dosimetry indices : minimum, maximum, average dose, standard deviation, requested D.x% and requested D.xcc. - volume indices : total volume, surface area and requested V.xGy or V.x%. - conformity indices : prescription isodose target volume (PITV), prescription dose spillage (PDS), conformity index (CI), conformation number (CN), new conformity index (NCI), Dice similarity coefficient (DSC), conformity index based on distance (CIdistance), conformity distance index (CDI), triple point conformity scale (CS3), underdosed lesion factor (ULF), overdosed healthy tissues factor (OHTF), geometric conformity index (gCI), COIN , critical organ scoring index (COSI), generalized COSI (gCOSI). - homogeneity indices from RTOG or ICRU. - gradient indices : gradient index based on volumes ratio, modified gradient index (mGI). In the following example, the patient folder contains a CT-scan files, a rt-dose file and a rt-struct file in which PTV and optic chiasm are outlined. ```{r, eval = FALSE} library (espadon) patient.folder <- choose.dir () pat <- load.patient.from.dicom (patient.folder, data = FALSE) S <- load.obj.data (pat$rtstruct[[1]]) CT <- load.obj.data (pat$ct[[1]]) D <- load.obj.data (pat$rtdose[[1]]) # resample D on the cut planes on which the chiasm has been outlined D.on.CT <- vol.regrid (D, CT) rt.indices.from.roi (D.on.CT, S, target.roi.name = "ptv", healthy.roi.name = "chiasm", presc.dose = 0.9 * 70) ``` The espadon package can also compute the Gamma index and the Chi index in 2D or 3D, using the ***rt.gamma.index*** and ***rt.chi.index***. ## Spatial similarity metrics The espadon package calculates several spatial similarity metrics, such as : - the volumetric DICE similarity defined by DSC = 2 (VA ∩ VB) / (VA+VB) - the Dice-jaccard coefficient defined by DJC = (VA ∩ VB) / (VA ∪ VB) - the mean distance to conformity MDC, over-contouring mean distance over.MDC and under-contouring mean distance under.MDC, defined by Jena et al [1] - the maximum , mean and quantiles of Hausdorff distances - the surface DICE metric defined by Nikolov et al [2] Metrics using the volumes of regions of interest (ROIs) are calculated using the ***sp.similarity.from.bin*** function, and those using surfaces are calculated using the ***sp.similarity.from.mesh*** function. In the following example, the patient's file contains a CT-scan file and an rt-struct file in which the contours of two regions of interest are to be compared. To calculate the spatial similarity metrics, first generate the binary volumes relative to the ROIs to be compared. ```{r, eval = FALSE} library (espadon) patient.folder <- choose.dir () pat <- load.patient.from.dicom (patient.folder, data = FALSE) S <- load.obj.data (pat$rtstruct[[1]]) CT <- load.obj.data (pat$ct[[1]]) # The binary objects for 2 ROIs named "roi A" and "roi B" respectively : bin.A <- bin.from.roi (CT, S, roi.sname = "roi A", alias = "ROI A") bin.B <- bin.from.roi (CT, S, roi.sname = "roi B", alias = "ROI B") # The mesh objects for these 2 ROIs: # FYI: the smooth.iteration argument avoids staircase meshing, thanks to z-axis binning. # As a result, ROI contours are also smoothed in XY. mesh.A <- mesh.from.bin(bin.A, smooth.iteration = 10, alias = "ROI A") mesh.B <- mesh.from.bin(bin.B, smooth.iteration = 10, alias = "ROI B") # spatial similarity sp.similarity.from.bin (bin.A , bin.B) sp.similarity.from.mesh (mesh.A , mesh.B, hausdorff.quantile = c (0.5, 0.95), surface.DSC.tol = 1:3) ``` ### References [1] Jena R, et al. (2010). “A novel algorithm for the morphometric assessment of radiotherapy treatment planning volumes.”Br J Radiol., 83(985), 44-51.doi:10.1259/bjr/27674581. [2] Nikolov S, et al. (2018). “Deep learning to achieve clinically applicable segmentation of head and neck anatomy for radiotherapy.” ArXiv,abs/1809.04430.