Ejemplo n.º 1
0
    def _saturation_correction(self, data, mask, nb_frames):
        """
        Mask pixels above a certain threshold.

        This is detector dependent. If a 2D frames was obtained by summing a series of
        frames (e.g. series measurement at P10), the threshold is multiplied
        accordingly.

        :param data: a 2D numpy array
        :param mask: a 2D numpy array of the same shape as data
        :param nb_frames: int, number of frames concatenated to obtain the 2D data array
        :return:

         - the masked data
         - the updated mask

        """
        if self.saturation_threshold is not None:
            valid.valid_ndarray((data, mask), ndim=2)

            valid.valid_item(nb_frames,
                             allowed_types=int,
                             min_excluded=0,
                             name="nb_frames")
            mask[data > self.saturation_threshold * nb_frames] = 1
            data[data > self.saturation_threshold * nb_frames] = 0
        return data, mask
Ejemplo n.º 2
0
    def _mask_gaps(self, data, mask):
        """
        Mask the gaps between sensors in the detector.

        :param data: a 2D numpy array
        :param mask: a 2D numpy array of the same shape as data
        :return:

         - the masked data
         - the updated mask

        """
        valid.valid_ndarray((data, mask),
                            ndim=2,
                            shape=self.unbinned_pixel_number,
                            fix_shape=True)

        data[:, 0:1] = 0
        data[:, -1:] = 0
        data[0:1, :] = 0
        data[-1:, :] = 0
        data[:, 1029:1041] = 0
        data[513:552, :] = 0
        data[1064:1103, :] = 0
        data[1615:1654, :] = 0

        mask[:, 0:1] = 1
        mask[:, -1:] = 1
        mask[0:1, :] = 1
        mask[-1:, :] = 1
        mask[:, 1029:1041] = 1
        mask[513:552, :] = 1
        mask[1064:1103, :] = 1
        mask[1615:1654, :] = 1
        return data, mask
Ejemplo n.º 3
0
    def _flatfield_correction(data, flatfield):
        """
        Apply flatfield correction to the data (multiplication).

        :param data: a 2D numpy ndarray
        :param flatfield: None or a 2D numpy array
        :return: the corrected data array
        """
        if flatfield is not None:
            valid.valid_ndarray((data, flatfield), ndim=2)
            return np.multiply(flatfield, data)
        return data
Ejemplo n.º 4
0
    def _background_subtraction(data, background):
        """
        Apply background subtraction to the data.

        :param data: a 2D numpy ndarray
        :param background: None or a 2D numpy array
        :return: the corrected data array
        """
        if background is not None:
            valid.valid_ndarray((data, background), ndim=2)
            return data - background
        return data
Ejemplo n.º 5
0
    def _linearity_correction(self, data):
        """
        Apply a correction to data if the detector response is not linear.

        :param data: a 2D numpy array
        :return: the corrected data array
        """
        if self.linearity_func is not None and callable(self.linearity_func):
            valid.valid_ndarray(data, ndim=2)
            data = data.astype(float)
            nby, nbx = data.shape
            return self.linearity_func(array_1d=data.flatten()).reshape(
                (nby, nbx))
        return data
Ejemplo n.º 6
0
    def _mask_gaps(self, data, mask):
        """
        Mask the gaps between sensors in the detector.

        :param data: a 2D numpy array
        :param mask: a 2D numpy array of the same shape as data
        :return:

         - the masked data
         - the updated mask

        """
        valid.valid_ndarray((data, mask),
                            ndim=2,
                            shape=self.unbinned_pixel_number,
                            fix_shape=True)

        data[:, 255:259] = 0
        data[:, 513:517] = 0
        data[:, 771:775] = 0
        data[0:257, 72:80] = 0
        data[255:259, :] = 0
        data[511:552, :] = 0
        data[804:809, :] = 0
        data[1061:1102, :] = 0
        data[1355:1359, :] = 0
        data[1611:1652, :] = 0
        data[1905:1909, :] = 0
        data[1248:1290, 478] = 0
        data[1214:1298, 481] = 0
        data[1649:1910, 620:628] = 0

        mask[:, 255:259] = 1
        mask[:, 513:517] = 1
        mask[:, 771:775] = 1
        mask[0:257, 72:80] = 1
        mask[255:259, :] = 1
        mask[511:552, :] = 1
        mask[804:809, :] = 1
        mask[1061:1102, :] = 1
        mask[1355:1359, :] = 1
        mask[1611:1652, :] = 1
        mask[1905:1909, :] = 1
        mask[1248:1290, 478] = 1
        mask[1214:1298, 481] = 1
        mask[1649:1910, 620:628] = 1
        return data, mask
