Example #1
0
 def _get_xform(self):
     "forms a Quaternion object and the r0 vector given a hdr dictionary"
     # The hdr dictionary is guaranteed to be full, if not specific
     if self.qform_code:
         qb, qc, qd, qfac = (self.quatern_b, self.quatern_c,
                             self.quatern_d, self.qfac)
         quat = Quaternion(i=qb, j=qc, k=qd, qfac=qfac)
         offset = (self.qoffset_x, self.qoffset_y, self.qoffset_z)
     elif self.sform_code:
         M = [[ self.srow_x0,self.srow_x1,self.srow_x2 ],
              [ self.srow_y0,self.srow_y1,self.srow_y2 ],
              [ self.srow_z0,self.srow_z1,self.srow_z2 ]]
         quat = Quaternion(M=M)
         offset = (self.srow_x3, self.srow_y3, self.srow_z3)
     else:
         quat = Quaternion(M=N.identity(3))
         offset = (0,0,0)
     return quat, offset
Example #2
0
 def toImage(self):
     orient_name = orientcode2orientname.get(self.orient, "")
     M = xforms.get(orient_name, np.identity(3))
     quat = Quaternion(M=M)
     offset_ana = np.array(
         [self.x0 * self.isize, self.y0 * self.jsize, self.z0 * self.ksize])
     offset = -np.dot(M, offset_ana)
     dimlengths = np.array([self.ksize, self.jsize, self.isize]) * \
                  np.array(self.data.shape[-3:])
     ##         # sanity check: if the offset values were funky, don't try to set it
     ##         if not (np.abs(offset*2) < dimlengths).all():
     ##             offset = None
     return ReconImage(self.data.copy(),
                       self.isize,
                       self.jsize,
                       self.ksize,
                       self.tsize,
                       offset=offset,
                       scaling=(self.scale_factor or 1.0),
                       orient_xform=quat)
Example #3
0
    def __init__(self,
                 data,
                 isize=1.,
                 jsize=1.,
                 ksize=1.,
                 tsize=1.,
                 offset=None,
                 scaling=None,
                 orient_xform=None):
        """
        Construct a ReconImage with at least data, isize, jsize, ksize,
        and tsize known. Optional information are an offset 3-tuple
        specifying (x0,y0,z0), a Quaternion object representing the
        transformation of this data to neurological orientation
        (+X,+Y,+Z) = (Right,Anterior,Superior), and a name for the data's
        orientation (used for ANALYZE format output).
        """
        self.setData(data)
        self.isize, self.jsize, self.ksize, self.tsize = \
                    (isize, jsize, ksize, tsize)

        self.orientation_xform = orient_xform or Quaternion()

        # offset should be the (x,y,z) offset in xyz-space
        xform = self.orientation_xform.tomatrix()
        if offset is not None:
            (self.x0, self.y0, self.z0) = offset
        else:
            # assume that vox at (idim/2, jdim/2, kdim/2) is the origin
            # thus Trans*(i0,j0,k0)^T + (x0,y0,z0)^T = (0,0,0)^T
            (self.x0, self.y0, self.z0) = \
                      -np.dot(xform, np.array([self.isize*self.idim/2.,
                                               self.jsize*self.jdim/2.,
                                               self.ksize*self.kdim/2.]))

        self.scaling = scaling or 1.0
