Beispiel #1
0
    def compute(self):
        import bni.gridding.Kaiser2D_utils as kaiser2D

        self.log.debug("Start CG SENSE 2D")
        # get port and widget inputs
        data = self.getData('data').astype(np.complex64, copy=False)
        coords = self.getData('coords').astype(np.float32, copy=False)
        weights = self.getData('weights').astype(np.float32, copy=False)

        mtx_original = self.getVal('mtx')
        iterations = self.getVal('iterations')
        step = self.getVal('step')
        oversampling_ratio = self.getVal('oversampling ratio')

        # for a single iteration step use the csm stored in the out port
        if step and (self.getData('oversampled CSM') is not None):
            csm = self.getData('oversampled CSM')
        else:
            csm = self.getData('coil sensitivity')
        if csm is not None:
            csm = csm.astype(np.complex64, copy=False)

        # oversampling: Oversample at the beginning and crop at the end
        mtx = np.int(mtx_original * oversampling_ratio)
        if mtx % 2:
            mtx += 1
        if oversampling_ratio > 1:
            mtx_min = np.int((mtx - mtx_original) / 2)
            mtx_max = mtx_min + mtx_original
        else:
            mtx_min = 0
            mtx_max = mtx

        # data dimensions
        nr_points = data.shape[-1]
        nr_arms = data.shape[-2]
        nr_coils = data.shape[0]
        if data.ndim == 3:
            extra_dim1 = 1
            extra_dim2 = 1
            data.shape = [nr_coils, extra_dim2, extra_dim1, nr_arms, nr_points]
        elif data.ndim == 4:
            extra_dim1 = data.shape[-3]
            extra_dim2 = 1
            data.shape = [nr_coils, extra_dim2, extra_dim1, nr_arms, nr_points]
        elif data.ndim == 5:
            extra_dim1 = data.shape[-3]
            extra_dim2 = data.shape[-4]
        elif data.ndim > 5:
            self.log.warn("Not implemented yet")
        out_dims_grid = [nr_coils, extra_dim2, extra_dim1, mtx, nr_arms, nr_points]
        out_dims_degrid = [nr_coils, extra_dim2, extra_dim1, nr_arms, nr_points]
        out_dims_fft = [nr_coils, extra_dim2, extra_dim1, mtx, mtx]
        iterations_shape = [extra_dim2, extra_dim1, mtx, mtx]

        # coords dimensions: (add 1 dimension as they could have another dimension for golden angle dynamics
        if coords.ndim == 3:
            coords.shape = [1, nr_arms, nr_points, 2]
            weights.shape = [1, nr_arms, nr_points]

        # output including all iterations
        x_iterations = np.zeros([iterations, extra_dim2, extra_dim1, mtx_original, mtx_original], dtype=np.complex64)
        if step and (iterations > 1):
            previous_iterations = self.getData('x iterations')
            previous_iterations.shape = [iterations - 1, extra_dim2, extra_dim1, mtx_original, mtx_original]
            x_iterations[:-1, :, :, :, :] = previous_iterations

        # pre-calculate Kaiser-Bessel kernel
        self.log.debug("Calculate kernel")
        kernel_table_size = 800
        kernel = kaiser2D.kaiserbessel_kernel(kernel_table_size, oversampling_ratio)

        # pre-calculate the rolloff for the spatial domain
        roll = kaiser2D.rolloff2D(mtx, kernel)

        # for a single iteration step use the oversampled csm and intermediate results stored in outports
        if step and (self.getData('d') is not None):
            self.log.debug("Save some time and use the previously determined csm stored in the cropped CSM outport.")
        else:  # this is the normal path (not single iteration step)
            # grid to create images that are corrupted by
            # aliasing due to undersampling.  If the k-space data have an
            # auto-calibration region, then this can be used to generate B1 maps.
            self.log.debug("Grid undersampled data")
            gridded_kspace = kaiser2D.grid2D(data, coords, weights, kernel, out_dims_grid)
            # FFT
            image_domain = kaiser2D.fft2D(gridded_kspace, dir=0, out_dims_fft=out_dims_fft)
            # rolloff
            image_domain *= roll

            # calculate auto-calibration B1 maps
            if csm is None:
                self.log.debug("Generating autocalibrated B1 maps...")
                # parameters from UI
                UI_width = self.getVal('Autocalibration Width (%)')
                UI_taper = self.getVal('Autocalibration Taper (%)')
                UI_mask_floor = self.getVal('Mask Floor (% of max mag)')
                UI_average_csm = self.getVal('Dynamic data - average all dynamics for csm')
                csm = kaiser2D.autocalibrationB1Maps2D(image_domain, taper=UI_taper, width=UI_width, mask_floor=UI_mask_floor, average_csm=UI_average_csm)
            else:
                # make sure input csm and data are the same mtx size.
                # Assuming the FOV was the same: zero-fill in k-space
                if csm.ndim != 5:
                    self.log.debug("Reshape imported csm")
                    csm.shape = [nr_coils, extra_dim2, extra_dim1, csm.shape[-2], csm.shape[-1]]
                if csm.shape[-1] != mtx:
                    self.log.debug("Interpolate csm to oversampled matrix size")
                    csm_oversampled_mtx = np.int(csm.shape[-1] * oversampling_ratio)
                    if csm_oversampled_mtx % 2:
                        csm_oversampled_mtx += 1
                    out_dims_oversampled_image_domain = [nr_coils, extra_dim2, extra_dim1, csm_oversampled_mtx, csm_oversampled_mtx]
                    csm = kaiser2D.fft2D(csm, dir=1, out_dims_fft=out_dims_oversampled_image_domain)
                    csm = kaiser2D.fft2D(csm, dir=0, out_dims_fft=out_dims_fft)
            self.setData('oversampled CSM', csm)
            self.setData('cropped CSM', csm[..., mtx_min:mtx_max, mtx_min:mtx_max])

        # keep a conjugate csm set on hand
        csm_conj = np.conj(csm)

        # Iteration 1:
        if step and (self.getData('d') is not None):
            self.log.debug("\tSENSE Iteration: " + str(iterations))
            # make sure the loop doesn't start if only one step is needed
            iterations = 0

            # Get the data from the last execution of this node for an
            # additional single iteration.
            d = self.getData('d').copy()
            r = self.getData('r').copy()
            x = self.getData('x').copy()

            # A
            Ad = csm * d  # add coil phase
            Ad *= roll  # pre-rolloff for degrid convolution
            Ad = kaiser2D.fft2D(Ad, dir=1)
            Ad = kaiser2D.degrid2D(Ad, coords, kernel, out_dims_degrid)
            Ad = kaiser2D.grid2D(Ad, coords, weights, kernel, out_dims_grid)
            Ad = kaiser2D.fft2D(Ad, dir=0)
            Ad *= roll
            Ad = csm_conj * Ad  # broadcast multiply to remove coil phase
            Ad = Ad.sum(axis=0)  # assume the coil dim is the first
        else:
            self.log.debug("\tSENSE Iteration: 1")
            # calculate initial conditions
            # d_0
            d_0 = csm_conj * image_domain  # broadcast multiply to remove coil phase
            d_0 = d_0.sum(axis=0)  # assume the coil dim is the first

            # Ad_0:
            #   degrid -> grid (loop over coils)
            Ad_0 = csm * d_0  # add coil phase
            Ad_0 *= roll  # pre-rolloff for degrid convolution
            Ad_0 = kaiser2D.fft2D(Ad_0, dir=1)
            Ad_0 = kaiser2D.degrid2D(Ad_0, coords, kernel, out_dims_degrid)
            Ad_0 = kaiser2D.grid2D(Ad_0, coords, weights, kernel, out_dims_grid)
            Ad_0 = kaiser2D.fft2D(Ad_0, dir=0)
            Ad_0 *= roll
            Ad_0 = csm_conj * Ad_0  # broadcast multiply to remove coil phase
            Ad_0 = Ad_0.sum(axis=0)  # assume the coil dim is the first

            # use the initial conditions for the first iter
            r = d = d_0
            x = np.zeros_like(d)
            Ad = Ad_0

        # CG - iter 1 or step
        d_last, r_last, x_last = self.do_cg(d, r, x, Ad)

        current_iteration = x_last.copy()
        current_iteration.shape = iterations_shape
        if step:
            x_iterations[-1, :, :, :, :] = current_iteration[..., mtx_min:mtx_max, mtx_min:mtx_max]
        else:
            x_iterations[0, :, :, :, :] = current_iteration[..., mtx_min:mtx_max, mtx_min:mtx_max]

        # Iterations >1:
        for i in range(iterations - 1):
            self.log.debug("\tSENSE Iteration: " + str(i + 2))

            # input the result of the last iter
            d = d_last
            r = r_last
            x = x_last

            # A
            Ad = csm * d  # add coil phase
            Ad *= roll  # pre-rolloff for degrid convolution
            Ad = kaiser2D.fft2D(Ad, dir=1)
            Ad = kaiser2D.degrid2D(Ad, coords, kernel, out_dims_degrid)
            Ad = kaiser2D.grid2D(Ad, coords, weights, kernel, out_dims_grid)
            Ad = kaiser2D.fft2D(Ad, dir=0)
            Ad *= roll
            Ad = csm_conj * Ad  # broadcast multiply to remove coil phase
            Ad = Ad.sum(axis=0)  # assume the coil dim is the first
            # CG
            d_last, r_last, x_last = self.do_cg(d, r, x, Ad)

            current_iteration = x_last.copy()
            current_iteration.shape = iterations_shape
            x_iterations[i + 1, :, :, :, :] = current_iteration[..., mtx_min:mtx_max, mtx_min:mtx_max]

        # return the final image
        self.setData('d', d_last)
        self.setData('r', r_last)
        self.setData('x', x_last)
        self.setData('out', np.squeeze(current_iteration[..., mtx_min:mtx_max, mtx_min:mtx_max]))
        self.setData('x iterations', np.squeeze(x_iterations))

        return 0
