# NiBabel

### Access a cacophony of neuro-imaging file formats

# `ecat`

Read ECAT format images

An ECAT format image consists of:

- a main header;

- at least one matrix list (mlist);

ECAT thinks of memory locations in terms of blocks. One block is 512 bytes. Thus block 1 starts at 0 bytes, block 2 at 512 bytes, and so on.

The matrix list is an array with one row per frame in the data.

Columns in the matrix list are:

- 0: Matrix identifier (frame number)

- 1: matrix data start block number (subheader followed by image data)

- 2: Last block number of matrix (image) data

- 3: Matrix status

  > - 1: hxists - rw

  > - 2: exists - ro

  > - 3: matrix deleted

There is one sub-header for each image frame (or matrix in the terminology above). A sub-header can also be called an image header. The sub-header is one block (512 bytes), and the frame (image) data follows.

There is very little documentation of the ECAT format, and many of the comments in this code come from a combination of trial and error and wild speculation.

XMedcon can read and write ECAT 6 format, and read ECAT 7 format: see http://xmedcon.sourceforge.net and the ECAT files in the source of XMedCon, currently `libs/tpc/*ecat*` and `source/m-ecat*`. Unfortunately XMedCon is GPL and some of the header files are adapted from CTI files (called CTI code below). It’s not clear what the licenses are for these files.

`EcatHeader`([binaryblock, endianness, check]) | Class for basic Ecat PET header
`EcatImage`(dataobj, affine, header, ...[, ...]) | Class returns a list of Ecat images, with one image(hdr/data) per frame
`EcatImageArrayProxy`(subheader) | Ecat implementation of array proxy protocol
`EcatSubHeader`(hdr, mlist, fileobj) | parses the subheaders in the ecat (.v) file there is one subheader for each frame in the ecat file
`get_frame_order`(mlist) | Returns the order of the frames stored in the file Sometimes Frames are not stored in the file in chronological order, this can be used to extract frames in correct order
`get_series_framenumbers`(mlist) | Returns framenumber of data as it was collected, as part of a series; not just the order of how it was stored in this or across other files
`read_mlist`(fileobj, endianness) | read (nframes, 4) matrix list array from fileobj
`read_subheaders`(fileobj, mlist, endianness) | Retrieve all subheaders and return list of subheader recarrays

## `EcatHeader`

