Example #1
0
    def __init__(self, image, bbox=None, mask=False,
                 grid_spacing=None, interpolation_order=3):
        """
        Creates a new SampledVolumeSlicer
        
        Parameters
        ----------
        image : a NIPY Image
            The image to slice
        bbox : iterable (optional)
            The {x,y,z} limits of the enrounding volume box. If None, then
            slices planes in the natural box of the image. This argument
            is useful for overlaying an image onto another image's volume box
        mask : bool or ndarray (optional)
            A binary mask, with same shape as image, with unmasked points
            marked as True (opposite of MaskedArray convention)
        grid_spacing : iterable (optional)
            New grid spacing for the sliced planes. If None, then the
            natural voxel spacing is used.
        """
        
        xyz_image = ni_api.Image(
            np.asarray(image),
            image.coordmap.reordered_range(xipy_ras)
            )
##                                  reorder_output(image.coordmap, 'xyz'))
        self.coordmap = xyz_image.coordmap
        self.raw_image = xyz_image
        nvox = np.product(self.raw_image.shape)
        # if the volume array of the map is more than 100mb in memory,
        # better use a memmap
        self._use_mmap = nvox*8 > 100e6
        self.interpolator = ImageInterpolator(xyz_image,
                                              order=interpolation_order,
                                              use_mmap=self._use_mmap)
##         if mask is True:
##             mask = compute_mask(np.asarray(self.raw_image), cc=0, m=.1, M=.999)
        if type(mask) is np.ndarray:
            self.update_mask(mask, positive_mask=True)
        else:
            self._masking = False
            self.raw_mask = None
            
        if grid_spacing is None:
            self.grid_spacing = list(vu.voxel_size(image.affine))
        else:
            self.grid_spacing = grid_spacing
        if bbox is None:
            self._nominal_bbox = vu.world_limits(xyz_image)
        else:
            self._nominal_bbox = bbox

        # defines {x,y,z}shape and {x,y,z}grid
        self._define_grids()
Example #2
0
    def update_mask(self, mask, positive_mask=True):
        cmap = self.coordmap
        if not positive_mask:
            # IE, if this is a MaskedArray type mask
            mask = np.logical_not(mask)
##         fmask = np.array([ndimage.binary_fill_holes(m) for m in mask], 'd')
        self.m_interpolator = ImageInterpolator(ni_api.Image(mask, cmap),
                                                order=3,
                                                use_mmap=self._use_mmap)
        self.raw_mask = mask
        self._masking = True
Example #3
0
def resample(image, target, mapping, shape, order=3, **interp_kws):
    """
    Resample an image to a target CoordinateMap with a "world-to-world" mapping
    and spline interpolation of a given order.

    Here, "world-to-world" refers to the fact that mapping should be
    a callable that takes a physical coordinate in "target"
    and gives a physical coordinate in "image". 

    Parameters
    ----------
    image : Image instance that is to be resampled
    target :target CoordinateMap for output image
    mapping : transformation from target.function_range
               to image.coordmap.function_range, i.e. 'world-to-world mapping'
               Can be specified in three ways: a callable, a
               tuple (A, b) representing the mapping y=dot(A,x)+b
               or a representation of this in homogeneous coordinates. 
    shape : shape of output array, in target.function_domain
    order : what order of interpolation to use in `scipy.ndimage`
    interp_kws : keyword arguments for ndimage interpolator routine

    Returns
    -------
    output : Image instance with interpolated data and output.coordmap == target
                  
    """

    if not callable(mapping):
        if type(mapping) is type(()):
            A, b = mapping
            ndimout = b.shape[0]
            ndimin = A.shape[1]
            mapping  = np.zeros((ndimout+1, ndimin+1))
            mapping[:ndimout,:ndimin] = A
            mapping[:ndimout,-1] = b
            mapping[-1,-1] = 1.

     # image world to target world mapping

        TW2IW = AffineTransform(target.function_range, image.coordmap.function_range, mapping)
    else:
        TW2IW = CoordinateMap(mapping, target.function_range, image.coordmap.function_range)

    function_domain = target.function_domain
    function_range = image.coordmap.function_range

    # target voxel to image world mapping
    TV2IW = compose(TW2IW, target)

    # CoordinateMap describing mapping from target voxel to
    # image world coordinates

    if not isinstance(TV2IW, AffineTransform):
        # interpolator evaluates image at values image.coordmap.function_range,
        # i.e. physical coordinates rather than voxel coordinates
        grid = ArrayCoordMap.from_shape(TV2IW, shape)
        interp = ImageInterpolator(image, order=order)
        idata = interp.evaluate(grid.transposed_values, **interp_kws)
        del(interp)
    else:
        TV2IV = compose(image.coordmap.inverse(), TV2IW)
        if isinstance(TV2IV, AffineTransform):
            A, b = affines.to_matrix_vector(TV2IV.affine)
            data = np.asarray(image)
            idata = affine_transform(data, A,
                                     offset=b,
                                     output_shape=shape,
                                     output=data.dtype,
                                     order=order,
                                     **interp_kws)
        else:
            interp = ImageInterpolator(image, order=order)
            grid = ArrayCoordMap.from_shape(TV2IV, shape)
            idata = interp.evaluate(grid.values, **interp_kws)
            del(interp)
            
    return Image(idata, target)
