From d03bffca0cbeaf4b46341dcf0c278fe914b5b7ac Mon Sep 17 00:00:00 2001 From: Mohamed Nasser Date: Sun, 4 Aug 2024 01:13:38 +0300 Subject: [PATCH 1/2] add new section to the docs for dro --- docs-mk/docs/references/dro/dro_read_mri.md | 3 + .../references/{models.md => dro/index.md} | 0 docs-mk/docs/references/dro/signal_enhance.md | 3 + docs-mk/docs/user-guide/dro.md | 9 +++ docs-mk/docs/user-guide/index.md | 2 + docs-mk/docs/user-guide/synthetic.md | 62 +++++++++++++++++++ docs-mk/mkdocs.yml | 14 ++++- src/osipi/DRO/DICOM_processing.py | 35 +++++++++-- src/osipi/DRO/__init__.py | 2 + 9 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 docs-mk/docs/references/dro/dro_read_mri.md rename docs-mk/docs/references/{models.md => dro/index.md} (100%) create mode 100644 docs-mk/docs/references/dro/signal_enhance.md create mode 100644 docs-mk/docs/user-guide/dro.md create mode 100644 docs-mk/docs/user-guide/synthetic.md diff --git a/docs-mk/docs/references/dro/dro_read_mri.md b/docs-mk/docs/references/dro/dro_read_mri.md new file mode 100644 index 0000000..73ec3a3 --- /dev/null +++ b/docs-mk/docs/references/dro/dro_read_mri.md @@ -0,0 +1,3 @@ +# osipi.dro.read_mri_from_dicom + +::: osipi.DRO.read_dicom_slices_as_4d_signal diff --git a/docs-mk/docs/references/models.md b/docs-mk/docs/references/dro/index.md similarity index 100% rename from docs-mk/docs/references/models.md rename to docs-mk/docs/references/dro/index.md diff --git a/docs-mk/docs/references/dro/signal_enhance.md b/docs-mk/docs/references/dro/signal_enhance.md new file mode 100644 index 0000000..452bad0 --- /dev/null +++ b/docs-mk/docs/references/dro/signal_enhance.md @@ -0,0 +1,3 @@ +# osipi.dro.SignalEnhancementExtract + +::: osipi.DRO.SignalEnhancementExtract diff --git a/docs-mk/docs/user-guide/dro.md b/docs-mk/docs/user-guide/dro.md new file mode 100644 index 0000000..8d3ebf2 --- /dev/null +++ b/docs-mk/docs/user-guide/dro.md @@ -0,0 +1,9 @@ +# What is a DRO(digital reference object)? +Digital reference object are synthetic images that have been created +by computer simulations of a target in its environment, you can look here for a more detailed explanation of what a [DRO](https://qibawiki.rsna.org/images/1/14/QIBA_DRO_2015_v1.42.pdf) is. + +# How to use DCE DROs in OSIPI +- [Read DRO data](synthetic.md#read-dro-data) +- [Enhance the signal](synthetic.md#enhance-the-signal) +- [Get the AIF](synthetic.md#get-the-aif) +- [Get the perfusion and tissue parameters](synthetic.md#get-the-perfusion-and-tissue-parameters) diff --git a/docs-mk/docs/user-guide/index.md b/docs-mk/docs/user-guide/index.md index e21bfa6..a894d01 100644 --- a/docs-mk/docs/user-guide/index.md +++ b/docs-mk/docs/user-guide/index.md @@ -15,3 +15,5 @@ Welcome to the User Guide section. - [Relaxation time to concentration](simulate.md) - [Concentration to tissue parameters](simulate.md) - [All in one go: signal to tissue parameters](simulate.md) +4. [DRO](dro.md) + - [Read data](simulate.md) diff --git a/docs-mk/docs/user-guide/synthetic.md b/docs-mk/docs/user-guide/synthetic.md new file mode 100644 index 0000000..6c0d3eb --- /dev/null +++ b/docs-mk/docs/user-guide/synthetic.md @@ -0,0 +1,62 @@ +## Read MR data from a folder of dicom files +``` python +import osipi + + +dicom_folder = 'path/to/dicom/folder' + +signal, slices, dicom_ref = osipi.read_dicom(dicom_folder) + +#Read a DICOM series from a folder path. +#Returns the signal, slices and dicom reference. +# - The signal is a 4D numpy array with dimensions (x, y, z, t) +# - The slices is a list of slices +# - The dicom reference is a sample dicom file from the folder + +``` + +## Enhance the signal +Enhance the signal by removing baseline signal which is the first time point of the signal. +Before the contrast agent is arrived, the signal is assumed to be constant. This constant signal is removed from the signal to enhance the signal. +``` python +import osipi +from osipi import enhance_signal + +E, S0, S = enhance_signal(signal, data_shape, 5) + +# - E is the enhanced signal after removing the baseline signal +# - S0 is the average baseline signal here in this example of the first 5 time points +# - S is the original raw signal +``` + +## Get the AIF +Get the Arterial Input Function (AIF) from the signal. +Using a mask, the AIF is extracted from the signal. + +``` python +import osipi +from osipi import get_aif_from_ROI +# first you may have to create a mask for the AIF manually or using a saved mask and apply it to the signal +aif_mask, rio_voxels = osipi.rio(signal, slice, saved=True) +# this returns the mask and the number of voxels in the mask +aif = get_aif_from_ROI(signal, aif_mask) +# this returns the AIF from the signal by averaging the signal over the voxels in the mask +``` + +## Get the perfusion and tissue parameters + +Get the perfusion and tissue parameters from the signal and the AIF. +You may here choose different models to fit the signal to get the parameters. + +``` python +import osipi +from osipi import extended_tofts_model + +# Fit the signal to the extended Tofts model +ktrans, ve, vp = extended_tofts_model(signal, aif, data_shape) + +# - ktrans is the volume transfer constant +# - ve is the extravascular extracellular volume fraction +# - vp is the plasma volume fraction +# visit CAPLEX for more information on the model and the parameters +``` diff --git a/docs-mk/mkdocs.yml b/docs-mk/mkdocs.yml index 8c1e0b6..8c62dc6 100644 --- a/docs-mk/mkdocs.yml +++ b/docs-mk/mkdocs.yml @@ -27,7 +27,7 @@ theme: language: en palette: - scheme: default - primary: teal + primary: deep-purple accent: purple toggle: icon: material/toggle-switch @@ -36,7 +36,7 @@ theme: toggle: icon: material/toggle-switch name: Switch to dark mode - primary: teal + primary: purple accent: lime plugins: @@ -57,12 +57,20 @@ nav: - Simulating data: - Overview: user-guide/simulate.md - Simulate: user-guide/gen_aif.md + - DRO: + - Overview: user-guide/dro.md + - DRO: user-guide/synthetic.md - About: about/index.md - Developer Guide: contribution/index.md - References: - references/index.md - - Index: + - Dro: + - references/dro/index.md + - DRO functions: + - read_mri_from_dicoms: references/dro/dro_read_mri.md + - signal_enhancement: references/dro/signal_enhance.md + - Models: - references/models/index.md - AIF models: - references/models/aif_models/index.md diff --git a/src/osipi/DRO/DICOM_processing.py b/src/osipi/DRO/DICOM_processing.py index e2781d9..bf06700 100644 --- a/src/osipi/DRO/DICOM_processing.py +++ b/src/osipi/DRO/DICOM_processing.py @@ -6,11 +6,20 @@ from osipi.DRO.filters_and_noise import median_filter -def read_dicom_slices_as_4d_signal(folder_path): - """ - Read a DICOM series from a folder path. - Returns the signal data as a 4D numpy array (x, y, z, t). +def read_dicom_slices_as_4d_signal(folder_path: str) -> (np.ndarray, dict, np.ndarray): + """Read DICOM files from a folder and return the 4D signal data. + + Args: + folder_path (str): Path to the folder containing DICOM files. + + Returns: + signal: The 4D signal data (x, y, z, t). + slices: Dictionary containing the slices. + example_data: The first slice. + + """ + slices = {} for root, _, files in os.walk(folder_path): for file in files: @@ -40,8 +49,22 @@ def read_dicom_slices_as_4d_signal(folder_path): return signal, slices, slices[slice_location][0][1] -def SignalEnhancementExtract(S, datashape, baselinepoints): - # Take baseline average +def SignalEnhancementExtract( + S: np.ndarray, datashape: list, baselinepoints: int = 5 +) -> (np.ndarray, np.ndarray, np.ndarray): + """Signal enhancement extraction from 4D signal data. + + Args: + S: is the 4D signal data (x, y, z, t). + datashape: is the shape of the data (x, y, z, t). + baselinepoints: is the number of time points before contrast injection. + + Returns: + E: The signal enhancement. + S0: The baseline signal (S0). + S: The 4D signal data (x, y, z, t). + + """ S0 = np.average(S[:, :, :, 0:baselinepoints], axis=3) # Take baseline signal E = np.zeros_like(S) diff --git a/src/osipi/DRO/__init__.py b/src/osipi/DRO/__init__.py index e69de29..3233cbf 100644 --- a/src/osipi/DRO/__init__.py +++ b/src/osipi/DRO/__init__.py @@ -0,0 +1,2 @@ + +from .DICOM_processing import read_dicom_slices_as_4d_signal, SignalEnhancementExtract From be73baf9df336e9f45ef67c87a83da31bfbb8807 Mon Sep 17 00:00:00 2001 From: Mohamed Nasser Date: Sun, 4 Aug 2024 12:28:26 +0300 Subject: [PATCH 2/2] Added fitting models to docs --- .../dro/fit_models/extended_tofts.md | 3 + .../dro/fit_models/forward_modified_tofts.md | 4 + .../dro/fit_models/modified_tofts.md | 3 + docs-mk/mkdocs.yml | 4 + src/osipi/DRO/Model.py | 103 ++++++++++++++++-- src/osipi/DRO/__init__.py | 4 +- 6 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 docs-mk/docs/references/dro/fit_models/extended_tofts.md create mode 100644 docs-mk/docs/references/dro/fit_models/forward_modified_tofts.md create mode 100644 docs-mk/docs/references/dro/fit_models/modified_tofts.md diff --git a/docs-mk/docs/references/dro/fit_models/extended_tofts.md b/docs-mk/docs/references/dro/fit_models/extended_tofts.md new file mode 100644 index 0000000..16806f9 --- /dev/null +++ b/docs-mk/docs/references/dro/fit_models/extended_tofts.md @@ -0,0 +1,3 @@ +# Extended Tofts Model + +:::osipi.DRO.extended_tofts_model diff --git a/docs-mk/docs/references/dro/fit_models/forward_modified_tofts.md b/docs-mk/docs/references/dro/fit_models/forward_modified_tofts.md new file mode 100644 index 0000000..d051b72 --- /dev/null +++ b/docs-mk/docs/references/dro/fit_models/forward_modified_tofts.md @@ -0,0 +1,4 @@ +# Forward Modified Tofts Model + +:::osipi.DRO.ForwardsModTofts + name: ForwardsModTofts diff --git a/docs-mk/docs/references/dro/fit_models/modified_tofts.md b/docs-mk/docs/references/dro/fit_models/modified_tofts.md new file mode 100644 index 0000000..d1991ff --- /dev/null +++ b/docs-mk/docs/references/dro/fit_models/modified_tofts.md @@ -0,0 +1,3 @@ +# Modified Tofts Model + +:::osipi.DRO.modifiedToftsMurase diff --git a/docs-mk/mkdocs.yml b/docs-mk/mkdocs.yml index 8c62dc6..917753f 100644 --- a/docs-mk/mkdocs.yml +++ b/docs-mk/mkdocs.yml @@ -70,6 +70,10 @@ nav: - DRO functions: - read_mri_from_dicoms: references/dro/dro_read_mri.md - signal_enhancement: references/dro/signal_enhance.md + - Fitting: + - Modified Tofts: references/dro/fit_models/modified_tofts.md + - Extended Tofts: references/dro/fit_models/extended_tofts.md + - Forward Tofts: references/dro/fit_models/forward_modified_tofts.md - Models: - references/models/index.md - AIF models: diff --git a/src/osipi/DRO/Model.py b/src/osipi/DRO/Model.py index e0d77d9..6ee96d7 100644 --- a/src/osipi/DRO/Model.py +++ b/src/osipi/DRO/Model.py @@ -7,7 +7,40 @@ import osipi -def modifiedToftsMurase(Cp, Ctiss, dt, datashape): +def modifiedToftsMurase( + Cp: np.ndarray, Ctiss: np.ndarray, dt: int, datashape: tuple +) -> (np.ndarray, np.ndarray, np.ndarray): + """Fit the Modified Tofts model to the data + + This function uses a linear solver (np.linalg.lstsq) + to fit the parameters of the Modified Tofts model. + The linear solver is used to solve the equation C = AB for B, + where C is the tissue concentration, + A is a matrix that depends on the arterial input function and the tissue concentration, + and B is the array of parameters. + + + Args: + Cp: is the arterial input function [OSIPI CAPLEX Q.IC1.001] + Ctiss: is the tissue concentration [OSIPI CAPLEX Q.IC1.001] as 4d array (x, y, z, t) + dt: is the time interval between samples + datashape: is the shape of the data array (x, y, z, t) + + Returns: + K1: is the rate constant of the forward model (Ktrans) [OSIPI CAPLEX Q.PH1.008] + k2: is the rate constant of the reverse model [OSIPI CAPLEX Q.PH1.009] + Vp: is the plasma volume fraction [OSIPI CAPLEX Q.PH1.001] + + See Also: + `Exteded Tofts Model` + `Tofts Model` + + References: + - Lexicon url: + https://osipi.github.io/OSIPI_CAPLEX/perfusionModels/#indicator-concentration-models + - Lexicon code: M.PH1.001 + - OSIPI name: Modified Tofts Model + """ # Fit Modified Tofts (Linear from Murase, 2004) # Cp = Ea/0.45, Ctis=E/0.45 # Matrix equation C=AB (same notation as Murase) @@ -120,7 +153,43 @@ def process_voxel(j, i, k, c_tiss, ca, t, type="ET"): return j, i, k, popt -def extended_tofts_model(ca, c_tiss, t): +def extended_tofts_model( + ca: np.ndarray, c_tiss: np.ndarray, t: np.ndarray +) -> (np.ndarray, np.ndarray, np.ndarray): + """ + Fit the Extended Tofts model to the data + + The function uses a curve fit from SciPy to fit the parameters of the Extended Tofts model. + + The function maintain a parallel processing using the multiprocessing library; + the number of processes is equal to the number of CPUs in the machine. + + The function returns the fitted parameters for each voxel in the 3D data array. + + + + + Args: + ca: is the arterial input function [OSIPI CAPLEX Q.IC1.001] + c_tiss: is the tissue concentration [OSIPI CAPLEX Q.IC1.001] as 4d array (x, y, z, t) + t: is the time as 1D array + + Returns: + K1: is the rate constant of the forward model (Ktrans) [OSIPI CAPLEX Q.PH1.008] + ve: is the extravascular extracellular volume fraction [OSIPI CAPLEX Q.PH1.001] + vp: is the plasma volume fraction [OSIPI CAPLEX Q.PH1.001] + + See Also: + `Modified Tofts Model` + `Tofts Model` + + References: + - Lexicon url: + https://osipi.github.io/OSIPI_CAPLEX/perfusionModels/#ETM + - Lexicon code: M.PH1.002 + - OSIPI name: Extended Tofts + + """ ktrans = np.zeros(c_tiss.shape[:-1]) ve = np.zeros(c_tiss.shape[:-1]) vp = np.zeros(c_tiss.shape[:-1]) @@ -175,10 +244,28 @@ def tofts_model(ca, c_tiss, t): return ktrans, ve -def ForwardsModTofts(K1, k2, Vp, Cp, dt): - # To be carried out as matmul C=BA - # Where C is the output Ctiss and B the parameters - # With A a matrix of cumulative integrals +def ForwardsModTofts(K1: np.ndarray, k2: np.ndarray, Vp: np.ndarray, Cp: np.ndarray, dt: int): + """Calculate the tissue concentration using the Tofts model + + Args: + K1: is the rate constant of the forward model (Ktrans) [OSIPI CAPLEX Q.PH1.008] + k2: is the rate constant of the reverse model [OSIPI CAPLEX Q.PH1.009] + Vp: is the plasma volume fraction [OSIPI CAPLEX Q.PH1.001] + Cp: is the arterial input function [OSIPI CAPLEX Q.IC1.001] + dt: is the time interval between samples + + Returns: + Ctiss: is the tissue concentration [OSIPI CAPLEX Q.IC1.001] as 4d array (x, y, z, t) + + See Also: + `Modified Tofts Model` + `Extended Tofts Model` + + References: + - Lexicon url: + https://osipi.github.io/OSIPI_CAPLEX/perfusionModels/#TM + - OSIPI name: Tofts Model + """ x, y, z = K1.shape t = Cp.shape[0] @@ -203,10 +290,6 @@ def ForwardsModTofts(K1, k2, Vp, Cp, dt): def ForwardsModTofts_1vox(K1, k2, Vp, Cp, dt): - # To be carried out as matmul C=BA - # Where C is the output Ctiss and B the parameters - # With A a matrix of cumulative integrals - t = Cp.shape[0] Ctiss = np.zeros(t) diff --git a/src/osipi/DRO/__init__.py b/src/osipi/DRO/__init__.py index 3233cbf..e5d29ff 100644 --- a/src/osipi/DRO/__init__.py +++ b/src/osipi/DRO/__init__.py @@ -1,2 +1,4 @@ - from .DICOM_processing import read_dicom_slices_as_4d_signal, SignalEnhancementExtract +from .Model import (modifiedToftsMurase, extended_tofts_model, + tofts_model, forward_extended_tofts, + ForwardsModTofts)