Beispiel #2
0
    def compute(self):

        import numpy as np
        import bni.gridding.Kaiser2D_utils as kaiser2D

        # get port and widget inputs
        coords = self.getData('coords').astype(np.float32, copy=False)
        data = self.getData('data').astype(np.complex64, copy=False)
        oversampling_ratio = self.getVal('oversampling ratio')
        
        # Determine matrix size before and after oversampling
        mtx_original = data.shape[-1]
        mtx = np.int(mtx_original * oversampling_ratio)
        if mtx%2:
            mtx+=1
        if oversampling_ratio > 1:
            mtx_min = np.int((mtx-mtx_original)/2)
            mtx_max = mtx_min + mtx_original
        else:
            mtx_min = 0
            mtx_max = mtx
        
        # data dimensions
        nr_points = coords.shape[-2]
        nr_arms = coords.shape[-3]
        if data.ndim == 2:
            nr_coils = 1
            extra_dim1 = 1
            extra_dim2 = 1
            data.shape = [nr_coils,extra_dim2,extra_dim1,mtx_original,mtx_original]
        elif data.ndim == 3:
            nr_coils = data.shape[0]
            extra_dim1 = 1
            extra_dim2 = 1
            data.shape = [nr_coils,extra_dim2,extra_dim1,mtx_original,mtx_original]
        elif data.ndim == 4:
            nr_coils = data.shape[0]
            extra_dim1 = data.shape[-3]
            extra_dim2 = 1
            data.shape = [nr_coils,extra_dim2,extra_dim1,mtx_original,mtx_original]
        elif data.ndim == 5:
            nr_coils = data.shape[0]
            extra_dim1 = data.shape[-3]
            extra_dim2 = data.shape[-4]
        elif data.ndim > 5:
            self.log.warn("Not implemented yet")
        out_dims_degrid = [nr_coils, extra_dim2, extra_dim1, nr_arms, nr_points]
        out_dims_fft = [nr_coils, extra_dim2, extra_dim1, mtx, mtx]

        # coords dimensions: (add 1 dimension as they could have another dimension for golden angle dynamics
        if coords.ndim == 3:
            coords.shape = [1,nr_arms,nr_points,2]

        # pre-calculate Kaiser-Bessel kernel
        kernel_table_size = 800
        kernel = kaiser2D.kaiserbessel_kernel( kernel_table_size, oversampling_ratio)
        
        # pre-calculate the rolloff for the spatial domain
        roll = kaiser2D.rolloff2D(mtx, kernel)

        # perform rolloff correction
        rolloff_corrected_data = data * roll[mtx_min:mtx_max,mtx_min:mtx_max]
    
        # inverse-FFT with zero-interpolation to oversampled k-space
        oversampled_kspace = kaiser2D.fft2D(rolloff_corrected_data, dir=1, out_dims_fft=out_dims_fft)
   
        out = kaiser2D.degrid2D(oversampled_kspace, coords, kernel, out_dims_degrid)
        self.setData('out', out.squeeze())
 
        return(0)