Ejemplo n.º 7
0
    def mask_detector(self,
                      data,
                      mask,
                      nb_frames=1,
                      flatfield=None,
                      background=None,
                      hotpixels=None):
        """
        Mask data measured with a 2D detector.

        It can apply flatfield correction, background subtraction, masking of hotpixels
        and detector gaps.

        :param data: the 2D data to mask
        :param mask: the 2D mask to be updated
        :param nb_frames: number of frames summed to yield the 2D data
         (e.g. in a series measurement), used when defining the threshold for hot pixels
        :param flatfield: the 2D flatfield array to be multiplied with the data
        :param background: a 2D array to be subtracted to the data
        :param hotpixels: a 2D array with hotpixels to be masked
         (1=hotpixel, 0=normal pixel)
        :return: the masked data and the updated mask
        """
        valid.valid_ndarray((data, mask), ndim=2)

        # linearity correction, returns the data itself if linearity_func is None
        data = self._linearity_correction(data)

        # flatfield correction
        data = self._flatfield_correction(data=data, flatfield=flatfield)

        # remove the background
        data = self._background_subtraction(data=data, background=background)

        # mask hotpixels
        data, mask = self._hotpixels_correction(data=data,
                                                mask=mask,
                                                hotpixels=hotpixels)
        # mask detector gaps
        data, mask = self._mask_gaps(data, mask)

        # remove saturated pixels
        data, mask = self._saturation_correction(data,
                                                 mask,
                                                 nb_frames=nb_frames)

        return data, mask
Ejemplo n.º 8
0
    def _mask_gaps(self, data, mask):
        """
        Mask the gaps between sensors in the detector.

        :param data: a 2D numpy array
        :param mask: a 2D numpy array of the same shape as data
        :return:

         - the masked data
         - the updated mask

        """
        valid.valid_ndarray((data, mask),
                            ndim=2,
                            shape=self.unbinned_pixel_number,
                            fix_shape=True)
        return data, mask
Ejemplo n.º 9
0
    def _hotpixels_correction(data, mask, hotpixels):
        """
        Apply hotpixels correction to the data and update the mask.

        :param data: a 2D numpy ndarray
        :param hotpixels: None or a 2D numpy array, 1 if the pixel needs to be masked,
         0 otherwise
        :return: the corrected data array
        """
        if hotpixels is not None:
            valid.valid_ndarray((data, mask, hotpixels), ndim=2)
            if ((hotpixels == 0).sum() +
                (hotpixels == 1).sum()) != hotpixels.size:
                raise ValueError("hotpixels should be an array of 0 and 1")

            data[hotpixels == 1] = 0
            mask[hotpixels == 1] = 1

        return data, mask
Ejemplo n.º 10
0
 def test_fix_shape_false(self):
     self.assertTrue(
         valid.valid_ndarray(arrays=(self.data, np.zeros((1, 1))),
                             fix_shape=False))
Ejemplo n.º 11
0
 def test_compatible_mixed_ndim(self):
     self.assertTrue(
         valid.valid_ndarray(arrays=(self.data, np.ones(1)),
                             ndim=(2, 1),
                             fix_ndim=False))
Ejemplo n.º 12
0
 def test_arrays_is_ndarray(self):
     self.assertTrue(valid.valid_ndarray(arrays=self.data))
Ejemplo n.º 13
0
 def test_shape_item_none(self):
     self.assertTrue(
         valid.valid_ndarray(arrays=(self.data, self.mask),
                             shape=(1, None)))
Ejemplo n.º 14
0
 def test_ndim_shape_none(self):
     self.assertTrue(
         valid.valid_ndarray(arrays=(self.data, self.mask),
                             ndim=None,
                             shape=None))