class nibabel.ecat.EcatHeader(binaryblock=None, endianness=None, check=True)

  Bases: `WrapStruct`, `SpatialHeader`

  Class for basic Ecat PET header

  Sub-parts of standard Ecat File

  - main header

  - matrix list which lists the information for each frame collected (can have 1 to many frames)

  - subheaders specific to each frame with possibly-variable sized data blocks

  This just reads the main Ecat Header, it does not load the data or read the mlist or any sub headers

  Initialize Ecat header from bytes object

  Parameters:
    binaryblock{None, bytes} optional

      binary block to set into header, By default, None in which case we insert default empty header block

    endianness{None, ‘<’, ‘>’, other endian code}, optional

      endian code of binary block, If None, guess endianness from the data

    check{True, False}, optional

      Whether to check and fix header for errors. No checks currently implemented, so value has no effect.

  __init__(binaryblock=None, endianness=None, check=True)

    Initialize Ecat header from bytes object

    Parameters:
      binaryblock{None, bytes} optional

        binary block to set into header, By default, None in which case we insert default empty header block

      endianness{None, ‘<’, ‘>’, other endian code}, optional

        endian code of binary block, If None, guess endianness from the data

      check{True, False}, optional

        Whether to check and fix header for errors. No checks currently implemented, so value has no effect.

  classmethod default_structarr(endianness=None)

    Return header data for empty header with given endianness

  get_data_dtype()

    Get numpy dtype for data from header

  get_filetype()

    Type of ECAT Matrix File from code stored in header

  get_patient_orient()

    gets orientation of patient based on code stored in header, not always reliable

  classmethod guessed_endian(hdr)

    Guess endian from MAGIC NUMBER value of header data

  template_dtype = dtype([('magic_number', 'S14'), ('original_filename', 'S32'), ('sw_version', '<u2'), ('system_type', '<u2'), ('file_type', '<u2'), ('serial_number', 'S10'), ('scan_start_time', '<u4'), ('isotope_name', 'S8'), ('isotope_halflife', '<f4'), ('radiopharmaceutical', 'S32'), ('gantry_tilt', '<f4'), ('gantry_rotation', '<f4'), ('bed_elevation', '<f4'), ('intrinsic_tilt', '<f4'), ('wobble_speed', '<u2'), ('transm_source_type', '<u2'), ('distance_scanned', '<f4'), ('transaxial_fov', '<f4'), ('angular_compression', '<u2'), ('coin_samp_mode', '<u2'), ('axial_samp_mode', '<u2'), ('ecat_calibration_factor', '<f4'), ('calibration_unitS', '<u2'), ('calibration_units_type', '<u2'), ('compression_code', '<u2'), ('study_type', 'S12'), ('patient_id', 'S16'), ('patient_name', 'S32'), ('patient_sex', 'S1'), ('patient_dexterity', 'S1'), ('patient_age', '<f4'), ('patient_height', '<f4'), ('patient_weight', '<f4'), ('patient_birth_date', '<u4'), ('physician_name', 'S32'), ('operator_name', 'S32'), ('study_description', 'S32'), ('acquisition_type', '<u2'), ('patient_orientation', '<u2'), ('facility_name', 'S20'), ('num_planes', '<u2'), ('num_frames', '<u2'), ('num_gates', '<u2'), ('num_bed_pos', '<u2'), ('init_bed_position', '<f4'), ('bed_position', '<f4', (15,)), ('plane_separation', '<f4'), ('lwr_sctr_thres', '<u2'), ('lwr_true_thres', '<u2'), ('upr_true_thres', '<u2'), ('user_process_code', 'S10'), ('acquisition_mode', '<u2'), ('bin_size', '<f4'), ('branching_fraction', '<f4'), ('dose_start_time', '<u4'), ('dosage', '<f4'), ('well_counter_corr_factor', '<f4'), ('data_units', 'S32'), ('septa_state', '<u2'), ('fill', 'S12')])

## `EcatImage`

