def test_remove_blob_1d(self): list_1d = 0.5 * np.ones(self.size) list_1d[self.idx1:self.idx2] = 1.0 mask_1d = np.zeros_like(list_1d) mask_1d[self.idx1:self.idx2] = 1.0 list_corr = remo.remove_blob_1d(list_1d, mask_1d) nmean = np.mean(list_corr[self.idx1:self.idx2]) num = np.abs(nmean - 0.5) self.assertTrue(num < self.eps)
def generate_full_sinogram_helical_scan(index, tomo_data, num_proj, pixel_size, y_start, y_stop, pitch, scan_type="180", angles=None, flat=None, dark=None, mask=None, crop=(0, 0, 0, 0)): """ Generate a full sinogram from a helical scan dataset which is a hdf/nxs object (Ref. [1]_). Full sinogram is all 1D projection of the same slice of a sample staying inside the field of view. Parameters ---------- index : int Index of the sinogram. tomo_data : hdf object. 3D array. num_proj : int Number of projections per 180-degree. pixel_size : float Pixel size. The unit must be the same as y-position. y_start : float Y-position of the stage at the beginning of the scan. y_stop : float Y-position of the stage at the end of the scan. pitch : float The distance which the y-stage is translated in one full rotation. scan_type : {"180", "360"} Data acquired is the 180-degree type or 360-degree type [1]. angles : array_like, optional 1D array. List of angles (degree) corresponding to acquired projections. flat : array_like, optional Flat-field image used for flat-field correction. dark : array_like, optional Dark-field image used for flat-field correction. mask : array_like, optional Used for removing streak artifacts caused by blobs in the flat-field image. crop : tuple of int, optional Used for cropping images. Returns ------- sinogram : array_like 2D array. Full sinogram. list_angle : array_like 1D array. List of angles corresponding to the generated sinogram. References ---------- .. [1] https://doi.org/10.1364/OE.418448 """ (depth0, height0, width0) = tomo_data.shape (crop_top, crop_bottom, crop_left, crop_right) = crop top = crop_top bottom = height0 - crop_bottom left = crop_left right = width0 - crop_right width = right - left height = bottom - top if flat is None: flat = np.ones((height0, width0), dtype=np.float32) if dark is None: dark = np.zeros((height0, width0), dtype=np.float32) if angles is None: step_angle = 180.0 / (num_proj - 1) angles = np.arange(0, depth0) * step_angle flat_dark = flat - dark FoV = pixel_size * height y_step = pitch / (2.0 * (num_proj - 1)) if scan_type == "180": y_e = y_stop - pitch / 2.0 y_s = y_start + pitch / 2.0 else: y_e = y_stop - pitch y_s = y_start + pitch num_proj_used = int(np.floor(FoV / y_step)) - 1 y_pos = (index - 1) * pixel_size + y_s i0 = int(np.ceil((y_e - y_pos) / y_step)) if (i0 < 0) or (i0 >= depth0): raise ValueError( "Sinogram index {0} requests a projection index {1} which " "is out of the projection range [0, {2}]".format( index, i0, depth0 - 1)) if (i0 + num_proj_used) >= depth0: raise ValueError( "Sinogram index {0} requests projection-indices in the range of " "[{1}, {2}] which is out of the data range [0, {3}]".format( index, i0, i0 + num_proj_used, depth0 - 1)) sinogram = np.zeros((num_proj_used, width), dtype=np.float32) for i in range(i0, i0 + num_proj_used): j0 = (y_e + FoV - i * y_step - y_pos) / pixel_size - 1 if (j0 < 0) or (j0 >= height): raise ValueError( "Requested row index {0} of projection {1} is out of" " the range [0, {2}]".format(j0, i0, height)) j0 = np.clip(j0, 0, height - 1) jd = int(np.floor(j0)) ju = int(np.ceil(j0)) list_down = (tomo_data[i, jd + crop_top, left:right] - dark[jd + crop_top, left:right]) / flat_dark[jd + crop_top, left:right] if mask is not None: list_down = remo.remove_blob_1d(list_down, mask[jd + crop_top, left:right]) if ju != jd: list_up = (tomo_data[i, ju + crop_top, left:right] - dark[ju + crop_top, left:right]) / flat_dark[ju + crop_top, left:right] if mask is not None: list_up = remo.remove_blob_1d(list_up, mask[ju + crop_top, left:right]) sinogram[i - i0] = list_down * (ju - j0) / (ju - jd) + list_up * ( j0 - jd) / (ju - jd) else: sinogram[i - i0] = list_down list_angle = angles[i0:i0 + num_proj_used] return sinogram, list_angle
def generate_sinogram_helical_scan(index, tomo_data, num_proj, pixel_size, y_start, y_stop, pitch, scan_type="180", angles=None, flat=None, dark=None, mask=None, crop=(0, 0, 0, 0)): """ Generate a 180-degree sinogram or a 360-degree sinogram from a helical scan dataset which is a hdf/nxs object (Ref. [1]_). Parameters ---------- index : int Index of the sinogram. tomo_data : hdf object. 3D array. num_proj : int Number of projections per 180-degree. pixel_size : float Pixel size. The unit must be the same as y-position. y_start : float Y-position of the stage at the beginning of the scan. y_stop : float Y-position of the stage at the end of the scan. pitch : float The distance which the y-stage is translated in one full rotation. scan_type : {"180", "360"} One of two options: "180" for generating a 180-degree sinogram or "360" for generating a 360-degree sinogram. angles : array_like, optional 1D array. List of angles (degree) corresponding to acquired projections. flat : array_like, optional Flat-field image used for flat-field correction. dark : array_like, optional Dark-field image used for flat-field correction. mask : array_like, optional Used for removing streak artifacts caused by blobs in the flat-field image. crop : tuple of int, optional Used for cropping images. Returns ------- sinogram : array_like 2D array. 180-degree sinogram or 360-degree sinogram. list_angle : array_like 1D array. List of angles corresponding to the generated sinogram. References ---------- .. [1] https://doi.org/10.1364/OE.418448 """ max_index = calc.calculate_maximum_index(y_start, y_stop, pitch, pixel_size, scan_type) (y_s, y_e) = calc.calculate_reconstructable_height(y_start, y_stop, pitch, scan_type) if index < 0 or index > max_index: msg1 = "Requested index {0} is out of available index-range" \ " [0, {1}]\n".format(index, max_index) msg2 = "corresponding to reconstructable heights" \ " [{0}, {1}]".format(y_s, y_e) raise ValueError(msg1 + msg2) (depth0, height0, width0) = tomo_data.shape (crop_top, crop_bottom, crop_left, crop_right) = crop top = crop_top bottom = height0 - crop_bottom left = crop_left right = width0 - crop_right width = right - left height = bottom - top if flat is None: flat = np.ones((height0, width0), dtype=np.float32) if dark is None: dark = np.zeros((height0, width0), dtype=np.float32) if angles is None: step_angle = 180.0 / (num_proj - 1) angles = np.arange(0, depth0) * step_angle flat_dark = flat - dark FoV = pixel_size * height y_step = pitch / (2.0 * (num_proj - 1)) if scan_type == "180": num_proj_used = num_proj else: num_proj_used = 2 * (num_proj - 1) + 1 y_pos = (index - 1) * pixel_size + y_s i0 = int(np.ceil((y_e - y_pos) / y_step)) if (i0 < 0) or (i0 >= depth0): raise ValueError("Sinogram index {0} requests a projection index {1}" " which is out of the data range [0, {2}]".format( index, i0, depth0 - 1)) sinogram = np.zeros((num_proj_used, width), dtype=np.float32) for i in range(i0, i0 + num_proj_used): j0 = (y_e + FoV - i * y_step - y_pos) / pixel_size - 1 if (j0 < 0) or (j0 >= height): raise ValueError( "Requested row index {0} of projection {1} is out of the" " range [0, {2}]".format(j0, i0, height - 1)) j0 = np.clip(j0, 0, height - 1) jd = int(np.floor(j0)) ju = int(np.ceil(j0)) list_down = (tomo_data[i, jd + crop_top, left:right] - dark[jd + crop_top, left:right]) / flat_dark[jd + crop_top, left:right] if mask is not None: list_down = remo.remove_blob_1d(list_down, mask[jd + crop_top, left:right]) if ju != jd: list_up = (tomo_data[i, ju + crop_top, left: right] - dark[ju + crop_top, left: right]) \ / flat_dark[ju + crop_top, left: right] if mask is not None: list_up = remo.remove_blob_1d(list_up, mask[ju + crop_top, left:right]) sinogram[i - i0] = list_down * (ju - j0) / (ju - jd) + list_up * ( j0 - jd) / (ju - jd) else: sinogram[i - i0] = list_down list_angle = angles[i0:i0 + num_proj_used] return sinogram, list_angle