Example #4
0
class SampledVolumeSlicer(VolumeSlicerInterface):
    """
    This object cuts up an image along the axes defined by its
    CoordinateMap target space. The SampledVolumeSlicer provides slices
    through an image such that the cut planes extend across the three
    {x,y,z} planes of the target space. Each plane is sampled from the
    original image voxel array by spline interpolation.
    """

    def __init__(self, image, bbox=None, mask=False,
                 grid_spacing=None, interpolation_order=3):
        """
        Creates a new SampledVolumeSlicer
        
        Parameters
        ----------
        image : a NIPY Image
            The image to slice
        bbox : iterable (optional)
            The {x,y,z} limits of the enrounding volume box. If None, then
            slices planes in the natural box of the image. This argument
            is useful for overlaying an image onto another image's volume box
        mask : bool or ndarray (optional)
            A binary mask, with same shape as image, with unmasked points
            marked as True (opposite of MaskedArray convention)
        grid_spacing : iterable (optional)
            New grid spacing for the sliced planes. If None, then the
            natural voxel spacing is used.
        """
        
        xyz_image = ni_api.Image(
            np.asarray(image),
            image.coordmap.reordered_range(xipy_ras)
            )
##                                  reorder_output(image.coordmap, 'xyz'))
        self.coordmap = xyz_image.coordmap
        self.raw_image = xyz_image
        nvox = np.product(self.raw_image.shape)
        # if the volume array of the map is more than 100mb in memory,
        # better use a memmap
        self._use_mmap = nvox*8 > 100e6
        self.interpolator = ImageInterpolator(xyz_image,
                                              order=interpolation_order,
                                              use_mmap=self._use_mmap)