class nibabel.ecat.EcatImage(dataobj, affine, header, subheader, mlist, extra=None, file_map=None)

  Bases: `SpatialImage`

  Class returns a list of Ecat images, with one image(hdr/data) per frame

  Initialize Image

  The image is a combination of (array, affine matrix, header, subheader, mlist) with optional meta data in extra, and filename / file-like objects contained in the file_map.

  Parameters:
    dataobj array-like

      image data

    affine None or (4,4) array-like

      homogeneous affine giving relationship between voxel coords and world coords.

    header None or header instance

      meta data for this image format

    subheader None or subheader instance

      meta data for each sub-image for frame in the image

    mlist None or array

      Matrix list array giving offset and order of data in file

    extra None or mapping, optional

      metadata associated with this image that cannot be stored in header or subheader

    file_map mapping, optional

      mapping giving file information for this image format

  Examples

      >>> import os
      >>> import nibabel as nib
      >>> nibabel_dir = os.path.dirname(nib.__file__)
      >>> from nibabel import ecat
      >>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v')
      >>> img = ecat.load(ecat_file)
      >>> frame0 = img.get_frame(0)
      >>> frame0.shape == (10, 10, 3)
      True
      >>> data4d = img.get_fdata()
      >>> data4d.shape == (10, 10, 3, 1)
      True

  __init__(dataobj, affine, header, subheader, mlist, extra=None, file_map=None)

    Initialize Image

    The image is a combination of (array, affine matrix, header, subheader, mlist) with optional meta data in extra, and filename / file-like objects contained in the file_map.

    Parameters:
      dataobj array-like

        image data

      affine None or (4,4) array-like

        homogeneous affine giving relationship between voxel coords and world coords.

      header None or header instance

        meta data for this image format

      subheader None or subheader instance

        meta data for each sub-image for frame in the image

      mlist None or array

        Matrix list array giving offset and order of data in file

      extra None or mapping, optional

        metadata associated with this image that cannot be stored in header or subheader

      file_map mapping, optional

        mapping giving file information for this image format

    Examples

        >>> import os
        >>> import nibabel as nib
        >>> nibabel_dir = os.path.dirname(nib.__file__)
        >>> from nibabel import ecat
        >>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v')
        >>> img = ecat.load(ecat_file)
        >>> frame0 = img.get_frame(0)
        >>> frame0.shape == (10, 10, 3)
        True
        >>> data4d = img.get_fdata()
        >>> data4d.shape == (10, 10, 3, 1)
        True

  ImageArrayProxy

    alias of `EcatImageArrayProxy`

  property affine
  files_types: tuple[ExtensionSpec, ...] = (('image', '.v'), ('header', '.v'))
  classmethod from_file_map(file_map, *, mmap=True, keep_file_open=None)

    class method to create image from mapping specified in file_map

  classmethod from_image(img)

    Class method to create new instance of own class from img

    Parameters:
      img `spatialimage` instance

        In fact, an object with the API of `spatialimage` - specifically `dataobj`, `affine`, `header` and `extra`.

    Returns:
      cimg `spatialimage` instance

        Image, of our own class

  get_data_dtype(frame)
  get_frame(frame, orientation=None)

    Get full volume for a time frame

    Parameters:

      - frame – Time frame index from where to fetch data

      - orientation – None (default), ‘neurological’ or ‘radiological’

    Return type:

      Numpy array containing (possibly oriented) raw data

  get_frame_affine(frame)

    returns 4X4 affine

  get_mlist()

    get access to the mlist

  get_subheaders()

    get access to subheaders

  header_class

    alias of `EcatHeader`

  classmethod load(filespec)

    Class method to create image from filename filename

    Parameters:
      filename str

        Filename of image to load

      mmap{True, False, ‘c’, ‘r’}, optional, keyword only

        mmap controls the use of numpy memory mapping for reading image array data. If False, do not try numpy `memmap` for data array. If one of {‘c’, ‘r’}, try numpy memmap with `mode=mmap`. A mmap value of True gives the same behavior as `mmap='c'`. If image data file cannot be memory-mapped, ignore mmap value and read array from file.

      keep_file_open{ None, True, False }, optional, keyword only

        keep_file_open controls whether a new file handle is created every time the image is accessed, or a single file handle is created and used for the lifetime of this `ArrayProxy`. If `True`, a single file handle is created and used. If `False`, a new file handle is created every time the image is accessed. The default value (`None`) will result in the value of `nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT` being used.

    Returns:
      img DataobjImage instance
  property shape
  subheader_class

    alias of `EcatSubHeader`

  to_file_map(file_map=None)

    Write ECAT7 image to file_map or contained `self.file_map`

    The format consist of:

    - A main header (512L) with dictionary entries in the form

        [numAvail, nextDir, previousDir, numUsed]

    - For every frame (3D volume in 4D data) - A subheader (size = frame_offset) - Frame data (3D volume)

  valid_exts: tuple[str, ...] = ('.v',)

## `EcatImageArrayProxy`

class nibabel.ecat.EcatImageArrayProxy(subheader)

  Bases: `object`

  Ecat implementation of array proxy protocol

  The array proxy allows us to freeze the passed fileobj and header such that it returns the expected data array.

  __init__(subheader)
  property is_proxy
  property ndim
  property shape

## `EcatSubHeader`

class nibabel.ecat.EcatSubHeader(hdr, mlist, fileobj)

  Bases: `object`

  parses the subheaders in the ecat (.v) file there is one subheader for each frame in the ecat file

  Parameters:
    hdr EcatHeader

      ECAT main header

    mlist array shape (N, 4)

      Matrix list

    fileobj ECAT file <filename>.v fileholder or file object

      with read, seek methods

  __init__(hdr, mlist, fileobj)

    parses the subheaders in the ecat (.v) file there is one subheader for each frame in the ecat file

    Parameters:
      hdr EcatHeader

        ECAT main header

      mlist array shape (N, 4)

        Matrix list

      fileobj ECAT file <filename>.v fileholder or file object

        with read, seek methods

  data_from_fileobj(frame=0, orientation=None)

    Read scaled data from file for a given frame

    Parameters:

      - frame – Time frame index from where to fetch data

      - orientation – None (default), ‘neurological’ or ‘radiological’

    Return type:

      Numpy array containing (possibly oriented) raw data

    See also

    raw_data_from_fileobj

  get_frame_affine(frame=0)

    returns best affine for given frame of data

  get_nframes()

    returns number of frames

  get_shape(frame=0)

    returns shape of given frame

  get_zooms(frame=0)

    returns zooms …pixdims

  raw_data_from_fileobj(frame=0, orientation=None)

    Get raw data from file object.

    Parameters:

      - frame – Time frame index from where to fetch data

      - orientation – None (default), ‘neurological’ or ‘radiological’

    Return type:

      Numpy array containing (possibly oriented) raw data

    See also

    data_from_fileobj