Ejemplo n.º 15
0
def grid_cdi(
    data,
    mask,
    setup,
    frames_logical,
    correct_curvature=False,
    debugging=False,
    **kwargs,
):
    """
    Interpolate reciprocal space forward CDI data.

    The interpolation is done from the measurement cylindrical frame to the
    laboratory frame (cartesian coordinates). Note that it is based on PetraIII P10
    beamline ( counterclockwise rotation, detector seen from the front).

    :param data: the 3D data, already binned in the detector frame
    :param mask: the corresponding 3D mask
    :param setup: an instance of the class Setup
    :param frames_logical: array of initial length the number of measured frames.
     In case of padding the length changes. A frame whose index is set to 1 means
     that it is used, 0 means not used, -1 means padded (added) frame.
    :param correct_curvature: if True, will correct for the curvature of
     the Ewald sphere
    :param debugging: set to True to see plots
    :param kwargs:
     - 'fill_value': tuple of two real numbers, fill values to use for pixels outside
       of the interpolation range. The first value is for the data, the second for the
       mask. Default is (0, 0)

    :return: the data and mask interpolated in the laboratory frame, q values
     (downstream, vertical up, outboard)
    """
    fill_value = kwargs.get("fill_value", (0, 0))
    valid.valid_ndarray(arrays=(data, mask), ndim=3)
    if setup.name == "P10_SAXS":
        if setup.rocking_angle == "inplane":
            if setup.custom_scan:
                cdi_angle = setup.custom_motors["hprz"]
            else:
                cdi_angle, _, _ = setup.loader.motor_positions(setup=setup)
                # second return value is the X-ray energy, third the detector distance
        else:
            raise ValueError(
                "out-of-plane rotation not yet implemented for forward CDI data"
            )
    else:
        raise NotImplementedError(
            "Not yet implemented for beamlines other than P10")

    data, mask, cdi_angle, frames_logical = check_cdi_angle(
        data=data,
        mask=mask,
        cdi_angle=cdi_angle,
        frames_logical=frames_logical,
        debugging=debugging,
    )
    if debugging:
        print("\ncdi_angle", cdi_angle)
    nbz, nby, nbx = data.shape
    print("\nData shape after check_cdi_angle and before regridding:", nbz,
          nby, nbx)
    print("\nAngle range:", cdi_angle.min(), cdi_angle.max())

    (interp_data, interp_mask), q_values, corrected_dirbeam = setup.ortho_cdi(
        arrays=(data, mask),
        cdi_angle=cdi_angle,
        fill_value=fill_value,
        correct_curvature=correct_curvature,
        debugging=debugging,
    )
    qx, qz, qy = q_values

    # check for Nan
    interp_mask[np.isnan(interp_data)] = 1
    interp_data[np.isnan(interp_data)] = 0
    interp_mask[np.isnan(interp_mask)] = 1
    # set the mask as an array of integers, 0 or 1
    interp_mask[np.nonzero(interp_mask)] = 1
    interp_mask = interp_mask.astype(int)

    # apply the mask to the data
    interp_data[np.nonzero(interp_mask)] = 0

    # calculate the position in pixels of the origin of the reciprocal space
    pivot_z = int((setup.direct_beam[1] - setup.detector.roi[2]) /
                  setup.detector.binning[2])
    # 90 degrees conter-clockwise rotation of detector X around qz, downstream
    _, numy, numx = interp_data.shape
    pivot_y = int(numy - corrected_dirbeam[0])
    # detector Y vertical down, opposite to qz vertical up
    pivot_x = int(numx - corrected_dirbeam[1])
    # detector X inboard at P10, opposite to qy outboard
    print("\nOrigin of the reciprocal space (Qx,Qz,Qy): "
          f"({pivot_z}, {pivot_y}, {pivot_x})\n")

    # plot the gridded data
    final_binning = (
        setup.detector.preprocessing_binning[2] * setup.detector.binning[2],
        setup.detector.preprocessing_binning[1] * setup.detector.binning[1],
        setup.detector.preprocessing_binning[2] * setup.detector.binning[2],
    )
    plot_comment = (
        f"_{numx}_{numy}_{numx}"
        f"_{final_binning[0]}_{final_binning[1]}_{final_binning[2]}.png")
    # sample rotation around the vertical direction at P10: the effective
    # binning in axis 0 is binning[2]

    max_z = interp_data.sum(axis=0).max()
    fig, _, _ = gu.contour_slices(
        interp_data,
        (qx, qz, qy),
        sum_frames=True,
        title="Regridded data",
        levels=np.linspace(0, np.ceil(np.log10(max_z)), 150, endpoint=True),
        plot_colorbar=True,
        scale="log",
        is_orthogonal=True,
        reciprocal_space=True,
    )
    fig.text(
        0.55,
        0.30,
        "Origin of the reciprocal space (Qx,Qz,Qy):\n\n" +
        "     ({:d}, {:d}, {:d})".format(pivot_z, pivot_y, pivot_x),
        size=14,
    )
    fig.savefig(setup.detector.savedir + "reciprocal_space_sum" + plot_comment)
    plt.close(fig)

    fig, _, _ = gu.contour_slices(
        interp_data,
        (qx, qz, qy),
        sum_frames=False,
        title="Regridded data",
        levels=np.linspace(0,
                           np.ceil(np.log10(interp_data.max(initial=None))),
                           150,
                           endpoint=True),
        slice_position=(pivot_z, pivot_y, pivot_x),
        plot_colorbar=True,
        scale="log",
        is_orthogonal=True,
        reciprocal_space=True,
    )
    fig.text(
        0.55,
        0.30,
        "Origin of the reciprocal space (Qx,Qz,Qy):\n\n" +
        "     ({:d}, {:d}, {:d})".format(pivot_z, pivot_y, pivot_x),
        size=14,
    )
    fig.savefig(setup.detector.savedir + "reciprocal_space_central" +
                plot_comment)
    plt.close(fig)

    fig, _, _ = gu.multislices_plot(
        interp_data,
        sum_frames=False,
        scale="log",
        plot_colorbar=True,
        vmin=0,
        slice_position=(pivot_z, pivot_y, pivot_x),
        title="Regridded data",
        is_orthogonal=True,
        reciprocal_space=True,
    )
    fig.text(
        0.55,
        0.30,
        "Origin of the reciprocal space (Qx,Qz,Qy):\n\n" +
        "     ({:d}, {:d}, {:d})".format(pivot_z, pivot_y, pivot_x),
        size=14,
    )
    fig.savefig(setup.detector.savedir + "reciprocal_space_central_pix" +
                plot_comment)
    plt.close(fig)
    if debugging:
        gu.multislices_plot(
            interp_mask,
            sum_frames=False,
            scale="linear",
            plot_colorbar=True,
            vmin=0,
            title="Regridded mask",
            is_orthogonal=True,
            reciprocal_space=True,
        )

    return interp_data, interp_mask, [qx, qz, qy], frames_logical