Example #4
0
def parse_siemens_hdr(fname):
    """
    lRepetitions                             = 19 (= nvol - 1)
    sKSpace.lBaseResolution                  = 64
    sKSpace.lPhaseEncodingLines              = 128
    sKSpace.ucMultiSliceMode                 = 0x2 (interleaved)
    tSequenceFileName                        = "%CustomerSeq%\ep2d_bold_bigfov"
    sRXSPEC.alDwellTime[0]                   = 2800
    alTR[0]                                  = 2500000
    lContrasts                               = 2
    alTE[0]                                  = 4920
    alTE[1]                                  = 7380
    sSliceArray.asSlice[0].sPosition.dTra    = -39.375
    sSliceArray.asSlice[0].sNormal.dTra      = 1
    sSliceArray.asSlice[0].dThickness        = 3
    sSliceArray.asSlice[0].dPhaseFOV         = 240
    sSliceArray.asSlice[0].dReadoutFOV       = 240
    sSliceArray.asSlice[0].dInPlaneRot       = 2.051034897e-010
    ...
    sSliceArray.lSize                        = 22
    sFastImaging.lEchoSpacing                = 420
    sPat.lAccelFactPE                        = 1
    sPat.lRefLinesPE                         = 24
    asCoilSelectMeas[0].aFFT_SCALE[0].flFactor = 1.26665
    asCoilSelectMeas[0].aFFT_SCALE[0].bValid = 1
    asCoilSelectMeas[0].aFFT_SCALE[0].lRxChannel = 1
    ...

    more info...
    sSliceArray.ucMode (I THINK this is slice acq orde -- can it be OR'd??)
    enum SeriesMode
    {
      ASCENDING   = 0x01,
      DESCENDING  = 0x02,
      INTERLEAVED = 0x04
    };
    
    sKSpace.unReordering
    enum Reordering
    {
      REORDERING_LINEAR    = 0x01,
      REORDERING_CENTRIC   = 0x02,
      REORDERING_LINE_SEGM = 0x04,
      REORDERING_PART_SEGM = 0x08,
      REORDERING_FREE_0    = 0x10,
      REORDERING_FREE_1    = 0x20,
      REORDERING_FREE_2    = 0x40,
      REORDERING_FREE_3    = 0x80
    };
    sKSpace.ucPhasePartialFourier
    sKSpace.ucSlicePartialFourier
    enum PartialFourierFactor
    {
      PF_HALF = 0x01,
      PF_5_8  = 0x02,
      PF_6_8  = 0x04,
      PF_7_8  = 0x08,
      PF_OFF  = 0x10
    };
    sKSpace.ucMultiSliceMode
    enum MultiSliceMode
    {
      MSM_SEQUENTIAL  = 0x01,
      MSM_INTERLEAVED = 0x02,
      MSM_SINGLESHOT  = 0x04
    };

    slice spacing ( can do with position arrays )
    slice order / acq order

    missing:
    n_pe_acq (can this be derived from n_pe and accel? may be 31 for accel=2)
    n_ref == nsegmeas + 1 (or is that a coincidence???)
    n_part -- do I care?
    rampsamp info
    """
    hdr_dict = {}
    ##     hdrlen = header_length(fname)
    ##     hdr_str = open(fname, 'r').read(4+hdrlen)
    hdr_str = header_string(fname)
    asc_dict = strip_ascconv(hdr_str)
    chan_scales = condense_array(asc_dict, 'asCoilSelectMeas[0].aFFT_SCALE')
    gains = chan_scales['flFactor']
    chans = chan_scales['lRxChannel'].astype('i')  # usually [1, 2, 3, 4, ...]
    hdr_dict['n_chan'] = gains.shape[0]
    hdr_dict['channel_gains'] = gains[chans - 1]
    hdr_dict['n_echo'] = int(asc_dict['lContrasts'])
    hdr_dict['n_vol'] = int(1 + asc_dict.get('lRepetitions', 0))
    hdr_dict['n_slice'] = int(asc_dict.get('sSliceArray.lSize', 1))
    hdr_dict['n_partition'] = int(asc_dict.get('sKSpace.lPartitions', 1))
    # I've seen set down as 127.. I don't think it could hurt to enforce 2**n
    # .. doing ceil(log2(n_pe)) is much more strong than round(log2(n_pe))
    # .. which one??
    n_pe = int(asc_dict['sKSpace.lPhaseEncodingLines'])
    n_pe = int(2**np.ceil(np.log2(n_pe)))
    hdr_dict['n_pe'] = n_pe
    hdr_dict['N1'] = int(asc_dict['sKSpace.lBaseResolution'])
    hdr_dict['M1'] = hdr_dict['n_fe'] = 2 * hdr_dict['N1']
    hdr_dict['fov_x'] = asc_dict['sSliceArray.asSlice[0].dReadoutFOV']
    hdr_dict['fov_y'] = asc_dict['sSliceArray.asSlice[0].dPhaseFOV']
    hdr_dict['tr'] = asc_dict['alTR[0]']
    hdr_dict['te'] = []
    for e in range(hdr_dict['n_echo']):
        hdr_dict['te'].append(asc_dict['alTE[%d]' % e])
    hdr_dict['dwell_time'] = asc_dict['sRXSPEC.alDwellTime[0]']
    hdr_dict['echo_spacing'] = asc_dict.get('sFastImaging.lEchoSpacing', 0.0)
    hdr_dict['accel'] = asc_dict.get('sPat.lAccelFactPE', 1)
    hdr_dict['n_acs'] = asc_dict.get('sPat.lRefLinesPE', 0)
    pslabel = asc_dict['tSequenceFileName'].split('\\')[-1]
    hdr_dict['isepi'] = (pslabel.find('ep2d') >= 0)
    hdr_dict['isgre'] = (pslabel == 'gre')
    hdr_dict['isagems'] = (pslabel == 'gre_field_mapping')
    hdr_dict['isgrs'] = (pslabel.find('grs3d') >= 0)
    hdr_dict['pslabel'] = pslabel

    # now decode some bit encoded fields
    slices = np.arange(hdr_dict['n_slice'])
    slicing_mode = asc_dict['sSliceArray.ucMode']
    if slicing_mode == 4:
        # interleaved
        if hdr_dict['n_slice'] % 2:
            hdr_dict['acq_order'] = np.concatenate(
                (slices[0::2], slices[1::2]))
        else:
            hdr_dict['acq_order'] = np.concatenate(
                (slices[1::2], slices[0::2]))
    elif slicing_mode == 2:
        # descending
        hdr_dict['acq_order'] = slices[::-1]
    else:
        # ascending
        hdr_dict['acq_order'] = slices

    sampstyle = asc_dict['sKSpace.unReordering']
    hdr_dict['sampstyle'] = {
        1: 'linear',
        2: 'centric'
    }.get(sampstyle, 'unknown')

    partial_fourier = asc_dict['sKSpace.ucPhasePartialFourier']
    # factors are encoded in increments of n/8 starting at 4
    partial_numerator = {1: 4, 2: 5, 4: 6, 8: 7, 16: 8}.get(partial_fourier)
    hdr_dict['pe0'] = int(
        round(hdr_dict['n_pe'] * (1 / 2. - partial_numerator / 8.)))

    # Now get rampsamp info, n_pe_acq (in case of accel>1), n_ref
    fields_by_section = {
        '<ParamFunctor."rawobjprovider">': [(
            '<ParamLong."RawLin">',  # raw str
            'n_pe_acq',  # our name
            int)],  # how to convert
        '<ParamFunctor."adjroftregrid">':
        [('<ParamLong."RampupTime">', 'T_ramp', float),
         ('<ParamLong."FlattopTime">', 'T_flat', float),
         ('<ParamLong."DelaySamplesTime">', 'T0', float),
         ('<ParamDouble."ADCDuration">', 'adc_period', float)],
        '<ParamFunctor."EPIPhaseCorrPE">':
        [('<ParamLong."NSeg">', 'n_refs', int)]
    }
    for section, parse_info in fields_by_section.items():
        p = hdr_str.find(section)
        valid = p >= 0
        if valid:
            s = hdr_str[p:]
        for (field, val_name, evalfunc) in parse_info:
            if not valid:
                hdr_dict[val_name] = evalfunc('0')
                continue
            p = s.find(field)
            s = s[(p + len(field)):]
            p = s.find('\n')
            val = s[:p].split()[-2]
            hdr_dict[val_name] = evalfunc(val)

    # see if we have should forge a plausible gradient shape
    if not hdr_dict['adc_period'] and (hdr_dict['isepi'] or hdr_dict['isgrs']):
        # time resolution for gradient events is 5 microsec..
        # calculate flat time as dt * M1
        # cut echo spacing time into 3 even parts: ramp_up + flat + ramp_dn
        adc = (hdr_dict['M1'] - 1) * hdr_dict['dwell_time'] / 1e3
        Tpe = hdr_dict['echo_spacing']
        # 1) make flat time long enough to support adc period
        # 2) make echo-spacing - flat_time be evenly split in two
        npts = int(adc / 5)
        if npts % 2:
            npts += 1
        else:
            npts += 2
        flat = npts * 5
        ramps = (Tpe - flat) / 2
        hdr_dict['T_flat'] = flat
        hdr_dict['T_ramp'] = ramps
        hdr_dict['T0'] = ramps
        hdr_dict['adc_period'] = adc
    hdr_dict['ramp_samp'] = (hdr_dict['T0'] < hdr_dict['T_ramp'])

    # now get slice thickness order, x0, y0, z0
    # (or is it i0, j0, k0 ??)

    slice_arrays = condense_array(asc_dict, 'sSliceArray.asSlice')
    ns = hdr_dict['n_slice']
    pos_tra = slice_arrays.get('sPosition.dTra', np.zeros(ns))
    pos_cor = slice_arrays.get('sPosition.dCor', np.zeros(ns))
    pos_sag = slice_arrays.get('sPosition.dSag', np.zeros(ns))
    if ns > 1:
        hdr_dict['dSL'] = np.sqrt( (pos_tra[1]-pos_tra[0])**2 + \
                                   (pos_cor[1]-pos_cor[0])**2 + \
                                   (pos_sag[1]-pos_sag[0])**2 )
        hdr_dict['slice_thick'] = slice_arrays['dThickness'][0]
        hdr_dict['slice_gap'] = hdr_dict['dSL'] - hdr_dict['slice_thick']
    else:
        hdr_dict['dSL'] = hdr_dict['slice_thick'] = hdr_dict['slice_gap'] = 1.