## get_frame_order

nibabel.ecat.get_frame_order(mlist)

  Returns the order of the frames stored in the file Sometimes Frames are not stored in the file in chronological order, this can be used to extract frames in correct order

  Returns:
    id_dict: dict mapping frame number -> [mlist_row, mlist_id]
    (where mlist id is value in the first column of the mlist matrix )

  Examples

      >>> import os
      >>> import nibabel as nib
      >>> nibabel_dir = os.path.dirname(nib.__file__)
      >>> from nibabel import ecat
      >>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v')
      >>> img = ecat.load(ecat_file)
      >>> mlist = img.get_mlist()
      >>> get_frame_order(mlist)
      {0: [0, 16842758]}

## get_series_framenumbers

nibabel.ecat.get_series_framenumbers(mlist)

  Returns framenumber of data as it was collected, as part of a series; not just the order of how it was stored in this or across other files

  For example, if the data is split between multiple files this should give you the true location of this frame as collected in the series (Frames are numbered starting at ONE (1) not Zero)

  Returns:
    frame_dict: dict mapping order_stored -> frame in series

      where frame in series counts from 1; [1,2,3,4…]

  Examples

      >>> import os
      >>> import nibabel as nib
      >>> nibabel_dir = os.path.dirname(nib.__file__)
      >>> from nibabel import ecat
      >>> ecat_file = os.path.join(nibabel_dir,'tests','data','tinypet.v')
      >>> img = ecat.load(ecat_file)
      >>> mlist = img.get_mlist()
      >>> get_series_framenumbers(mlist)
      {0: 1}

## read_mlist

nibabel.ecat.read_mlist(fileobj, endianness)

  read (nframes, 4) matrix list array from fileobj

  Parameters:
    fileobj file-like

      an open file-like object implementing `seek` and `read`

  Returns:
    mlist(nframes, 4) ndarray

      matrix list is an array with `nframes` rows and columns:

      - 0: Matrix identifier (frame number)

      - 1: matrix data start block number (subheader followed by image data)

      - 2: Last block number of matrix (image) data

      - 3: Matrix status

        > - 1: hxists - rw

        > - 2: exists - ro

        > - 3: matrix deleted

  Notes

  A block is 512 bytes.

  `block_no` in the code below is 1-based. block 1 is the main header, and the mlist blocks start at block number 2.

  The 512 bytes in an mlist block contain 32 rows of the int32 (nframes, 4) mlist matrix.

  The first row of these 32 looks like a special row. The 4 values appear to be (respectively):

  - not sure - maybe negative number of mlist rows (out of 31) that are blank and not used in this block. Called nfree but unused in CTI code;

  - block_no - of next set of mlist entries or 2 if no more entries. We also allow 1 or 0 to signal no more entries;

  - <no idea>. Called prvblk in CTI code, so maybe previous block no;

  - n_rows - number of mlist rows in this block (between ?0 and 31) (called nused in CTI code).

## read_subheaders

nibabel.ecat.read_subheaders(fileobj, mlist, endianness)

  Retrieve all subheaders and return list of subheader recarrays

  Parameters:
    fileobj file-like

      implementing `read` and `seek`

    mlist(nframes, 4) ndarray

      Columns are: * 0 - Matrix identifier. * 1 - subheader block number * 2 - Last block number of matrix data block. * 3 - Matrix status

    endianness{‘<’, ‘>’}

      little / big endian code

  Returns:
    subheaders list

      List of subheader structured arrays