Slice Timing Corrections (Adding)

Hi, Im trying to add Slice Timing information in my .json file.

Here is the original .json file

{
    "Manufacturer": "Philips",
    "PatientPosition": "XXX",
    "SeriesDescription": "ImageMRSERIES",
    "ProtocolName": "AxialfMRI_rest",
    "SeriesNumber": 3,
    "AcquisitionNumber": 3,
    "ImageComments": "BRAINMRI(NON_CONTRAST)+DIFFUSION",
    "PhilipsRescaleSlope": 0.79805,
    "PhilipsRescaleIntercept": 0,
    "PhilipsScaleSlope": 0.0359877,
    "UsePhilipsFloatNotDisplayScaling": 1,
    "EchoTime": 0.035,
    "RepetitionTime": 3,
    "ImageOrientationPatientDICOM": [
        0.99852,
        0.0442888,
        0.0315681,
        -0.0329498,
        0.954388,
        -0.296744
    ],
    "ConversionSoftware": "dcm2niix",
    "ConversionSoftwareVersion": "v1.0.20220505",
    "TaskName": "BRAINMRINONCONTRASTDIFFUSION"
}

Using nibabel module and python, I find out that functional volume image (I,e .nii.gz) has 100 time dimensions and TR = 3s.

print(img.shape)
print(img.header.get_zooms())

results are as below

(128,128,35,100)
(1.719,1.719,4.000146, 3.0)

I guessed that I should add Slice Timing Correction like this: “Slice Timing” : [3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 87, 93, 99, 105, 111, 117, 123, 129, 135, 141, 147, 153, 159, 165, 171, 177, 183, 189, 195, 201, 207, 213, 219, 225, 231, 237, 243, 249, 255, 261, 267, 273, 279, 285, 291, 297, 0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198, 204, 210, 216, 222, 228, 234, 240, 246, 252, 258, 264, 270, 276, 282, 288, 294]

The reason for this is that it scanned for 100 times and TR was 3sec. (“Philips” Scanner use Interleaved way in default)

Please tell me if im wrong.

Best,
Junyong Oh

Hi @dhwnsdyd21,

This is not the right reasoning. Slice timing happens within each TR. Since your data were acquired axial, each slice should represent one cross-section in the Z-direction. Your data has 35 slices in the Z direction, thus there should be 35 items in your slice timing.

The slice time values should range from 0 to (TR-slice time). I cannot say what order the slices are since I am not familiar with this acquisition.

Best,
Steven