##     norm_tra = slice_arrays.get('sNormal.dTra', np.zeros(ns))
##     norm_cor = slice_arrays.get('sNormal.dCor', np.zeros(ns))
##     norm_sag = slice_arrays.get('sNormal.dSag', np.zeros(ns))

##     # the normal tells us the angles phi and theta in the rotation,
##     # psi is found literally in the header
##     normal = np.array([norm_sag[0], norm_cor[0], norm_tra[0]])

##     theta = np.arccos(normal[2])
##     phi = np.arccos(-normal[1]/np.sin(theta))
##     psi = slice_arrays.get('dInPlaneRot', np.zeros(ns))[0]

##     m = real_euler_rot(phi=phi, theta=theta, psi=psi)

    dat = MemmapDatFile(fname, nblocks=1)
    mdh = MDH(dat[0]['hdr'])
    in_plane_rot = slice_arrays.get('dInPlaneRot', np.zeros(ns))[0]
    # this is the "rotated" voxel to world mapping
    xform = Quaternion(i=mdh.quatI, j=mdh.quatJ, k=mdh.quatK)
    # this is the voxel transform in the vox coordinates
    m = xform.tomatrix()
    # maybe this is irrelevant? maybe the rotation matrix is just
    # x pe fe sl
    # L x  x  x
    # P x  x  x
    # S x  x  x
    ##     rot = eulerRot(phi=in_plane_rot)
    ##     m = np.dot(m, rot)
    # m is now the transform from (j,i,k) to (L,P,S) (DICOM coords)
    # since (R,A,S) = (-L,-P,S), just multiply two axes by -1 and swap them
    m_ijk = np.zeros_like(m)
    m_ijk[:, 0] = -m[:, 1]
    m_ijk[:, 1] = -m[:, 0]
    m_ijk[:, 2] = m[:, 2]
    m_ijk[:2] *= -1

    # now m*(n_fe/2, n_pe/2, 0) + r0 = (-pos_sag[0], -pos_cor[0], pos_tra[0])
    kdim = hdr_dict['n_slice']
    ksize = hdr_dict['dSL']
    jdim = hdr_dict['n_pe']
    jsize = hdr_dict['fov_y'] / jdim
    # account for oversampling.. n_fe is already 2X base resolution
    idim = hdr_dict['n_fe']
    isize = 2.0 * hdr_dict['fov_x'] / idim
    dim_scale = np.array([isize, jsize, ksize])
    sl0_center = np.array([-pos_sag[0], -pos_cor[0], pos_tra[0]])
    # want sl0_center = M*(i=idim/2,j=jdim/2,k=0) + r0
    sl0_vox_center = np.array([idim / 2, jdim / 2, 0])
    r0 = sl0_center - np.dot(m_ijk * dim_scale, sl0_vox_center)
    ##     print m*dim_scale, sl0_vox_center
    ##     print sl0_center, r0
    # would have to re-adjust one of the r0 components when oversampling
    # is eliminated
    hdr_dict['orientation_xform'] = Quaternion(M=m_ijk)
    hdr_dict['x0'] = r0[0]
    hdr_dict['y0'] = r0[1]
    hdr_dict['z0'] = r0[2]
    # faking this for now..
    hdr_dict['nseg'] = 1
    return hdr_dict