Beispiel #3
0
    def compute(self):
        import bni.gridding.Kaiser2D_utils as kaiser2D
        
        self.log.debug("Start CG SENSE 2D")
        # get port and widget inputs
        data = self.getData('data').astype(np.complex64, copy=False)
        coords = self.getData('coords').astype(np.float32, copy=False)
        weights = self.getData('weights').astype(np.float32, copy=False)
        
        mtx_original = self.getVal('mtx')
        iterations = self.getVal('iterations')
        step = self.getVal('step')
        oversampling_ratio = self.getVal('oversampling ratio')
        number_threads = self.getVal('number of threads')
        GA = self.getVal('Golden Angle - combine dynamics before gridding')
        
        # for a single iteration step use the csm stored in the out port
        if step and (self.getData('oversampled CSM') is not None):
            csm = self.getData('oversampled CSM')
        else:
            csm = self.getData('coil sensitivity')
        if csm is not None:
            csm = csm.astype(np.complex64, copy=False)
        
        # oversampling: Oversample at the beginning and crop at the end
        mtx = np.int(np.around(mtx_original * oversampling_ratio))
        if mtx%2:
            mtx+=1
        if oversampling_ratio > 1:
            mtx_min = np.int(np.around((mtx-mtx_original)/2))
            mtx_max = mtx_min + mtx_original
        else:
            mtx_min = 0
            mtx_max = mtx
                
        # data dimensions
        nr_points = data.shape[-1]
        nr_arms = data.shape[-2]
        nr_coils = data.shape[0]
        if data.ndim == 3:
            extra_dim1 = 1
            extra_dim2 = 1
            data.shape = [nr_coils,extra_dim2,extra_dim1,nr_arms,nr_points]
        elif data.ndim == 4:
            extra_dim1 = data.shape[-3]
            extra_dim2 = 1
            data.shape = [nr_coils,extra_dim2,extra_dim1,nr_arms,nr_points]
        elif data.ndim == 5:
            extra_dim1 = data.shape[-3]
            extra_dim2 = data.shape[-4]
        elif data.ndim > 5:
            self.log.warn("Not implemented yet")
        out_dims_grid = [nr_coils, extra_dim2, extra_dim1, mtx, nr_arms, nr_points]
        out_dims_degrid = [nr_coils, extra_dim2, extra_dim1, nr_arms, nr_points]
        out_dims_fft = [nr_coils, extra_dim2, extra_dim1, mtx, mtx]
        iterations_shape = [extra_dim2, extra_dim1, mtx, mtx]

        # coords dimensions: (add 1 dimension as they could have another dimension for golden angle dynamics
        if coords.ndim == 3:
            coords.shape = [1,nr_arms,nr_points,2]
            weights.shape = [1,nr_arms,nr_points]

        # output including all iterations
        x_iterations = np.zeros([iterations,extra_dim2,extra_dim1,mtx_original,mtx_original],dtype=np.complex64)
        if step and (iterations > 1):
            previous_iterations = self.getData('x iterations')
            previous_iterations.shape = [iterations-1,extra_dim2, extra_dim1, mtx_original, mtx_original]
            x_iterations[:-1,:,:,:,:] = previous_iterations

        # pre-calculate Kaiser-Bessel kernel
        self.log.debug("Calculate kernel")
        kernel_table_size = 800
        kernel = kaiser2D.kaiserbessel_kernel( kernel_table_size, oversampling_ratio)
        
        # pre-calculate the rolloff for the spatial domain
        
        roll = kaiser2D.rolloff2D(mtx, kernel)

        # for a single iteration step use the oversampled csm and intermediate results stored in outports
        if step and (self.getData('d') is not None):
            self.log.debug("Save some time and use the previously determined csm stored in the cropped CSM outport.")
        elif GA: #combine data from GA dynamics before gridding, use code from VirtualChannels_GPI.py
            # grid images for each phase - needs to be done at some point, not really here for csm though.
            self.log.debug("Grid undersampled data")
            gridded_kspace = kaiser2D.grid2D(data, coords, weights, kernel, out_dims_grid, number_threads=number_threads)
            # FFT
            image_domain = kaiser2D.fft2D(gridded_kspace, dir=0, out_dims_fft=out_dims_fft)
            # rolloff
            image_domain *= roll
            
            twoD_or_threeD = coords.shape[-1]
            # parameters from UI
            UI_width = self.getVal('Autocalibration Width (%)')
            UI_taper = self.getVal('Autocalibration Taper (%)')
            UI_mask_floor = self.getVal('Mask Floor (% of max mag)')
            mask_dilation = self.getVal('Autocalibration mask dilation [pixels]')
            UI_average_csm = self.getVal('Dynamic data - average all dynamics for csm')
            numiter = self.getVal('Autocalibration SDC Iterations')
            original_csm_mtx = np.int(0.01 * UI_width * mtx_original)
            
            is_GoldenAngle_data = True
            nr_arms_csm = self.getVal('# golden angle dynamics for csm')
            nr_all_arms_csm = extra_dim1 * nr_arms
            extra_dim1_csm = 1
            
            # coords dimensions: (add 1 dimension as they could have another dimension for golden angle dynamics
            if coords.ndim == 3:
                coords.shape = [1,nr_arms,nr_points,twoD_or_threeD]
            
            # create low resolution csm
            # cropping the data will make gridding and FFT much faster
            magnitude_one_interleave = np.zeros(nr_points)
            for x in range(nr_points):
                magnitude_one_interleave[x] = np.sqrt( coords[0,0,x,0]**2 + coords[0,0,x,1]**2)
            within_csm_width_radius = magnitude_one_interleave[:] < (0.01 * UI_width * 0.5) # for BNI spirals should be 0.45 instead of 0.5
            nr_points_csm_width = within_csm_width_radius.sum()
            # in case of radial trajectory, it doesn't start at zero..
            found_start_point = 0
            found_end_point = 0
            for x in range(nr_points):
                if ((not found_start_point) and (within_csm_width_radius[x])):
                    found_start_point = 1
                    start_point = x
                if ((not found_end_point) and (found_start_point) and (not within_csm_width_radius[x])):
                    found_end_point = 1
                    end_point = x
            if not found_end_point:
                end_point = nr_points
            self.log.node("Start and end points in interleave are: "+str(start_point)+" and "+str(end_point)+" leading to "+str(nr_points_csm_width)+" points for csm.")
            
            arm_counter = 0
            extra_dim1_counter = 0
            arm_with_data_counter = 0
            while (arm_with_data_counter < nr_arms_csm and extra_dim1_counter < extra_dim1):
                if (coords[extra_dim1_counter, arm_counter,0,0] != coords[extra_dim1_counter, arm_counter,-1,0]): #only equal when no data in this interleave during resorting
                    arm_with_data_counter += 1
                arm_counter += 1
                if arm_counter == nr_arms:
                    arm_counter = 0
                    extra_dim1_counter += 1
            self.log.node("Found "+str(arm_with_data_counter)+" arms, and was looking for "+str(nr_arms_csm)+" from a total of "+str(nr_all_arms_csm)+" arms.")
            
            csm_data = np.zeros([nr_coils,extra_dim2,extra_dim1_csm,arm_with_data_counter,nr_points_csm_width], dtype=data.dtype)
            csm_coords = np.zeros([1,arm_with_data_counter,nr_points_csm_width,twoD_or_threeD], dtype=coords.dtype)
            
            arm_counter = 0
            extra_dim1_counter = 0
            arm_with_data_counter = 0
            while (arm_with_data_counter < nr_arms_csm and extra_dim1_counter < extra_dim1):
                if (coords[extra_dim1_counter, arm_counter,0,0] != coords[extra_dim1_counter, arm_counter,-1,0]): #only equal when no data in this interleave during resorting
                    csm_data[:,:,0,arm_with_data_counter,:] = data[:,:,extra_dim1_counter,arm_counter,start_point:end_point]
                    csm_coords[0,arm_with_data_counter,:,:] = coords[extra_dim1_counter,arm_counter,start_point:end_point,:]
                    arm_with_data_counter += 1
                arm_counter += 1
                if arm_counter == nr_arms:
                    arm_counter = 0
                    extra_dim1_counter += 1
            self.log.node("Found "+str(arm_with_data_counter)+" arms, and was looking for "+str(nr_arms_csm)+" from a total of "+str(nr_all_arms_csm)+" arms.")
            
            # now set the dimension lists
            out_dims_grid_csm = [nr_coils, extra_dim2, extra_dim1_csm, mtx, arm_with_data_counter, nr_points_csm_width]
            out_dims_fft_csm = [nr_coils, extra_dim2, extra_dim1_csm, mtx, mtx]
            
            # generate SDC based on number of arms and nr of points being used for csm
            import core.gridding.sdc as sd
            #csm_weights = sd.twod_sdcsp(csm_coords.squeeze().astype(np.float64), numiter, 0.01 * UI_taper, mtx)
            cmtxdim = np.array([mtx,mtx],dtype=np.int64)
            wates = np.ones((arm_with_data_counter * nr_points_csm_width), dtype=np.float64)
            coords_for_sdc = csm_coords.astype(np.float64)
            coords_for_sdc.shape = [arm_with_data_counter * nr_points_csm_width, twoD_or_threeD]
            csm_weights = sd.twod_sdc(coords_for_sdc, wates, cmtxdim, numiter, 0.01 * UI_taper )
            csm_weights.shape = [1,arm_with_data_counter,nr_points_csm_width]
            
            # Grid
            gridded_kspace_csm = kaiser2D.grid2D(csm_data, csm_coords, csm_weights.astype(np.float32), kernel, out_dims_grid_csm, number_threads=number_threads)
            image_domain_csm = kaiser2D.fft2D(gridded_kspace_csm, dir=0, out_dims_fft=out_dims_fft_csm)
            # rolloff
            image_domain_csm *= roll
            # # crop to original matrix size
            # csm = image_domain_csm[...,mtx_min:mtx_max,mtx_min:mtx_max]
            # normalize by rms (better would be to use a whole body coil image
            csm_rms = np.sqrt(np.sum(np.abs(image_domain_csm)**2, axis=0))
            image_domain_csm = image_domain_csm / csm_rms
            # zero out points that are below mask threshold
            thresh = 0.01 * UI_mask_floor * csm_rms.max()
            mask = csm_rms > thresh
            # use scipy library to grow mask and fill holes.
            from scipy import ndimage
            mask.shape = [mtx,mtx]
            mask = ndimage.morphology.binary_dilation(mask, iterations=mask_dilation)
            mask = ndimage.binary_fill_holes(mask)
            
            image_domain_csm *= mask
            if extra_dim1 > 1:
                csm = np.zeros([nr_coils, extra_dim2, extra_dim1, mtx, mtx], dtype=image_domain_csm.dtype)
                for extra_dim1_counter in range(extra_dim1):
                    csm[:,:,extra_dim1_counter,:,:]=image_domain_csm[:,:,0,:,:]
            else:
                csm = image_domain_csm
            self.setData('oversampled CSM', csm)
            self.setData('cropped CSM', csm[...,mtx_min:mtx_max,mtx_min:mtx_max])
            
        else: # this is the normal path (not single iteration step)
            # grid to create images that are corrupted by
            # aliasing due to undersampling.  If the k-space data have an
            # auto-calibration region, then this can be used to generate B1 maps.
            self.log.debug("Grid undersampled data")
            gridded_kspace = kaiser2D.grid2D(data, coords, weights, kernel, out_dims_grid, number_threads=number_threads)
            # FFT
            image_domain = kaiser2D.fft2D(gridded_kspace, dir=0, out_dims_fft=out_dims_fft)
            # rolloff
            image_domain *= roll

            # calculate auto-calibration B1 maps
            if csm is None:
                self.log.debug("Generating autocalibrated B1 maps...")
                # parameters from UI
                UI_width = self.getVal('Autocalibration Width (%)')
                UI_taper = self.getVal('Autocalibration Taper (%)')
                UI_mask_floor = self.getVal('Mask Floor (% of max mag)')
                UI_average_csm = self.getVal('Dynamic data - average all dynamics for csm')
                csm = kaiser2D.autocalibrationB1Maps2D(image_domain, taper=UI_taper, width=UI_width, mask_floor=UI_mask_floor, average_csm=UI_average_csm)
            else:
                # make sure input csm and data are the same mtx size.
                # Assuming the FOV was the same: zero-fill in k-space
                if csm.ndim != 5:
                    self.log.debug("Reshape imported csm")
                    csm.shape = [nr_coils,extra_dim2,extra_dim1,csm.shape[-2],csm.shape[-1]]
                if csm.shape[-1] != mtx:
                    self.log.debug("Interpolate csm to oversampled matrix size")
                    csm_oversampled_mtx = np.int(csm.shape[-1] * oversampling_ratio)
                    if csm_oversampled_mtx%2:
                        csm_oversampled_mtx+=1
                    out_dims_oversampled_image_domain = [nr_coils, extra_dim2, extra_dim1, csm_oversampled_mtx, csm_oversampled_mtx]
                    csm = kaiser2D.fft2D(csm, dir=1, out_dims_fft=out_dims_oversampled_image_domain)
                    csm = kaiser2D.fft2D(csm, dir=0, out_dims_fft=out_dims_fft)
            self.setData('oversampled CSM', csm)
            self.setData('cropped CSM', csm[...,mtx_min:mtx_max,mtx_min:mtx_max])

        # keep a conjugate csm set on hand
        csm_conj = np.conj(csm)

        ## Iteration 1:
        if step and (self.getData('d') is not None):
            self.log.debug("\tSENSE Iteration: " + str(iterations))
            # make sure the loop doesn't start if only one step is needed
            iterations = 0

            # Get the data from the last execution of this node for an
            # additional single iteration.
            d = self.getData('d').copy()
            r = self.getData('r').copy()
            x = self.getData('x').copy()

            # A
            Ad = csm * d # add coil phase
            Ad *= roll # pre-rolloff for degrid convolution
            Ad = kaiser2D.fft2D(Ad, dir=1)
            Ad = kaiser2D.degrid2D(Ad, coords, kernel, out_dims_degrid, number_threads=number_threads, oversampling_ratio = oversampling_ratio)
            Ad = kaiser2D.grid2D(Ad, coords, weights, kernel, out_dims_grid, number_threads=number_threads)
            Ad = kaiser2D.fft2D(Ad, dir=0)
            Ad *= roll
            Ad = csm_conj * Ad # broadcast multiply to remove coil phase
            Ad = Ad.sum(axis=0) # assume the coil dim is the first
        else:
            self.log.debug("\tSENSE Iteration: 1")
            # calculate initial conditions
            # d_0
            d_0 = csm_conj * image_domain # broadcast multiply to remove coil phase
            d_0 = d_0.sum(axis=0) # assume the coil dim is the first

            # Ad_0:
            #   degrid -> grid (loop over coils)
            Ad_0 = csm * d_0 # add coil phase
            Ad_0 *= roll # pre-rolloff for degrid convolution
            Ad_0 = kaiser2D.fft2D(Ad_0, dir=1)
            Ad_0 = kaiser2D.degrid2D(Ad_0, coords, kernel, out_dims_degrid, number_threads=number_threads, oversampling_ratio = oversampling_ratio)
            Ad_0 = kaiser2D.grid2D(Ad_0, coords, weights, kernel, out_dims_grid, number_threads=number_threads)
            Ad_0 = kaiser2D.fft2D(Ad_0, dir=0)
            Ad_0 *= roll
            Ad_0 = csm_conj * Ad_0 # broadcast multiply to remove coil phase
            Ad_0 = Ad_0.sum(axis=0) # assume the coil dim is the first
            
            # use the initial conditions for the first iter
            r = d = d_0
            x = np.zeros_like(d)
            Ad = Ad_0

        # CG - iter 1 or step
        d_last, r_last, x_last = self.do_cg(d, r, x, Ad)
        
        current_iteration = x_last.copy()
        current_iteration.shape = iterations_shape
        if step:
            x_iterations[-1,:,:,:,:] = current_iteration[...,mtx_min:mtx_max,mtx_min:mtx_max]
        else:
            x_iterations[0,:,:,:,:] = current_iteration[...,mtx_min:mtx_max,mtx_min:mtx_max]

        ## Iterations >1:
        for i in range(iterations-1):
            self.log.debug("\tSENSE Iteration: " + str(i+2))

            # input the result of the last iter
            d = d_last
            r = r_last
            x = x_last

            # A
            Ad = csm * d # add coil phase
            Ad *= roll # pre-rolloff for degrid convolution
            Ad = kaiser2D.fft2D(Ad, dir=1)
            Ad = kaiser2D.degrid2D(Ad, coords, kernel, out_dims_degrid, number_threads=number_threads, oversampling_ratio = oversampling_ratio)
            Ad = kaiser2D.grid2D(Ad, coords, weights, kernel, out_dims_grid, number_threads=number_threads)
            Ad = kaiser2D.fft2D(Ad, dir=0)
            Ad *= roll
            Ad = csm_conj * Ad # broadcast multiply to remove coil phase
            Ad = Ad.sum(axis=0) # assume the coil dim is the first
            # CG
            d_last, r_last, x_last = self.do_cg(d, r, x, Ad)

            current_iteration = x_last.copy()
            current_iteration.shape = iterations_shape
            x_iterations[i+1,:,:,:,:] = current_iteration[..., mtx_min:mtx_max, mtx_min:mtx_max]

        # return the final image     
        self.setData('d', d_last)
        self.setData('r', r_last)
        self.setData('x', x_last)
        self.setData('out', np.squeeze(current_iteration[..., mtx_min:mtx_max, mtx_min:mtx_max]))
        self.setData('x iterations', np.squeeze(x_iterations))

        return 0