(Optional) In-vitro phantom preparation ● Timing 3h
As discussed earlier, in-vitro phantoms can be assembled as a preliminary means to assess the parameter quantification performance. A variety of CEST-phantoms can be used for this purpose, depending on the target application (e.g., bovine serum albumin (BSA) for brain imaging, creatine and phosphocreatine for muscle metabolism imaging, etc). While instructions for CEST-MRI phantom preparation can be found elsewhere (e.g., at57,58,96), we provide here a simple-to-make L-arginine phantom example, which will enable reproducing the results shown in Fig. 6.
!CAUTION Proper safety techniques should be followed when handling chemicals. All phantom preparations should be carried out with caution in a fume hood. Use protective eye wear, gloves and a lab coat.
1. Based on the molecular weight of the compound of interest (174.20 gr/mol for L-arginine), and the volume of the intermediate preparation container (e.g., a 50 mL Falcon tube), use the spatula, weighing paper, and balance to weigh the required powder needed for the largest sample concentration (e.g., 100 mM).
2. Add PBS 1x and thoroughly stir the closed container using the vortex mixer. Visually verify the solubility of the sample.
3. Titrate the sample using the NaOH/HCl and the pH meter into the desired pH levels (for the L-arginine example shown in Fig. 6, use pH 4, 4.5, 5, 5.5, or 6), while stirring between measurements.
4. Pour the resulting solution into a 2 mL glass vias (for preclinical phantoms) or 15 mL test tube (for clinical-scanner-oriented phantoms).
5. Use the remaining solution in the 50 mL falcon tube to synthesize additional samples by serial dilution. Titrate to reach the desired pH.
6. After completing three 2mL glass vial samples (preclinical) or six 15 mL test tube samples (clinical), position the samples within a new 50 mL Falcon tube (preclinical) or a dedicated test tube holder (clinical). See setup examples in Fig. 6 (right column).
7. Fill the large phantom container completely using PBS 1x or saline. Minimize the existence of any air bubbles.
Pulse sequence setup
8. CEST MRI simulator installation: ● Timing 5-10 min
Download the signal simulator from the repository https://github.com/momentum-laboratory/molecular-mrf and extract it. If you have git installed, you can use the following command instead:
git clone [email protected]:momentum-laboratory/molecular-mrf.git
The prerequisites are C/C++11 compiler, SWIG and Python version v3.9 or newer. While C/C++11 compiler is installed by default in most Linux distributions, it requires installation on Windows and Mac (e.g., using https://www.cygwin.com/ or https://visualstudio.microsoft.com/ ). On Linux and Mac OS machine SWIG can be installed using a default package manager, for example:
sudo apt-get install swig
On a Windows machine it can be downloaded from https://www.swig.org/download.html. For the next steps of the installation, it is important to create a clean environment for python v3.9, using conda or venv:
conda create –n name_of_env python=3.9
conda activate name_of_env
Change the current working directory to the repository folder and install the simulator:
cd open-py-cest-mrf
pip install –e .
The flag –e allows installation in editable mode and the (.) is necessary. ▲CRITICAL The installation folder path is fixed. If the package is moved re-installation will be required. To verify the success of the installation, run:
pip list
You should see a variety of packages, including the following critical two:
BMCSimulator 0.2
cest-mrf 0.2 .../open-py-cest-mrf
◆ TROUBLESHOOTING.
If the installation failed, you can employ docker and run the following command:
docker pull vnikale/pycest-starter
9. Loading and installing CEST-MRF pulse sequences into the MRI scanner ● Timing 5 min
For preclinical (Bruker MRI) scanners, the complete pulse sequence should be downloaded from https://osf.io/52bsg and installed according to the detailed manual provided within the link. For clinical scanners utilizing the pulseq framework, the .seq files should be loaded into the scanner computer, as detailed in the pulseq installation manual (http://pulseq.github.io/, typically into the folder c:\MedCom\Mricustomer\seq\pulseq\). The compound specific pulse sequences (Table 1) are provided in the folder ‘supplementary/published_pulse_sequences/’ in https://github.com/momentum-laboratory/molecular-mrf and in the pulseq CEST open library https://github.com/kherz/pulseq-cest-library/tree/master/seq-library.
10. Creating new CEST-MRF pulse sequences (example given for L-arginine) ● Timing 30 sec
The previous step enables the use of previously published/generated MRF pulse sequences. For research purpose, one may wish to adjust and optimize CEST-MRF for a new compound target, or a different imaging scenario. As an example, we will define an MRF sequence for L-arginine quantification30,73 using a continuous wave saturation pulse at 9.4T. The Code snippets provided below can also be found in the “dot_product_example” folder (within the main https://github.com/momentum-laboratory/molecular-mrf repository).
First, the pypulseq package needs to be imported:
import pypulseq
Next, the acquisition parameters are defined. In this example, the sequence consists of a saturation pulse fixed at the L-arg chemical shift (3.0 ppm), with 30 images acquired with varying saturation pulse powers (B1):
seq_defs = {}
seq_defs['n_pulses'] = 1 # number of pulses
seq_defs['tp'] = 3 # pulse duration [s]
seq_defs['td'] = 0 # inter-pulse delay [s]
seq_defs['Trec'] = 1 # delay before readout [s]
seq_defs['DCsat'] = seq_defs['tp'] / (seq_defs['tp'] + seq_defs['td']) # duty cycle
seq_defs['offsets_ppm'] = [3.0] * 30 # offset vector [ppm]
seq_defs['num_meas'] = len(seq_defs['offsets_ppm']) # number of images
seq_defs['Tsat'] = seq_defs['n_pulses'] * (seq_defs['tp'] + seq_defs['td']) - seq_defs['td']
seq_defs['B0'] = 9.4 # B0 [T]
seq_defs['seq_id_string'] = 'larg_seq’ # unique seq id
seq_defs['B1pa'] = [5, 5, 3, 3.75, 2.5, 1.75, 5.5, 6, 3.75,5.75, 0.25, 3, 6, 4.5, 3.75, 3.5, 3.5, 0, 3.75, 6, 3.75, 4.75, 4.5, 4.25, 3.25, 5.25, 5.25, 0.25, 4.5, 5.25]
For the readout, we will use a 60 degrees flip angle followed by signal ADC:
imaging_pulse = pypulseq.make_block_pulse(60 * np.pi / 180, duration=2.1e-3)
imaging_delay = pypulseq.make_delay(20e-3)
pseudo_adc = pypulseq.make_adc(1, duration=1e-3)
seq = pypulseq.Sequence()
11. Now we can build the entire sequence in a loop which calls the various definitions declared earlier:
for idx, B1 in enumerate(seq_defs['B1pa']):
if idx > 0: # add relaxtion block after first measurement
seq.add_block(pypulseq.make_delay(seq_defs['Trec'] - te)) # net recovery time
# saturation pulse
current_offset_hz = seq_defs['offsets_ppm'][idx] * seq_defs['B0'] * gyro_ratio_hz
fa_sat = B1 * gyro_ratio_rad * seq_defs['tp'] # flip angle of sat pulse
# add pulses
for n_p in range(seq_defs['n_pulses']):
# If B1 is 0 simulate delay instead of a saturation pulse
if B1 == 0:
seq.add_block(pypulseq.make_delay(seq_defs['tp'])) # net recovery time
else:
sat_pulse = pypulseq.make_block_pulse(fa_sat, duration=seq_defs['tp'], freq_offset=current_offset_hz)
seq.add_block(sat_pulse)
# delay between pulses
if n_p < seq_defs['n_pulses'] - 1:
seq.add_block(pypulseq.make_delay(seq_defs['td']))
# Imaging pulse
seq.add_block(imaging_pulse)
seq.add_block(imaging_delay)
seq.add_block(pseudo_adc)
The definitions are now stored and added as human-readable text to the final pulse sequence (.seq) file:
def_fields = seq_defs.keys()
for field in def_fields:
seq.set_definition(field, seq_defs[field])
seq.write(seq_fn)
▲CRITICAL Note that the output .seq file serves a dual purpose, providing instructions for both the in-silico simulator and the physical scanner. For hybrid pulseq/vendor protocols, where the readout is separately determined at the scanner, separated files should be created (see an example in the function ‘write_sequence_clinical’, located in the folder ‘dot_product_example’ from the provided GitHub repository).
▲CRITICAL If you prefer using .py scripts instead of Jupyter notebooks make sure to run them from the root folder as modules. Otherwise, key utility functions will not be accessible. For example, to run the dot_prod_example, use:
python -m dot_prod_example.preclinical
Animal / human subject setup ● Timing 10 min / 5 min per animal / human subject, respectively
This part of the protocol has two main variants: (i) Animal (rodent) setup in preclinical scanners or (ii) Human setup in clinical scanners. In both cases, the steps are not unique for CEST-MRF, and follow traditional MRI research practice97,98,99.
Animal positioning
▲CRITICAL All procedures involving animal use must be performed in accordance with institutional and national guidelines and regulations and approved by the relevant animal care and use committees.
12. Place the rodent in an induction chamber containing 4% wt/vol isoflurane with air or oxygen. Upon relaxation, transfer the animal to the dedicated scan cradle, while gradually reducing the isoflurane level to ~1–2% wt/vol , to be applied via a nose cone.
! CAUTION Isoflurane must be scavenged properly (e.g., using a charcoal canister or vacuum pumps) to minimize exposure to humans.
13. Gently fix the organ to be imaged using dedicated bars, tape, or small cushions.
14. Apply lubricant eye ointment to prevent drying of the cornea.
▲CRITICAL An MRI-compatible respiration rate monitoring sensor should be used to ensure mouse well-being.
15. Maintain the animal body temperature at 37°C using a hot air blower, circulating water pump, or similar, while using a rectal temperature sensor for feedback.
16. Position the animal cradle within the RF coil and make sure the target organ is at the center of the magnet.
Human subject setup
! CAUTION All procedures involving human imaging must be performed under approval by the local ethics/IRB committee and in accordance with institutional and national guidelines and regulations. Written informed consent must be given before the study.
17. Gently position the subject on the scanner bed and install the organ-relevant coil.
18. Verify the use of ear-plugs, and the subject familiarity with the hand-pump used to communicate with the staff in case of discomfort or emergency.
19. Position the isocenter at the middle of the organ of interest using the laser marking.
! CAUTION Make sure to notify the subject to close their eyes prior to activating the laser.
20. Slowly insert the subject into the magnet.
Raw data acquisition ● Timing 12 - 30 min per subject/animal (depending on optional steps)
21. Acquire scout images and calibrate the RF coils (typically performed manually in preclinical scanners using the matching and tuning rods).
22. Determine the location of the slice of interest (axial orientation is recommended to minimize B0 inhomogeneity) or slice stack. A minimal voxel size of ~0.3 mm x 0.3 mm x 1 mm is recommended for rodents, and 1.8 mm size isotropic for humans.
23. Shim the magnetic field manually or using vendor-provided automatic shimming.
24. Scan the subject/animal using the CEST-MRF pulse sequence of interest (see Table 1 for a detailed list of available pulse sequences).
25. (Optional) Acquire T1, T2, B0, and/or B1 maps (using vendor-provided protocols) while using the same image geometry used in the CEST-MRF pulse sequence. These maps are required for implementing the sequential learning approach (Fig. 4 and Fig. 7), or as a means for later exploring the origins of noisy MRF reconstruction (such as those stemming from inhomogeneous B0 or B1 fields). For B0/B1 mapping we recommend the use of the water saturation shift referencing (WASSR100) sequence or the simultaneous mapping of water shift and B1 (WASABI101) sequence, as provided in the pulseq-CEST open library (https://github.com/kherz/pulseq-cest-library/tree/master/seq-library).
26. (Optional) Acquire additional contrast-weighted images for comparison. These may include T2-weighted images, diffusion, and perfusion images, depending on the pathology of interest and using vendor-provided pulse sequences. Traditional CEST Z-spectrum can be acquired by using the pulse sequence files: APT_3T_00x.seq, available at https://github.com/kherz/pulseq-cest-library/tree/master/seq-library, where x is replaced by 0-4 in order of the sequence effectiveness, as described in the CEST-weighted brain imaging consensus paper102. A preclinical CEST-weighted pulse sequence for Bruker scanners is given in the ‘supplementary/published_pulse_sequences/cest_weighted’ folder at https://github.com/momentum-laboratory/molecular-mrf/.
Image analysis, processing, and AI-based reconstruction
27. CEST MRF dictionary generation ● Timing 10 sec – 22 hrs (depends on the number of CPU available and the scenario of interest)
To generate synthetic signal trajectories (exemplified here for L-arginine quantification at 9.4T) a .yaml file which stores the required magnetic tissue properties is required. These definitions are determined by the user in the ‘configs.py’ file:
class ConfigPreclinical(Config):
def __init__(self):
config = {}
config['yaml_fn'] = 'scenario.yaml'
config['seq_fn'] = 'acq_protocol.seq'
config['dict_fn'] = 'dict.mat'
# Water_pool
config['water_pool'] = {}
config['water_pool']['t1'] = np.arange(2500, 3350, 50) / 1000
config['water_pool']['t1'] = config['water_pool']['t1'].tolist() # vary t1
config['water_pool']['t2'] = np.arange(600, 1250, 50) / 1000
config['water_pool']['t2'] = config['water_pool']['t2'].tolist() # vary t2
config['water_pool']['f'] = 1
# Solute pool
config['cest_pool'] = {}
config['cest_pool']['Amine'] = {}
config['cest_pool']['Amine']['t1'] = [2800 / 1000]
config['cest_pool']['Amine']['t2'] = [40 / 1000]
config['cest_pool']['Amine']['k'] = np.arange(100, 1410, 10).tolist()
config['cest_pool']['Amine']['dw'] = 3
config['cest_pool']['Amine']['f'] = np.arange(10, 125, 5) * 3 / 110000
config['cest_pool']['Amine']['f'] = config['cest_pool']['Amine']['f'].tolist()
...
config['b0'] = 9.4
config['num_workers'] = 16
Note that python list definitions (.tolist()) are needed for flawless .yaml saving. The variable num_workes determines how many threads will be used (depending on the number of available CPUs). Finally, the .yaml file is generated and saved using the function:
write_yaml_dict(cfg, yaml_fn)
Based on the .yaml tissue parameter combinations and the desired acquisition protocol defined in the .seq file, the dictionary simulation can take place using the following function:
dictionary = generate_mrf_cest_dictionary(seq_fn=seq_fn,param_fn=yaml_fn,dict_fn=dict_fn,num_workers=cfg.num_workers,axes='xy')
The input arguments *_fn expect strings with the names of the .yaml, .seq, and .mat files, respectively. The output dictionary that stores all synthetic signal trajectories and their paired parameters will be saved as a .mat file. The parameter ‘axes’ selects which magnetization vector component is stored. While the default is ‘xy’, the ‘z’ option is useful for Z-spectrum simulation where no readout is simulated. For credible simulation, the practical limitations of the scanner should be explicitly declared, for example:
...
lims = pp.Opts(
max_grad=40,
grad_unit="mT/m",
max_slew=130,
slew_unit="T/m/s",
rf_ringdown_time=30e-6,
rf_dead_time=100e-6,
rf_raster_time=1e-6,
gamma=cfg.gamma/2/np.pi*1e6,
)
...
gx_spoil, gy_spoil, gz_spoil = [
pp.make_trapezoid(channel=c, system=lims, amplitude=spoil_amp, duration=spoil_dur, rise_time=rise_time)
...
28. Dot-product matching of L-arginine phantom data ● Timing 10 sec (L-arginine) – 2.3 hrs (in-vivo)29
As we now possess both acquired data and a simulated dictionary, parameter quantification can take place. The function for dot product matching is available at the repository folder ‘open-py-cest-mrf/cest_mrf/metrics/dot_product.py’:
def dot_prod_matching(dictionary = None, acquired_data = None, dict_fn = None, acquired_data_fn = None, batch_size = 256):
...
The function can either receive the dictionary and the acquired data as variables or strings containing the respectively saved file names (‘dict_fn’, ‘acquired_data_fn’). Dot product matching is performed in batches due to RAM considerations and can be adjusted according to the available hardware using the argument ‘batch_size’. ▲CRITICAL The number of image pixels must be divisible by ‘batch_size’.
The next steps include image reshaping, L2 normalization, and dot-product calculation in batches:
data = acquired_data.reshape((n_iter, r_raw_data * c_raw_data), order='F')
norm_dict = synt_sig / (la.norm(synt_sig, axis=0) + 1e-10)
norm_data = data / (la.norm(data, axis=0) + 1e-10)
batch_indices = range(0, data.shape[1], batch_size)
for ind in range(np.size(batch_indices)):
current_score = norm_data[:, batch_indices[ind]: batch_indices[ind] + batch_size].T @ norm_dict
dp[0, batch_indices[ind]: batch_indices[ind] + batch_size] = np.max(current_score, axis=1)
dp_ind = np.argmax(current_score, axis=1)
t1w[0, batch_indices[ind]: batch_indices[ind] + batch_size] = dict_t1w[0, dp_ind]
…
For each batch, the maximal dot-product is found, and its corresponding index is stored. An example dot-product matching for the provided L-arginine phantom data is performed using the following command:
quant_maps =
dot_prod_matching(dict_fn='dict.mat', acquired_data_fn='acquired_data.mat')
The resulting quantitative exchange parameter maps are shown in Fig. 6.
29. Supervised deep reconstruction network training (exemplified for L-arg data) ● Timing 5 min (L-arg) – 6 hrs (in-vivo)29
The full code for this step can be found in the jupyter notebook file deep_reco_example/deep_reco_preclinical.ipynb. First PyTorch is installed:
!pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu118
◆ TROUBLESHOOTING
▲CRITICAL: If the user prefers using the .py script instead of the Jupyter notebook, PyTorch and OpenCV. Need to be installed using:
pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu118
pip3 install opencv-python
After importing all required libraries, a fully connected NN is defined:
class Network(nn.Module):
def __init__(self):
super(Network, self, sig_n).__init__()
self.l1 = nn.Linear(sig_n, 300)
self.relu1 = nn.ReLU()
self.l2 = nn.Linear(300, 300)
self.relu2 = nn.ReLU()
self.l3 = nn.Linear(300, 2)
def forward(self, x):
x = self.l1(x)
x = self.relu1(x)
x = self.l2(x)
x = self.relu2(x)
x = self.l3(x)
return x
The network receives an input signal of length sig_n and returns a vector of two parameters: concentration and proton exchange rate. The Pytorch Dataset class is used to import signals throughout the training from the dictionary .mat file created earlier.
▲CRITICAL For simplicity, validation datasets are not described here, yet they are required for robust learning. A simple first line of validation can be performed using separately simulated signal trajectories with parameter combinations that were not present in the training dictionary.
In the next step we define the training hyper-parameters:
learning_rate = 0.0001
batch_size = 256
num_epochs = 100
noise_std = 0.002 # noise level for training
As NN converge faster and more efficiently with normalized data, we will next find the minimum and maximum values of the output exchange parameters (as set in the dictionary):
temp_data = sio.loadmat('dict.mat')
min_fs = np.min(temp_data['fs_0'])
min_ksw = np.min(temp_data['ksw_0'].transpose().astype(np.float))
max_fs = np.max(temp_data['fs_0'])
max_ksw = np.max(temp_data['ksw_0'].transpose().astype(np.float))
min_param_tensor = torch.tensor(np.hstack((min_fs, min_ksw)), requires_grad=False)
max_param_tensor = torch.tensor(np.hstack((max_fs, max_ksw)), requires_grad=False)
Now let’s initialize the network, optimizer and data loader:
reco_net = Network(sig_n).to(device)
optimizer = torch.optim.Adam(reco_net.parameters(), lr=learning_rate)
training_data = sio.loadmat('dict.mat')
dataset = DatasetMRF(training_data)
train_loader = DataLoader(dataset=dataset,
batch_size=batch_size,
shuffle=True,
num_workers=8)
After the target normalization the training loop takes place using the mean-squared-error loss:
...
target = torch.stack((cur_fs, cur_ksw), dim=1)
target = normalize_range(original_array=target, original_min=min_param_tensor,
original_max=max_param_tensor, new_min=-1, new_max=1).to(device).float()
...
loss = torch.mean((prediction - target) ** 2)
30. Supervised reconstruction network inference (exemplified using L-arg data) ● Timing 10 sec
The training will stop after reaching the maximum number of epochs set and the model will be stored as a checkpoint. A previously stored checkpoint can be loaded using the following lines:
loaded_checkpoint = torch.load('checkpoint')
reco_net = Network(sig_n).to(device)
reco_net.load_state_dict(loaded_checkpoint['model_state_dict'])
Next, the experimentally acquired data is loaded, and inference takes place:
acquired_data = sio.loadmat('acquired_data.mat')['acquired_data'].astype(np.float)
_, c_acq_data, w_acq_data = np.shape(acquired_data)
# Reshaping the acquired data to the shape expected by the NN (e.g. 30 x ... )
acquired_data = np.reshape(acquired_data, (sig_n, c_acq_data * w_acq_data), order='F')
acquired_data = acquired_data / np.sqrt(np.sum(acquired_data ** 2, axis=0))
# Transposing for compatibility with the NN - now each row is a trajectory
acquired_data = acquired_data.T
acquired_data = torch.from_numpy(acquired_data).to(device).float()
acquired_data.requires_grad = False
# Switching to evaluation mode
reco_net.eval()
prediction = reco_net(acquired_data)
prediction = un_normalize_range(prediction, original_min=min_param_tensor.to(device),
original_max=max_param_tensor.to(device), new_min=-1, new_max=1)
Note that this step has also to un-normalize the output parameters back into physically meaningful units.
31. Sequential reconstruction network training and inference (exemplified using Iohexol phantoms and tumor bearing mice) ● Timing 10-15 min
The sequential reconstruction pipeline is very similar to the previously described reconstruction network. The primary difference lies in the network ability to receive as input the raw MRF signals (either simulated or experimental) alongside previously determined parameters, such as water T1 and T2 maps:
...
target = torch.stack((cur_fs, cur_ksw), dim=1)
target = normalize_range(original_array=target, original_min=min_param_tensor,
original_max=max_param_tensor, new_min=0, new_max=1).to(device)
input_water_t1t2 = torch.stack((cur_t1w, cur_t2w), dim=1)
input_water_t1t2 = normalize_range(original_array=input_water_t1t2, original_min=min_water_t1t2_tensor, original_max=max_water_t1t2_tensor, new_min=0, new_max=1)
...
noised_sig = torch.hstack((input_water_t1t2, noised_sig))
prediction = reco_net(noised_sig.to(device).float())
The folder "sequential_example" includes two step-by-step examples of training sequential networks, with the expected output maps shown in Fig. 7. Note that the supplementary input of T1 and T2 maps can be augmented by B0 and B1 field maps, or any other separately determined properties. The process can continue recursively using the exact same approach; e.g., by leveraging the semisolid MT exchange parameters output from a first network as input for the next29.
◆ TROUBLESHOOTING
32. Multi-pool deep reconstruction of human data● Timing 10 sec
The pulse sequences used in previously published human molecular MRF studies are provided here as open .seq files (Table 1). A fully trained neural network for reconstructing raw CEST-MRF data, acquired on a 3T scanner with an EPI readout, into six quantitative parameter maps is available under the human_example folder at https://github.com/momentum-laboratory/deep-molecular-mrf alongside a raw MRF human data and a full inference code. The training pipeline for this clinical network is very similar to the supervised deep reconstruction examples given above. For specific training parameters see41,67. The expected output for the quantitative parameter map reconstruction is shown in Fig. 8.
33. (Optional alternative) Unsupervised training and reconstruction of semisolid MT and water parameter maps ● Timing 10 sec for inference, ~22 hours for training40
In case the user has access to several healthy human volunteers or patients and would rather train the reconstruction network on real acquired data instead of simulated dictionaries, the unsupervised reconstruction route may be chosen, replacing the need to generate dictionaries. Note that the acquisition is still based on MRF principles (Fig. 5). The pulse sequence used in a previously published unsupervised human semisolid MT MRF study40 is provided here (Table 1). A fully trained neural network for reconstructing semisolid MT MRF data, acquired on a 3T scanner into four quantitative parameter maps is available under the unsupervised_example folder at https://github.com/momentum-laboratory/deep-molecular-mrf, alongside a raw MRF human data and a full inference code. The function can be activated using the following command:
python -m unsupervised_example.cnn_inference
where ‘data’ and ‘result’ are strings containing the paths to the acquired data and the folder where the results will be saved, respectively. ‘model’ is a string containing the path for the trained model. For specific training parameters see40. The expected output for the unsupervised parameter map reconstruction is shown in Fig. 9.
Data availability
All the data used in this work is available at https://github.com/momentum-laboratory/deep-molecular-mrf. It includes raw MRF data, quantitative parameter maps (Figs. 6-9), a CAD file for 3D printing a six vial (phantom) holder, and pulse sequence files (Table 1). A complete preclinical CEST-MRF pulse sequence for Bruker scanners is available at https://osf.io/52bsg. The .seq format files used in this work were also deposited at the pulseq CEST open library: https://github.com/kherz/pulseq-cest-library/tree/master/seq-library.
Code availability
All code is available on https://github.com/momentum-laboratory/deep-molecular-mrfin the format of Python scripts and Jupyter notebooks.
Author contributions statement: Conceptualization: N.V., C.T.F, and O. P. Methodology: N.V., O.C., H.Y.H, M.Z., C.T.F, and O.P. Data curation: O.C., H.Y.H., M.Z., C.T.F, and O.P. Writing: N.V. H.Y.H, C.T.F, and O.P. Reviewing and editing: N.V., O.C., H.Y.H., M.Z., C.T.F., and O.P. Supervision: C.T.F. and O.P.
Competing interests: The authors declare the following competing interests: C.T.F. and O.C. hold a patent for the CEST MR fingerprinting method (patent no. US10,605,877).