Example #5
0
    def transform(self, new_mapping=None, transform=None, force=False):
        """Updates the voxel to real-space transform.

        There are two modes of usage--
        1) supply a new voxel to real mapping.
           In this case a voxel to voxel transform is found, and the image
           is rotated in-plane around the origin. Only transposes and
           reflections are supported. Image info is updated appropriately

        2) supply a real to real transform to apply to the current mapping.
           In this case the data is not updated, but the mapping is updated.

        """
        if new_mapping is None and transform is None:
            return
        if new_mapping is not None and transform is not None:
            print """
            The user must specify either a new mapping to convert to,
            or a transform to apply, but cannot specify both."""
            return
        if transform is not None:
            # this doesn't change the image array, it just updates the
            # transformation
            old_xform = self.orientation_xform.tomatrix()
            if isinstance(transform, Quaternion):
                transform = transform.tomatrix()
            dim_scale = np.array([self.isize, self.jsize, self.ksize])
            r0 = np.array([self.x0, self.y0, self.z0])
            origin_voxels = np.round(
                np.linalg.solve(old_xform * dim_scale, -r0))
            # now derive r0 again.. Tmap*(i,j,k)^T + r0^T = (x,y,z)^T
            r0 = -np.dot(transform * dim_scale, origin_voxels)
            self.x0, self.y0, self.z0 = r0
            self.orientation_xform = Quaternion(M=transform)
            return
        # else handle the new mapping
        from recon.slicerimage import nearest_simple_transform
        # Tr already maps [i,j,k]^T into [R,A,S] ...
        # Here I want to change coordinates with this relationship:
        # Tr*[i,j,k]^T = Tnew*[i',j',k']^T
        # so Tnew*(Tvx*[i,j,k]^T) = Tr*[i,j,k]^T
        # So inv(Tnew)*Tr*[i,j,k] = [i',j',k'] = orientation of choice!
        # The task is to analyze Tvx = (Tnew^-1 * Tr) to get rotation

        Tr = self.orientation_xform.tomatrix()
        Tvx = np.linalg.solve(new_mapping, Tr)
        Tvxp = nearest_simple_transform(Tvx)
        # allow a goodly amount of wiggle room for each element of the
        # rotation matrix
        if not np.allclose(Tvxp, Tvx, atol=1e-4):
            # Tvxp might be a good choice in this case.. so could suggest
            # Tnew'*Tvxp = Tr
            # (Tvxp^T * Tnew'^T) = Tr^T
            # Tnew' = solve(Tvxp^T, Tr^T)^T
            Tnew_suggest = np.linalg.solve(Tvxp.T, Tr.T).T
            if not force:
                raise ValueError("""This method will not transform the data to
                the stated new mapping because the transformation cannot be
                accomplished through transposes and reflections. The closest new
                mapping you can perform is:\n""" + str(Tnew_suggest))
            else:
                print """It is not possible to rotate simply to the stated
                mapping; proceeding with this mapping:\n"""+str(Tnew_suggest)+\
                """\nbut lying about the final mapping."""
                #new_mapping = Tnew_suggest
                Tvx = Tvxp
        else:
            # if Tvx is adequately close to its trimmed version,
            # let's go ahead and use the simple transform
            Tvx = Tvxp
        if not Tvx[-1, -1]:
            raise ValueError("This operation only makes in-plane rotations. "\
                             "EG you cannot use the sagittal transform for "\
                             "an image in the coronal plane.")
        if (Tvx == np.identity(3)).all():
            print "Already in place, not transforming"
            return
        # this is for simple mixing of indices
        Tvx_abs = np.abs(Tvx)
        r0 = np.array([self.x0, self.y0, self.z0])
        dvx_0 = np.array([self.isize, self.jsize, self.ksize])
        # mix up the voxel sizes
        dvx_1 = np.dot(Tvx_abs, dvx_0)
        (self.isize, self.jsize, self.ksize) = dvx_1

        dim_sizes = np.array(self.shape[-3:][::-1])
        # solve for (i,j,k) where Tr*(i,j,k)^T + r0 = (0,0,0)
        # columns are i,j,k space, so scale columns by vox sizes
        vx_0 = np.linalg.solve(Tr * dvx_0, -r0)
        # transform the voxels --
        # can normalize to {0..I-1},{0..J-1},{0..K-1} due to periodicity
        vx_1 = (np.dot(Tvx, vx_0) + dim_sizes) % dim_sizes
        r0_prime = -np.dot(new_mapping * dvx_1, vx_1)
        (self.x0, self.y0, self.z0) = r0_prime
        if self.shape[-1] != self.shape[-2]:
            func = compose_xform(Tvx, view=False, square=False)
            if self.tdim:
                new_shape = (self.tdim, )
            else:
                new_shape = ()
            new_shape += tuple(np.dot(Tvx_abs,
                                      self.shape[::-1]).astype('i'))[::-1]
            temp = func(self[:])
            self.resize(new_shape)
            self[:] = temp.copy()
            del temp
        else:
            func = compose_xform(Tvx, view=False)
            func(self[:])
        self.orientation_xform = Quaternion(M=new_mapping)