##         if mask is True:
##             mask = compute_mask(np.asarray(self.raw_image), cc=0, m=.1, M=.999)
        if type(mask) is np.ndarray:
            self.update_mask(mask, positive_mask=True)
        else:
            self._masking = False
            self.raw_mask = None
            
        if grid_spacing is None:
            self.grid_spacing = list(vu.voxel_size(image.affine))
        else:
            self.grid_spacing = grid_spacing
        if bbox is None:
            self._nominal_bbox = vu.world_limits(xyz_image)
        else:
            self._nominal_bbox = bbox

        # defines {x,y,z}shape and {x,y,z}grid
        self._define_grids()

    def _define_grids(self):
        dx, dy, dz = self.grid_spacing
        xbox, ybox, zbox = map( lambda x: np.array(x).reshape(2,2),
                                vu.limits_to_extents(self._nominal_bbox) )
        
        # xbox is [ <ylims> , <zlims> ], so xgrid is [ypts.T, zpts.T]
        xr = np.diff(xbox)[:,0]
        xspacing = np.array([dy, dz])
        self.xshape = (xr / xspacing).astype('i')
        aff = ni_api.AffineTransform.from_start_step('ij', 'xy',
                                                     xbox[:,0], xspacing)
        self.xgrid = ni_api.ArrayCoordMap(aff, self.xshape).values

        # ybox is [ <xlims>, <zlims> ], so ygrid is [xpts.T, zpts.T]
        yr = np.diff(ybox)[:,0]
        yspacing = np.array([dx, dz])
        self.yshape = (yr / yspacing).astype('i')
        aff = ni_api.AffineTransform.from_start_step('ij', 'xy',
                                                     ybox[:,0], yspacing)
        self.ygrid = ni_api.ArrayCoordMap(aff, self.yshape).values

        # zbox is [ <xlims>, <ylims> ], so zgrid is [xpts.T, ypts.T]
        zr = np.diff(zbox)[:,0]
        zspacing = np.array([dx, dy])
        self.zshape = (zr / zspacing).astype('i')
        aff = ni_api.AffineTransform.from_start_step('ij', 'xy',
                                                     zbox[:,0], zspacing)
        self.zgrid = ni_api.ArrayCoordMap(aff, self.zshape).values

        bb_min = [b[0] for b in self._nominal_bbox]
        lx = self.yshape[0]
        ly = self.xshape[0]
        lz = self.yshape[1]
        bb_max = [bb_min[0] + dx*lx, bb_min[1] + dy*ly, bb_min[2] + dz*lz]
        self.bbox = zip(bb_min, bb_max)
        
    def update_mask(self, mask, positive_mask=True):
        cmap = self.coordmap
        if not positive_mask:
            # IE, if this is a MaskedArray type mask
            mask = np.logical_not(mask)
##         fmask = np.array([ndimage.binary_fill_holes(m) for m in mask], 'd')
        self.m_interpolator = ImageInterpolator(ni_api.Image(mask, cmap),
                                                order=3,
                                                use_mmap=self._use_mmap)
        self.raw_mask = mask
        self._masking = True

    def _update_grid_spacing(self, grid_spacing):
        self.grid_spacing = grid_spacing
        self._define_grids()

##     def update_mask_crit(self, crit, thresh):
##         self._masking = True
##         self._mcrit = crit
##         self._thresh = thresh

    def _closest_grid_pt(self, coord, ax):
        """For a given coordinate value on an axis, find the closest
        fixed grid point as defined on the complementary grid planes.
        This is to ensure consistency at the intersection of
        interpolated planes
        """
        ax_min = np.asarray(self.bbox)[ax,0]
        ax_step = self.grid_spacing[ax]
        # ax_min + n*ax_step = coord
        new_coord = round ( (coord-ax_min) / ax_step ) * ax_step + ax_min
        return new_coord
        
    def _cut_plane(self, ax, coord, **interp_kw):
        """
        For a given axis in {SAG, COR, AXI}, make a plane cut in the
        volume at the coordinate value.

        Parameters
        ----------
        ax : int
            axis label in {SAG, COR, AXI} (defined in xipy.slicing)
        coord : float
            coordinate value along this axis
        interp_kw : dict
            Keyword args for the interpolating machinery
            (ie, ndimage.map_coordinates keyword args)
            
        Returns
        _______
        plane : ndarray
            The transverse plane sampled at the grid points and fixed axis
            coordinate for the given args
        """
    
        # a little hokey
        grid_lookup = {SAG: ('xshape', 'xgrid'),
                       COR: ('yshape', 'ygrid'),
                       AXI: ('zshape', 'zgrid')}
        sname, gname= grid_lookup[ax]
        ii, jj = transverse_plane_lookup(ax)
        grid = getattr(self, gname)
        shape = getattr(self, sname)
        coords = np.empty((3, grid.shape[0]), 'd')
        coords[ax] = self._closest_grid_pt(coord, ax)
        coords[ii] = grid[:,0]; coords[jj] = grid[:,1]
        pln = self.interpolator.evaluate(coords, **interp_kw).reshape(shape)
        if self._masking:
            m_pln = self.m_interpolator.evaluate(coords, mode='constant',
                                                 cval=-10).reshape(shape)
            return np.ma.masked_where(m_pln < 0.5, pln)
        return pln

    def update_target_space(self, coreg_image):
        raise NotImplementedError('not sure how to do this yet')