The Philips interleaved slice order is not the same as Siemens interleaved.
The time spacing between 2 neighboring slices is always large.
The step size is round(sqrt(# of slices)). In your example with 35 slices = 6.
The acq scheme is:
Slice 1,7,13,19,25,31,2,8,…

Thank you for your reply.

So I should add “Slice Timining” : [1,7,13,19,25,31,2,8,14,20,26,32,3,9,15,21,27,33,4,10,16,22,28,34,5,11,17,23,29,35] in my .json file?

Please explain how should I set my Slice Timing information and why

Thanks,
Junyong Oh

As @Steven wrote, the values should range from 0 to (TR - slice time).
Slice time is TR / 35 in seconds.
Now
(np.array([1,7,13,19,25,31,2,8,14,20,26,32,3,9,15,21,27,33,4,10,16,22,28,34,5,11,17,23,29,35])- 1) * slice time is what you are looking for

You need the timing information for the slice scan time correction.

Jörg

What is timing information?

# === GENERAL INFORMATION ========================================================
#
.    Patient name                       :   Confidential
.    Examination name                   :   BRAIN MRI (NON_CONTRAST) + DIFFUSION
.    Protocol name                      :   Axial fMRI_rest
.    Examination date/time              :   Confidential
.    Series Type                        :   Image   MRSERIES
.    Acquisition nr                     :   3
.    Reconstruction nr                  :   1
.    Scan Duration [sec]                :   309
.    Max. number of cardiac phases      :   1
.    Max. number of echoes              :   1
.    Max. number of slices/locations    :   35
.    Max. number of dynamics            :   100
.    Max. number of mixes               :   1
.    Patient position                   :   Head First Supine
.    Preparation direction              :   Anterior-Posterior
.    Technique                          :   FEEPI
.    Scan resolution  (x, y)            :   80  81
.    Scan mode                          :   MS
.    Repetition time [ms]               :   3000.000  
.    FOV (ap,fh,rl) [mm]                :   220.000  140.000  220.000
.    Water Fat shift [pixels]           :   16.037
.    Angulation midslice(ap,fh,rl)[degr]:   -2.481  1.894  -17.189
.    Off Centre midslice(ap,fh,rl) [mm] :   7.913  11.977  -1.066
.    Flow compensation <0=no 1=yes> ?   :   0
.    Presaturation     <0=no 1=yes> ?   :   0
.    Phase encoding velocity [cm/sec]   :   0.000000  0.000000  0.000000
.    MTC               <0=no 1=yes> ?   :   0
.    SPIR              <0=no 1=yes> ?   :   1
.    EPI factor        <0,1=no EPI>     :   81
.    Dynamic scan      <0=no 1=yes> ?   :   1
.    Diffusion         <0=no 1=yes> ?   :   0
.    Diffusion echo time [ms]           :   0.0000
.    Max. number of diffusion values    :   1
.    Max. number of gradient orients    :   1
.    Number of label types   <0=no ASL> :   0

Here is my par/rec file.

I want to add Slice Timing information for fmriprep. How can I extract slice timing information using par/rec file above?

I also want to know the direction of scan, such as top-down, bottom-top.

Best,
@dhwnsdyd21

Hi @dhwnsdyd21,

This was answered in mine and @cni-md ‘s post above. You can see from your PAR/REC that you have 35 slices (Max. number of slices/locations : 35) which must happen within a TR of Repetition time [ms] : 3000.000 .

That is answered by this part:

Which suggests a PhaseEncodingDirection of j-.

Best,
Steven

Hi @cni-md.

I was able to successfully run fmriprep without error report.

But Slice Timing correction was not performed.

This is my .json file about my data.

"SeriesDescription": "ImageMRSERIES",
    "ProtocolName": "AxialfMRI_rest",
    "SeriesNumber": 3,
    "AcquisitionNumber": 3,
    "ImageComments": "BRAINMRI(NON_CONTRAST)+DIFFUSION",
    "PhilipsRescaleSlope": 0.79805,
    "PhilipsRescaleIntercept": 0,
    "PhilipsScaleSlope": 0.0359877,
    "UsePhilipsFloatNotDisplayScaling": 1,
    "EchoTime": 0.035,
    "RepetitionTime": 3,
    "Slice Timing" : [0.0, 0.5142857142857142, 1.0285714285714285, 1.542857142857143, 2.057142857142857, 2.5714285714285716, 0.08571428571428572, 0.6, 1.1142857142857143, 1.6285714285714286, 2.142857142857143, 2.657142857142857, 0.17142857142857143, 0.6857142857142857, 1.2, 1.7142857142857144, 2.2285714285714286, 2.742857142857143, 0.2571428571428571, 0.7714285714285715, 1.2857142857142858, 1.8, 2.3142857142857145, 2.8285714285714287, 0.34285714285714286, 0.8571428571428572, 1.3714285714285714, 1.8857142857142857, 2.4, 2.914285714285714],
    "ImageOrientationPatientDICOM": [
        0.99852,
        0.0442888,
        0.0315681,
        -0.0329498,
        0.954388,
        -0.296744
    ],
    "ConversionSoftware": "dcm2niix",
    "ConversionSoftwareVersion": "v1.0.20220505",
    "TaskName": "BRAINMRINONCONTRASTDIFFUSION"
}

I know that my question could be hard to understand.

Please lemme know if im missing something.

Thanks again. @cni-md

I count only 30 values in the “Slice Timing” array. There must be 35, one for each slice.

Additionally, there should not be a space between Slice and Timing, as there current is in your JSON.

Hi @dhwnsdyd21 ,

As other have mentioned above, slice timing is not directly provided by Philips scanner, but can be calculated based on your acquisition protocol.

For my study I created this small python utility that helps me calculating the correct timings for multi-band acquisitions. In my case (I assume this is the Philips default, but please corrects me if I am wrong) I have a multi-band factor of 2 (2 slices collected for each time point) where the first slices are the bottom one and the central one. Here is the code:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Mar 29 19:29:46 2024

@author: costantino_ai
"""

import pydicom
import numpy as np

def calculate_slice_timing(tr, slices, multiband_factor):
    """
    Calculate the slice timing order for fMRI data according to a specific interleaved pattern.
    In this pattern, acquisition starts from both ends of the slice stack towards the middle,
    with the first and the middle slice starting at time 0, and so on in pairs.

    :param tr: Repetition time (TR) in seconds.
    :param slices: Total number of slices.
    :param multiband_factor: Multiband acceleration factor (how many slices per time bin).
    :return: A list of slice timings in seconds, compatible with BIDS format.
    """
    # Time per slice is the TR divided by the number of slice groups (half the number of slices)
    time_per_slice = tr / (len(slices) / multiband_factor)
    # Initialize slice timings array with zeros
    slice_timings = np.zeros(len(slices))
    
    for i in range(len(slices) // multiband_factor):
        # Time for the current pair of slices
        current_time = i * time_per_slice
        # Set timing for a pair: one from the start and one from the middle
        slice_timings[i] = current_time
        slice_timings[i + len(slices) // multiband_factor] = current_time

    return np.array(slice_timings)

def extract_dicom_info(f):
    """
    Extracts TR and the total number of slices from a DICOM file.

    :param f: Path to the DICOM file.
    :return: Tuple containing the TR in seconds and the total number of slices.
    """
    d = pydicom.read_file(f, defer_size='1KB', stop_before_pixels=True)
    tr = float(d.SharedFunctionalGroupsSequence[0].MRTimingAndRelatedParametersSequence[0].RepetitionTime) / 1000
    all_slices = d.PerFrameFunctionalGroupsSequence
    slices = [s for s in all_slices
              if s.FrameContentSequence[0].TemporalPositionIndex == 1]
    return tr, slices

# Set filename to the DICOM image file
dicom_fname = "/data/projects/chess/data/sourcedata/sub-34/dicom/DICOM/IM_0001"

# Get relevant info
tr, slices = extract_dicom_info(dicom_fname)
multiband_factor = 2

# Get the slice timing
# NOTE: this assumes an ascending order (FH), where the first time bin takes
#       the first slice and the slice at half. For instance, if you have 60 
#       slices, at time 0 you get slice 1 and 31, at time 1 slice 2-32, etc.  
#       For single-band, see https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/4
slice_timings = calculate_slice_timing(tr, slices, multiband_factor)
print(slice_timings)

you will need to copy these values in the Slice Timing entry of your JSON file.

I hope it helps!

Andrea

1 Like