Ejemplo n.º 16
0
def beamstop_correction(data, setup, debugging=False):
    """
    Correct absorption from the beamstops during P10 forward CDI experiment.

    :param data: the 3D stack of 2D CDI images, shape = (nbz, nby, nbx) or 2D image of
     shape (nby, nbx)
    :param setup: an instance of the class Setup
    :param debugging: set to True to see plots
    :return: the corrected data
    """
    valid.valid_ndarray(arrays=data, ndim=(2, 3))
    energy = setup.energy
    if not isinstance(energy, Real):
        raise TypeError(
            f"Energy should be a number in eV, not a {type(energy)}")

    print(f"Applying beamstop correction for the X-ray energy of {energy}eV")

    if energy not in [8200, 8700, 10000, 10235]:
        print("no beam stop information for the X-ray energy of {:d}eV,"
              " defaulting to the correction for 8700 eV".format(int(energy)))
        energy = 8700

    ndim = data.ndim
    if ndim == 3:
        pass
    elif ndim == 2:
        data = data[np.newaxis, :, :]
    else:
        raise ValueError("2D or 3D data expected")
    nbz, nby, nbx = data.shape

    directbeam_y = setup.direct_beam[0] - setup.detector.roi[0]  # vertical
    directbeam_x = setup.direct_beam[1] - setup.detector.roi[2]  # horizontal

    # at 8200eV, the transmission of 100um Si is 0.26273
    # at 8700eV, the transmission of 100um Si is 0.32478
    # at 10000eV, the transmission of 100um Si is 0.47337
    # at 10235eV, the transmission of 100um Si is 0.51431
    if energy == 8200:
        factor_large = 1 / 0.26273  # 5mm*5mm (100um thick) Si wafer
        factor_small = 1 / 0.26273  # 3mm*3mm (100um thick) Si wafer
        pixels_large = [-33, 35, -31, 36]
        # boundaries of the large wafer relative to the direct beam (V x H)
        pixels_small = [-14, 14, -11, 16]
        # boundaries of the small wafer relative to the direct beam (V x H)
    elif energy == 8700:
        factor_large = 1 / 0.32478  # 5mm*5mm (100um thick) Si wafer
        factor_small = 1 / 0.32478  # 3mm*3mm (100um thick) Si wafer
        pixels_large = [-33, 35, -31, 36]
        # boundaries of the large wafer relative to the direct beam (V x H)
        pixels_small = [-14, 14, -11, 16]
        # boundaries of the small wafer relative to the direct beam (V x H)
    elif energy == 10000:
        factor_large = 2.1 / 0.47337  # 5mm*5mm (200um thick) Si wafer
        factor_small = 4.5 / 0.47337  # 3mm*3mm (300um thick) Si wafer
        pixels_large = [-36, 34, -34, 35]
        # boundaries of the large wafer relative to the direct beam (V x H)
        pixels_small = [-21, 21, -21, 21]
        # boundaries of the small wafer relative to the direct beam (V x H)
    else:  # energy = 10235
        factor_large = 2.1 / 0.51431  # 5mm*5mm (200um thick) Si wafer
        factor_small = 4.5 / 0.51431  # 3mm*3mm (300um thick) Si wafer
        pixels_large = [-34, 35, -33, 36]
        # boundaries of the large wafer relative to the direct beam (V x H)
        pixels_small = [-20, 22, -20, 22]
        # boundaries of the small wafer relative to the direct beam (V x H)

    # define boolean arrays for the large and the small square beam stops
    large_square = np.zeros((nby, nbx))
    large_square[directbeam_y + pixels_large[0]:directbeam_y + pixels_large[1],
                 directbeam_x + pixels_large[2]:directbeam_x +
                 pixels_large[3], ] = 1
    small_square = np.zeros((nby, nbx))
    small_square[directbeam_y + pixels_small[0]:directbeam_y + pixels_small[1],
                 directbeam_x + pixels_small[2]:directbeam_x +
                 pixels_small[3], ] = 1

    # define the boolean array for the border of the large square wafer
    # (the border is 1 pixel wide)
    temp_array = np.zeros((nby, nbx))
    temp_array[directbeam_y + pixels_large[0] + 1:directbeam_y +
               pixels_large[1] - 1, directbeam_x + pixels_large[2] +
               1:directbeam_x + pixels_large[3] - 1, ] = 1
    large_border = large_square - temp_array

    # define the boolean array for the border of the small square wafer
    # (the border is 1 pixel wide)
    temp_array = np.zeros((nby, nbx))
    temp_array[directbeam_y + pixels_small[0] + 1:directbeam_y +
               pixels_small[1] - 1, directbeam_x + pixels_small[2] +
               1:directbeam_x + pixels_small[3] - 1, ] = 1
    small_border = small_square - temp_array

    if debugging:
        gu.imshow_plot(
            data,
            sum_frames=True,
            sum_axis=0,
            vmin=0,
            vmax=11,
            plot_colorbar=True,
            scale="log",
            title="data before absorption correction",
            is_orthogonal=False,
            reciprocal_space=True,
        )

        gu.combined_plots(
            tuple_array=(large_square, small_square, large_border,
                         small_border),
            tuple_sum_frames=(False, False, False, False),
            tuple_sum_axis=0,
            tuple_width_v=None,
            tuple_width_h=None,
            tuple_colorbar=False,
            tuple_vmin=0,
            tuple_vmax=11,
            is_orthogonal=False,
            reciprocal_space=True,
            tuple_title=(
                "large_square",
                "small_square",
                "larger border",
                "small border",
            ),
            tuple_scale=("linear", "linear", "linear", "linear"),
        )

    # absorption correction for the large and small square beam stops
    for idx in range(nbz):
        tempdata = data[idx, :, :]
        tempdata[np.nonzero(large_square)] = (
            tempdata[np.nonzero(large_square)] * factor_large)
        tempdata[np.nonzero(small_square)] = (
            tempdata[np.nonzero(small_square)] * factor_small)
        data[idx, :, :] = tempdata

    if debugging:
        width = 40
        _, (ax0, ax1) = plt.subplots(nrows=1, ncols=2, figsize=(12, 6))
        ax0.plot(
            np.log10(data[:, directbeam_y, directbeam_x - width:directbeam_x +
                          width].sum(axis=0)))
        ax0.set_title("horizontal cut after absorption correction")
        ax0.vlines(
            x=[
                width + pixels_large[2],
                width + pixels_large[3],
                width + pixels_small[2],
                width + pixels_small[3],
            ],
            ymin=ax0.get_ylim()[0],
            ymax=ax0.get_ylim()[1],
            colors="b",
            linestyle="dashed",
        )
        ax1.plot(
            np.log10(data[:, directbeam_y - width:directbeam_y + width,
                          directbeam_x].sum(axis=0)))
        ax1.set_title("vertical cut after absorption correction")
        ax1.vlines(
            x=[
                width + pixels_large[0],
                width + pixels_large[1],
                width + pixels_small[0],
                width + pixels_small[1],
            ],
            ymin=ax1.get_ylim()[0],
            ymax=ax1.get_ylim()[1],
            colors="b",
            linestyle="dashed",
        )

        gu.imshow_plot(
            data,
            sum_frames=True,
            sum_axis=0,
            vmin=0,
            vmax=11,
            plot_colorbar=True,
            scale="log",
            title="data after absorption correction",
            is_orthogonal=False,
            reciprocal_space=True,
        )

    # interpolation for the border of the large square wafer
    indices = np.argwhere(large_border == 1)
    data[np.nonzero(np.repeat(large_border[np.newaxis, :, :], nbz,
                              axis=0))] = 0  # exclude border points
    for frame in range(nbz):  # loop over 2D images in the detector plane
        tempdata = data[frame, :, :]
        for idx in range(indices.shape[0]):
            pixrow = indices[idx, 0]
            pixcol = indices[idx, 1]
            counter = (9 - large_border[pixrow - 1:pixrow + 2,
                                        pixcol - 1:pixcol + 2].sum()
                       )  # number of pixels in a 3x3 window
            # which do not belong to the border
            tempdata[pixrow, pixcol] = (
                tempdata[pixrow - 1:pixrow + 2, pixcol - 1:pixcol + 2].sum() /
                counter)
        data[frame, :, :] = tempdata

    # interpolation for the border of the small square wafer
    indices = np.argwhere(small_border == 1)
    data[np.nonzero(np.repeat(small_border[np.newaxis, :, :], nbz,
                              axis=0))] = 0  # exclude border points
    for frame in range(nbz):  # loop over 2D images in the detector plane
        tempdata = data[frame, :, :]
        for idx in range(indices.shape[0]):
            pixrow = indices[idx, 0]
            pixcol = indices[idx, 1]
            counter = (9 - small_border[pixrow - 1:pixrow + 2,
                                        pixcol - 1:pixcol + 2].sum()
                       )  # number of pixels in a 3x3 window
            # which do not belong to the border
            tempdata[pixrow, pixcol] = (
                tempdata[pixrow - 1:pixrow + 2, pixcol - 1:pixcol + 2].sum() /
                counter)
        data[frame, :, :] = tempdata

    if debugging:
        width = 40
        _, (ax0, ax1) = plt.subplots(nrows=1, ncols=2, figsize=(12, 6))
        ax0.plot(
            np.log10(data[:, directbeam_y, directbeam_x - width:directbeam_x +
                          width].sum(axis=0)))
        ax0.set_title("horizontal cut after interpolating border")
        ax0.vlines(
            x=[
                width + pixels_large[2],
                width + pixels_large[3],
                width + pixels_small[2],
                width + pixels_small[3],
            ],
            ymin=ax0.get_ylim()[0],
            ymax=ax0.get_ylim()[1],
            colors="b",
            linestyle="dashed",
        )
        ax1.plot(
            np.log10(data[:, directbeam_y - width:directbeam_y + width,
                          directbeam_x].sum(axis=0)))
        ax1.set_title("vertical cut after interpolating border")
        ax1.vlines(
            x=[
                width + pixels_large[0],
                width + pixels_large[1],
                width + pixels_small[0],
                width + pixels_small[1],
            ],
            ymin=ax1.get_ylim()[0],
            ymax=ax1.get_ylim()[1],
            colors="b",
            linestyle="dashed",
        )

        gu.imshow_plot(
            data,
            sum_frames=True,
            sum_axis=0,
            vmin=0,
            vmax=11,
            plot_colorbar=True,
            scale="log",
            title="data after interpolating the border of beam stops",
            is_orthogonal=False,
            reciprocal_space=True,
        )
    return data
Ejemplo n.º 17
0
def check_cdi_angle(data, mask, cdi_angle, frames_logical, debugging=False):
    """
    Check for overlaps of the sample rotation motor position in forward CDI experiment.

    It checks if there is no overlap in the measurement angles, and crops it otherwise.
    Flip the rotation direction to convert sample angles into detector angles. Update
    data, mask and frames_logical accordingly.

    :param data: 3D forward CDI dataset before gridding.
    :param mask: 3D mask
    :param cdi_angle: array of measurement sample angles in degrees
    :param frames_logical: array of initial length the number of measured frames.
     In case of padding the length changes. A frame whose index is set to 1 means
     that it is used, 0 means not used, -1 means padded (added) frame.
    :param debugging: True to have more printed comments
    :return: updated data, mask, detector cdi_angle, frames_logical
    """
    valid.valid_ndarray(arrays=(data, mask), ndim=3)
    detector_angle = np.zeros(len(cdi_angle))
    # flip the rotation axis in order to compensate the rotation of the Ewald sphere
    # due to sample rotation
    print(
        "Reverse the rotation direction to compensate the rotation of the Ewald sphere"
    )
    for idx, item in enumerate(cdi_angle):
        detector_angle[idx] = cdi_angle[0] - (item - cdi_angle[0])

    wrap_angle = util.wrap(obj=detector_angle,
                           start_angle=detector_angle.min(),
                           range_angle=180)
    for idx, item in enumerate(wrap_angle):
        duplicate = np.isclose(wrap_angle[:idx], item, rtol=1e-06,
                               atol=1e-06).sum()
        # duplicate will be different from 0 if there is a duplicated angle
        frames_logical[idx] = frames_logical[idx] * (
            duplicate == 0)  # set frames_logical to 0 if duplicated angle

    if debugging:
        print("frames_logical after checking duplicated angles:\n",
              frames_logical)

    # find first duplicated angle
    try:
        index_duplicated = np.where(frames_logical == 0)[0][0]
        # change the angle by a negligeable amount
        # to still be able to use it for interpolation
        if cdi_angle[1] - cdi_angle[0] > 0:
            detector_angle[
                index_duplicated] = detector_angle[index_duplicated] - 0.0001
        else:
            detector_angle[
                index_duplicated] = detector_angle[index_duplicated] + 0.0001
        print(
            "RegularGridInterpolator cannot take duplicated values: shifting frame",
            index_duplicated,
            "by 1/10000 degrees for the interpolation",
        )

        frames_logical[index_duplicated] = 1
    except IndexError:  # no duplicated angle
        print("no duplicated angle")

    data = data[np.nonzero(frames_logical)[0], :, :]
    mask = mask[np.nonzero(frames_logical)[0], :, :]
    detector_angle = detector_angle[np.nonzero(frames_logical)]
    return data, mask, detector_angle, frames_logical
Ejemplo n.º 18
0
 def test_ndim_type_list(self):
     self.assertTrue(
         valid.valid_ndarray(arrays=(self.data, self.mask), ndim=[2, 3]))
Ejemplo n.º 19
0
def reload_cdi_data(
    data,
    mask,
    scan_number,
    setup,
    normalize_method="skip",
    debugging=False,
    **kwargs,
):
    """
    Reload forward CDI data, apply optional threshold, normalization and binning.

    :param data: the 3D data array
    :param mask: the 3D mask array
    :param scan_number: the scan number to load
    :param setup: an instance of the class Setup
    :param normalize_method: 'skip' to skip, 'monitor'  to normalize by the default
     monitor, 'sum_roi' to normalize by the integrated intensity in a defined region
     of interest
    :param debugging:  set to True to see plots
    :parama kwargs:
     - 'photon_threshold' = float, photon threshold to apply before binning

    :return:
     - the updated 3D data and mask arrays
     - the monitor values used for the intensity normalization

    """
    valid.valid_ndarray(arrays=(data, mask), ndim=3)
    # check and load kwargs
    valid.valid_kwargs(
        kwargs=kwargs,
        allowed_kwargs={"photon_threshold"},
        name="kwargs",
    )
    photon_threshold = kwargs.get("photon_threshold", 0)
    valid.valid_item(
        photon_threshold,
        allowed_types=Real,
        min_included=0,
        name="photon_threshold",
    )

    nbz, nby, nbx = data.shape
    frames_logical = np.ones(nbz)

    print((data < 0).sum(), " negative data points masked"
          )  # can happen when subtracting a background
    mask[data < 0] = 1
    data[data < 0] = 0

    # normalize by the incident X-ray beam intensity
    if normalize_method == "skip":
        print("Skip intensity normalization")
        monitor = []
    else:
        if normalize_method == "sum_roi":
            monitor = data[:,
                           setup.detector.sum_roi[0]:setup.detector.sum_roi[1],
                           setup.detector.sum_roi[2]:setup.detector.
                           sum_roi[3], ].sum(axis=(1, 2))
        else:  # use the default monitor of the beamline
            monitor = setup.loader.read_monitor(
                scan_number=scan_number,
                setup=setup,
            )

        print("Intensity normalization using " + normalize_method)
        data, monitor = loader.normalize_dataset(
            array=data,
            monitor=monitor,
            norm_to_min=True,
            savedir=setup.detector.savedir,
            debugging=True,
        )

    # pad the data to the shape defined by the ROI
    if (setup.detector.roi[1] - setup.detector.roi[0] > nby
            or setup.detector.roi[3] - setup.detector.roi[2] > nbx):
        start = (
            0,
            max(0, abs(setup.detector.roi[0])),
            max(0, abs(setup.detector.roi[2])),
        )
        print("Paddind the data to the shape defined by the ROI")
        data = util.crop_pad(
            array=data,
            pad_start=start,
            output_shape=(
                data.shape[0],
                setup.detector.roi[1] - setup.detector.roi[0],
                setup.detector.roi[3] - setup.detector.roi[2],
            ),
        )
        mask = util.crop_pad(
            array=mask,
            pad_value=1,
            pad_start=start,
            output_shape=(
                mask.shape[0],
                setup.detector.roi[1] - setup.detector.roi[0],
                setup.detector.roi[3] - setup.detector.roi[2],
            ),
        )

    # apply optional photon threshold before binning
    if photon_threshold != 0:
        mask[data < photon_threshold] = 1
        data[data < photon_threshold] = 0
        print("Applying photon threshold before binning: < ", photon_threshold)

    # bin data and mask in the detector plane if needed
    # binning in the stacking dimension is done at the very end of the data processing
    if (setup.detector.binning[1] != 1) or (setup.detector.binning[2] != 1):
        print(
            "Binning the data: detector vertical axis by",
            setup.detector.binning[1],
            ", detector horizontal axis by",
            setup.detector.binning[2],
        )
        data = util.bin_data(
            data,
            (1, setup.detector.binning[1], setup.detector.binning[2]),
            debugging=debugging,
        )
        mask = util.bin_data(
            mask,
            (1, setup.detector.binning[1], setup.detector.binning[2]),
            debugging=debugging,
        )
        mask[np.nonzero(mask)] = 1

    return data, mask, frames_logical, monitor
Ejemplo n.º 20
0
 def test_ndim_type_tuple(self):
     self.assertTrue(
         valid.valid_ndarray(arrays=(self.data, self.mask), ndim=(2, 3)))