Example #1
0
    def percentile_range(self, low=69.0, high=99.9):
        d = HexrdConfig().current_images_dict()
        l = min([np.percentile(d[key], low) for key in d.keys()])
        h = min([np.percentile(d[key], high) for key in d.keys()])

        if h - l < 5:
            h = l + 5

        # This debug is useful for now, keeping it in for sanity...
        print("Range to be used: ", l, " -> ", h)

        return l, h
Example #2
0
def cartesian_viewer():
    images_dict = HexrdConfig().current_images_dict()
    plane_data = HexrdConfig().active_material.planeData
    pixel_size = HexrdConfig().cartesian_pixel_size

    # HEDMInstrument expects None Euler angle convention for the
    # config. Let's get it as such.
    iconfig = HexrdConfig().instrument_config_none_euler_convention
    rme = HexrdConfig().rotation_matrix_euler()
    instr = instrument.HEDMInstrument(instrument_config=iconfig,
                                      tilt_calibration_mapping=rme)

    # Make sure each key in the image dict is in the panel_ids
    if images_dict.keys() != instr._detectors.keys():
        msg = ('Images do not match the panel ids!\n' +
               'Images: ' + str(list(images_dict.keys())) + '\n' +
               'PanelIds: ' + str(list(instr._detectors.keys())))
        raise Exception(msg)

    return InstrumentViewer(instr, images_dict, plane_data, pixel_size)
Example #3
0
class InstrumentViewer:

    def __init__(self):
        self.type = ViewType.cartesian
        self.instr = create_hedm_instrument()
        self.images_dict = HexrdConfig().current_images_dict()

        # Perform some checks before proceeding
        self.check_keys_match()
        self.check_angles_feasible()

        self.pixel_size = HexrdConfig().cartesian_pixel_size
        self.warp_dict = {}
        self.detector_corners = {}

        dist = HexrdConfig().cartesian_virtual_plane_distance
        dplane_tvec = np.array([0., 0., -dist])

        rotate_x = HexrdConfig().cartesian_plane_normal_rotate_x
        rotate_y = HexrdConfig().cartesian_plane_normal_rotate_y

        dplane_tilt = np.radians(np.array(([rotate_x, rotate_y, 0.])))

        self.dplane = DisplayPlane(tvec=dplane_tvec, tilt=dplane_tilt)
        self.make_dpanel()

        # Check that the image size won't be too big...
        self.check_size_feasible()

        self.plot_dplane()

    def check_keys_match(self):
        # Make sure each key in the image dict is in the panel_ids
        if self.images_dict.keys() != self.instr._detectors.keys():
            msg = ('Images do not match the panel ids!\n'
                   f'Images: {str(list(self.images_dict.keys()))}\n'
                   f'PanelIds: {str(list(self.instr._detectors.keys()))}')
            raise Exception(msg)

    def check_angles_feasible(self):
        max_angle = 120.0

        # Check all combinations of detectors. If any have an angle
        # between them that is greater than the max, raise an exception.
        combos = itertools.combinations(self.instr._detectors.items(), 2)
        bad_combos = []
        acos = np.arccos
        norm = np.linalg.norm
        for x, y in combos:
            n1, n2 = x[1].normal, y[1].normal
            angle = np.degrees(acos(np.dot(n1, n2) / norm(n1) / norm(n2)))
            if angle > max_angle:
                bad_combos.append(((x[0], y[0]), angle))

        if bad_combos:
            msg = 'Cartesian plot not feasible\n'
            msg += 'Angle between detectors is too large\n'
            msg += '\n'.join([f'{x[0]}: {x[1]}' for x in bad_combos])
            raise Exception(msg)

    def check_size_feasible(self):
        available_mem = psutil.virtual_memory().available
        img_dtype = np.float64
        dtype_size = np.dtype(img_dtype).itemsize

        mem_usage = self.dpanel.rows * self.dpanel.cols * dtype_size
        # Extra memory we probably need for other things...
        mem_extra_buffer = 1e7
        mem_usage += mem_extra_buffer
        if mem_usage > available_mem:

            def format_size(size):
                sizes = [
                    ('TB', 1e12),
                    ('GB', 1e9),
                    ('MB', 1e6),
                    ('KB', 1e3),
                    ('B', 1)
                ]
                for s in sizes:
                    if size > s[1]:
                        return f'{round(size / s[1], 2)} {s[0]}'

            msg = 'Not enough memory for Cartesian plot\n'
            msg += f'Memory available: {format_size(available_mem)}\n'
            msg += f'Memory required: {format_size(mem_usage)}'
            raise Exception(msg)

    def make_dpanel(self):
        self.dpanel_sizes = self.dplane.panel_size(self.instr)
        self.dpanel = self.dplane.display_panel(self.dpanel_sizes,
                                                self.pixel_size,
                                                self.instr.beam_vector)

    @property
    def extent(self):
        # We might want to use self.dpanel.col_edge_vec and
        # self.dpanel.row_edge_vec here instead.
        # !!! recall that extents are (left, right, bottom, top)
        x_lim = self.dpanel.col_dim / 2
        y_lim = self.dpanel.row_dim / 2
        return -x_lim, x_lim, -y_lim, y_lim

    def update_overlay_data(self):
        if not HexrdConfig().show_overlays:
            # Nothing to do
            return

        # must update self.dpanel from HexrdConfig
        self.pixel_size = HexrdConfig().cartesian_pixel_size
        self.make_dpanel()

        # The overlays for the Cartesian view are made via a fake
        # instrument with a single detector.
        # Make a copy of the instrument and modify.
        temp_instr = copy.deepcopy(self.instr)
        temp_instr._detectors.clear()
        temp_instr._detectors['dpanel'] = self.dpanel

        update_overlay_data(temp_instr, self.type)

    def plot_dplane(self):
        # Cache the image max and min for later use
        images = self.images_dict.values()
        self.min = min([x.min() for x in images])
        self.max = max([x.max() for x in images])

        # Create the warped image for each detector
        for detector_id in self.images_dict.keys():
            self.create_warped_image(detector_id)

        # Generate the final image
        self.generate_image()

    def detector_borders(self, det):
        corners = self.detector_corners.get(det, [])

        # These corners are in pixel coordinates. Convert to Cartesian.
        # Swap x and y first.
        corners = [[y, x] for x, y in corners]
        corners = self.dpanel.pixelToCart(corners)

        x_vals = [x[0] for x in corners]
        y_vals = [x[1] for x in corners]

        if x_vals and y_vals:
            # Double each set of points.
            # This is so that if a point is moved, it won't affect the
            # other line sharing this point.
            x_vals = [x for x in x_vals for _ in (0, 1)]
            y_vals = [y for y in y_vals for _ in (0, 1)]

            # Move the first point to the back to complete the line
            x_vals += [x_vals.pop(0)]
            y_vals += [y_vals.pop(0)]

            # Make sure all points are inside the frame.
            # If there are points outside the frame, move them inside.
            extent = self.extent
            x_range = (extent[0], extent[1])
            y_range = (extent[2], extent[3])

            def out_of_frame(p):
                # Check if point p is out of the frame
                return (not x_range[0] <= p[0] <= x_range[1] or
                        not y_range[0] <= p[1] <= y_range[1])

            def move_point_into_frame(p, p2):
                # Make sure we don't divide by zero
                if p2[0] == p[0]:
                    return

                # Move point p into the frame via its line equation
                # y = mx + b
                m = (p2[1] - p[1]) / (p2[0] - p[0])
                b = p[1] - m * p[0]
                if p[0] < x_range[0]:
                    p[0] = x_range[0]
                    p[1] = m * p[0] + b
                elif p[0] > x_range[1]:
                    p[0] = x_range[1]
                    p[1] = m * p[0] + b

                if p[1] < y_range[0]:
                    p[1] = y_range[0]
                    p[0] = (p[1] - b) / m
                elif p[1] > y_range[1]:
                    p[1] = y_range[1]
                    p[0] = (p[1] - b) / m

            i = 0
            while i < len(x_vals) - 1:
                # We look at pairs of points at a time
                p1 = [x_vals[i], y_vals[i]]
                p2 = [x_vals[i + 1], y_vals[i + 1]]

                if out_of_frame(p1):
                    # Move the point into the frame via its line equation
                    move_point_into_frame(p1, p2)
                    x_vals[i], y_vals[i] = p1[0], p1[1]

                    # Insert a pair of Nones to disconnect the drawing
                    x_vals.insert(i, None)
                    y_vals.insert(i, None)
                    i += 1

                if out_of_frame(p2):
                    # Move the point into the frame via its line equation
                    move_point_into_frame(p2, p1)
                    x_vals[i + 1], y_vals[i + 1] = p2[0], p2[1]

                i += 2

        # The border drawer expects a list of lines.
        # We only have one line here.
        return [(x_vals, y_vals)]

    @property
    def all_detector_borders(self):
        borders = {}
        for det in self.images_dict.keys():
            borders[det] = self.detector_borders(det)

        return borders

    def create_warped_image(self, detector_id):
        img = self.images_dict[detector_id]
        panel = self.instr._detectors[detector_id]

        # map corners
        corners = np.vstack(
            [panel.corner_ll,
             panel.corner_lr,
             panel.corner_ur,
             panel.corner_ul,
             ]
        )
        mp = panel.map_to_plane(corners, self.dplane.rmat,
                                self.dplane.tvec)

        col_edges = self.dpanel.col_edge_vec
        row_edges = self.dpanel.row_edge_vec
        j_col = cellIndices(col_edges, mp[:, 0])
        i_row = cellIndices(row_edges, mp[:, 1])

        src = np.vstack([j_col, i_row]).T

        # Save detector corners in pixel coordinates
        self.detector_corners[detector_id] = src

        dst = panel.cartToPixel(corners, pixels=True)
        dst = dst[:, ::-1]

        tform3 = tf.ProjectiveTransform()
        tform3.estimate(src, dst)

        res = tf.warp(img, tform3,
                      output_shape=(self.dpanel.rows, self.dpanel.cols))
        self.warp_dict[detector_id] = res
        return res

    def generate_image(self):
        img = np.zeros((self.dpanel.rows, self.dpanel.cols))
        for key in self.images_dict.keys():
            img += self.warp_dict[key]

        # Rescale the data to match the scale of the original dataset
        # TODO: try to get create_calibration_image to not rescale the
        # result to be between 0 and 1 in the first place so this will
        # not be necessary.
        self.img = np.interp(img, (img.min(), img.max()), (self.min, self.max))

    def update_detector(self, det):
        # First, convert to the "None" angle convention
        iconfig = HexrdConfig().instrument_config_none_euler_convention

        t_conf = iconfig['detectors'][det]['transform']
        self.instr.detectors[det].tvec = t_conf['translation']
        self.instr.detectors[det].tilt = t_conf['tilt']

        # Update the individual detector image
        self.create_warped_image(det)

        # Generate the final image
        self.generate_image()
Example #4
0
class PolarView:
    """Create (two-theta, eta) plot of detectors
    """
    def __init__(self, instrument):

        self.instr = instrument

        self.images_dict = HexrdConfig().images_dict

        self.warp_dict = {}

        self.snip1d_background = None

        self.update_angular_grid()

    @property
    def detectors(self):
        return self.instr.detectors

    @property
    def chi(self):
        return self.instr.chi

    @property
    def tvec_s(self):
        return self.instr.tvec

    @property
    def tth_min(self):
        return np.radians(HexrdConfig().polar_res_tth_min)

    @property
    def tth_max(self):
        return np.radians(HexrdConfig().polar_res_tth_max)

    @property
    def tth_range(self):
        return self.tth_max - self.tth_min

    @property
    def tth_pixel_size(self):
        return HexrdConfig().polar_pixel_size_tth

    def tth_to_pixel(self, tth):
        """
        convert two-theta value to pixel value (float) along two-theta axis
        """
        return np.degrees(tth - self.tth_min) / self.tth_pixel_size

    @property
    def eta_min(self):
        return np.radians(HexrdConfig().polar_res_eta_min)

    @property
    def eta_max(self):
        return np.radians(HexrdConfig().polar_res_eta_max)

    @property
    def eta_range(self):
        return self.eta_max - self.eta_min

    @property
    def eta_pixel_size(self):
        return HexrdConfig().polar_pixel_size_eta

    def eta_to_pixel(self, eta):
        """
        convert eta value to pixel value (float) along eta axis
        """
        return np.degrees(eta - self.eta_min) / self.eta_pixel_size

    @property
    def ntth(self):
        return int(np.degrees(self.tth_range) / self.tth_pixel_size)

    @property
    def neta(self):
        return int(np.degrees(self.eta_range) / self.eta_pixel_size)

    @property
    def shape(self):
        return (self.neta, self.ntth)

    def update_angular_grid(self):
        tth_vec = np.radians(self.tth_pixel_size * (np.arange(self.ntth)))\
            + self.tth_min + 0.5 * np.radians(self.tth_pixel_size)
        eta_vec = np.radians(self.eta_pixel_size * (np.arange(self.neta)))\
            + self.eta_min + 0.5 * np.radians(self.eta_pixel_size)
        self._angular_grid = np.meshgrid(eta_vec, tth_vec, indexing='ij')

    @property
    def angular_grid(self):
        return self._angular_grid

    @property
    def eta_period(self):
        return HexrdConfig().polar_res_eta_period

    @property
    def images_dict(self):
        return self._images_dict

    @images_dict.setter
    def images_dict(self, v):
        self._images_dict = v

        # Cache the image min and max for later use
        self.min = min(x.min() for x in v.values())
        self.max = max(x.max() for x in v.values())

    def detector_borders(self, det):
        panel = self.detectors[det]

        row_vec, col_vec = panel.row_pixel_vec, panel.col_pixel_vec
        x_start, x_stop = col_vec[0], col_vec[-1]
        y_start, y_stop = row_vec[0], row_vec[-1]

        # Create the borders in Cartesian
        borders = [[[x, y_start] for x in col_vec],
                   [[x, y_stop] for x in col_vec],
                   [[x_start, y] for y in row_vec],
                   [[x_stop, y] for y in row_vec]]

        # Convert each border to angles
        for i, border in enumerate(borders):
            angles, _ = detectorXYToGvec(border,
                                         panel.rmat,
                                         ct.identity_3x3,
                                         panel.tvec,
                                         ct.zeros_3,
                                         ct.zeros_3,
                                         beamVec=panel.bvec,
                                         etaVec=panel.evec)
            angles = np.array(angles)
            angles[1:, :] = mapAngle(angles[1:, :],
                                     np.radians(self.eta_period),
                                     units='radians')
            # Convert to degrees, and keep them as lists for
            # easier modification later
            borders[i] = np.degrees(angles).tolist()

        # Here, we are going to remove points that are out-of-bounds,
        # and we are going to insert None in between points that are far
        # apart (in the y component), so that they are not connected in the
        # plot. This happens for detectors that are wrapped in the image.
        x_range = np.degrees((self.tth_min, self.tth_max))
        y_range = np.degrees((self.eta_min, self.eta_max))

        # "Far apart" is currently defined as half of the y range
        max_y_distance = abs(y_range[1] - y_range[0]) / 2.0
        for j in range(4):
            border_x, border_y = borders[j][0], borders[j][1]
            i = 0
            # These should be the same length, but just in case...
            while i < len(border_x) and i < len(border_y):
                x, y = border_x[i], border_y[i]
                if (not x_range[0] <= x <= x_range[1]
                        or not y_range[0] <= y <= y_range[1]):
                    # The point is out of bounds, remove it
                    del border_x[i], border_y[i]
                    continue

                if i != 0 and abs(y - border_y[i - 1]) > max_y_distance:
                    # Points are too far apart. Insert a None
                    border_x.insert(i, None)
                    border_y.insert(i, None)
                    i += 1

                i += 1

        return borders

    @property
    def all_detector_borders(self):
        borders = {}
        for key in self.images_dict.keys():
            borders[key] = self.detector_borders(key)

        return borders

    def create_warp_image(self, det):
        angpts = self.angular_grid
        dummy_ome = np.zeros((self.ntth * self.neta))

        # lcount = 0
        panel = self.detectors[det]
        img = self.images_dict[det]

        gvec_angs = np.vstack(
            [angpts[1].flatten(), angpts[0].flatten(), dummy_ome]).T

        xypts = np.nan * np.ones((len(gvec_angs), 2))
        valid_xys, rmats_s, on_plane = _project_on_detector_plane(
            gvec_angs,
            panel.rmat,
            np.eye(3),
            self.chi,
            panel.tvec,
            tvec_c,
            self.tvec_s,
            panel.distortion,
            beamVec=panel.bvec)
        xypts[on_plane] = valid_xys

        wimg = panel.interpolate_bilinear(
            xypts,
            img,
            pad_with_nans=True,
        ).reshape(self.shape)
        nan_mask = np.isnan(wimg)

        # Store as masked array
        self.warp_dict[det] = np.ma.masked_array(data=wimg,
                                                 mask=nan_mask,
                                                 fill_value=0.)
        return self.warp_dict[det]

    def generate_image(self):
        # sum masked images in self.warp_dict using np.ma ufuncs
        #
        # !!! checked that np.ma.sum is applying logical OR across masks (JVB)
        # !!! assignment to img fills NaNs with zeros
        # !!! NOTE: cannot set `data` attribute on masked_array,
        #           but can manipulate mask
        maimg = np.ma.sum(np.ma.stack(self.warp_dict.values()), axis=0)
        img = maimg.data

        # Rescale the data to match the scale of the original dataset
        kwargs = {
            'image': img,
            'in_range': (np.nanmin(img), np.nanmax(img)),
            'out_range': (self.min, self.max),
        }
        img = rescale_intensity(**kwargs)

        # do SNIP if requested
        # !!! Fast snip1d (ndimage) cannot handle nans
        # from hexrdgui.hexrd.ui.utils:
        #     Fast_SNIP_1D = 0
        #     SNIP_1D = 1
        #     SNIP_2D = 2
        if HexrdConfig().polar_apply_snip1d:
            if HexrdConfig().polar_snip1d_algorithm in [1, 2]:
                img[maimg.mask] = np.nan
            self.snip1d_background = run_snip1d(img)
            # Perform the background subtraction
            img -= self.snip1d_background
        else:
            self.snip1d_background = None

        # Apply user-specified masks if they are present
        for mask in HexrdConfig().visible_polar_masks:
            maimg.mask = np.logical_or(maimg.mask, ~mask)
        img[maimg.mask] = np.nan

        self.img = img  # just a normal ndarry with NaNs

    def warp_all_images(self):
        # Create the warped image for each detector
        for det in self.images_dict.keys():
            self.create_warp_image(det)

        # Generate the final image
        self.generate_image()

    def update_images_dict(self):
        if HexrdConfig().any_intensity_corrections:
            self.images_dict = HexrdConfig().images_dict

    def update_detector(self, det):
        # If there are intensity corrections and the detector transform
        # has been modified, we need to update the images dict.
        self.update_images_dict()

        # First, convert to the "None" angle convention
        iconfig = HexrdConfig().instrument_config_none_euler_convention

        t_conf = iconfig['detectors'][det]['transform']
        self.instr.detectors[det].tvec = t_conf['translation']
        self.instr.detectors[det].tilt = t_conf['tilt']

        # Update the individual detector image
        self.create_warp_image(det)

        # Generate the final image
        self.generate_image()
Example #5
0
class PolarView:
    """Create (two-theta, eta) plot of detectors
    """
    def __init__(self, instrument):

        self.instr = instrument

        self.images_dict = HexrdConfig().current_images_dict()

        self.warp_dict = {}

        self.snip1d_background = None

        self.update_angular_grid()

    @property
    def detectors(self):
        return self.instr.detectors

    @property
    def chi(self):
        return self.instr.chi

    @property
    def tvec_s(self):
        return self.instr.tvec

    @property
    def tth_min(self):
        return np.radians(HexrdConfig().polar_res_tth_min)

    @property
    def tth_max(self):
        return np.radians(HexrdConfig().polar_res_tth_max)

    @property
    def tth_range(self):
        return self.tth_max - self.tth_min

    @property
    def tth_pixel_size(self):
        return HexrdConfig().polar_pixel_size_tth

    def tth_to_pixel(self, tth):
        """
        convert two-theta value to pixel value (float) along two-theta axis
        """
        return np.degrees(tth - self.tth_min) / self.tth_pixel_size

    @property
    def eta_min(self):
        return np.radians(HexrdConfig().polar_res_eta_min)

    @property
    def eta_max(self):
        return np.radians(HexrdConfig().polar_res_eta_max)

    @property
    def eta_range(self):
        return self.eta_max - self.eta_min

    @property
    def eta_pixel_size(self):
        return HexrdConfig().polar_pixel_size_eta

    def eta_to_pixel(self, eta):
        """
        convert eta value to pixel value (float) along eta axis
        """
        return np.degrees(eta - self.eta_min) / self.eta_pixel_size

    @property
    def ntth(self):
        return int(np.degrees(self.tth_range) / self.tth_pixel_size)

    @property
    def neta(self):
        return int(np.degrees(self.eta_range) / self.eta_pixel_size)

    @property
    def shape(self):
        return (self.neta, self.ntth)

    def update_angular_grid(self):
        tth_vec = np.radians(self.tth_pixel_size * (np.arange(self.ntth)))\
            + self.tth_min + 0.5 * np.radians(self.tth_pixel_size)
        eta_vec = np.radians(self.eta_pixel_size * (np.arange(self.neta)))\
            + self.eta_min + 0.5 * np.radians(self.eta_pixel_size)
        self._angular_grid = np.meshgrid(eta_vec, tth_vec, indexing='ij')

    @property
    def angular_grid(self):
        return self._angular_grid

    def detector_borders(self, det):
        panel = self.detectors[det]

        row_vec, col_vec = panel.row_pixel_vec, panel.col_pixel_vec
        x_start, x_stop = col_vec[0], col_vec[-1]
        y_start, y_stop = row_vec[0], row_vec[-1]

        # Create the borders in Cartesian
        borders = [[[x, y_start] for x in col_vec],
                   [[x, y_stop] for x in col_vec],
                   [[x_start, y] for y in row_vec],
                   [[x_stop, y] for y in row_vec]]

        # Convert each border to angles
        for i, border in enumerate(borders):
            angles, _ = detectorXYToGvec(border,
                                         panel.rmat,
                                         ct.identity_3x3,
                                         panel.tvec,
                                         ct.zeros_3,
                                         ct.zeros_3,
                                         beamVec=panel.bvec,
                                         etaVec=panel.evec)
            # Convert to degrees, and keep them as lists for
            # easier modification later
            borders[i] = np.degrees(angles).tolist()

        # Here, we are going to remove points that are out-of-bounds,
        # and we are going to insert None in between points that are far
        # apart (in the y component), so that they are not connected in the
        # plot. This happens for detectors that are wrapped in the image.
        x_range = np.degrees((self.tth_min, self.tth_max))
        y_range = np.degrees((self.eta_min, self.eta_max))

        # "Far apart" is currently defined as half of the y range
        max_y_distance = abs(y_range[1] - y_range[0]) / 2.0
        for j in range(4):
            border_x, border_y = borders[j][0], borders[j][1]
            i = 0
            # These should be the same length, but just in case...
            while i < len(border_x) and i < len(border_y):
                x, y = border_x[i], border_y[i]
                if (not x_range[0] <= x <= x_range[1]
                        or not y_range[0] <= y <= y_range[1]):
                    # The point is out of bounds, remove it
                    del border_x[i], border_y[i]
                    continue

                if i != 0 and abs(y - border_y[i - 1]) > max_y_distance:
                    # Points are too far apart. Insert a None
                    border_x.insert(i, None)
                    border_y.insert(i, None)
                    i += 1

                i += 1

        return borders

    @property
    def all_detector_borders(self):
        borders = {}
        for key in self.images_dict.keys():
            borders[key] = self.detector_borders(key)

        return borders

    def create_warp_image(self, det):
        angpts = self.angular_grid
        dummy_ome = np.zeros((self.ntth * self.neta))

        # lcount = 0
        panel = self.detectors[det]
        img = self.images_dict[det]

        gvec_angs = np.vstack(
            [angpts[1].flatten(), angpts[0].flatten(), dummy_ome]).T

        xypts = np.nan * np.ones((len(gvec_angs), 2))
        valid_xys, rmats_s, on_plane = _project_on_detector_plane(
            gvec_angs,
            panel.rmat,
            np.eye(3),
            self.chi,
            panel.tvec,
            tvec_c,
            self.tvec_s,
            panel.distortion,
            beamVec=panel.bvec)
        xypts[on_plane] = valid_xys

        self.warp_dict[det] = panel.interpolate_bilinear(
            xypts, img, pad_with_nans=False).reshape(self.shape)
        return self.warp_dict[det]

    def generate_image(self):
        img = np.zeros(self.shape)
        for key in self.images_dict.keys():
            img += self.warp_dict[key]

        # ??? do log scaling here
        # img = log_scale_img(log_scale_img(sqrt_scale_img(img)))

        # Rescale the data to match the scale of the original dataset
        img = rescale_intensity(img, out_range=(self.min, self.max))

        if HexrdConfig().polar_apply_snip1d:
            self.snip1d_background = run_snip1d(img)
            # Perform the background subtraction
            img -= self.snip1d_background
        else:
            self.snip1d_background = None

        # Apply masks if they are present
        masks = HexrdConfig().polar_masks
        for mask in masks:
            img[~mask] = 0

        self.img = img

    def warp_all_images(self):
        # Cache the image max and min for later use
        images = self.images_dict.values()
        self.min = min([x.min() for x in images])
        self.max = max([x.max() for x in images])

        # Create the warped image for each detector
        for det in self.images_dict.keys():
            self.create_warp_image(det)

        # Generate the final image
        self.generate_image()

    def update_detector(self, det):
        # First, convert to the "None" angle convention
        iconfig = HexrdConfig().instrument_config_none_euler_convention

        t_conf = iconfig['detectors'][det]['transform']
        self.instr.detectors[det].tvec = t_conf['translation']
        self.instr.detectors[det].tilt = t_conf['tilt']

        # Update the individual detector image
        self.create_warp_image(det)

        # Generate the final image
        self.generate_image()
Example #6
0
class InstrumentViewer:

    def __init__(self):
        self.type = ViewType.cartesian
        self.instr = create_hedm_instrument()
        self.images_dict = HexrdConfig().current_images_dict()

        # Make sure each key in the image dict is in the panel_ids
        if self.images_dict.keys() != self.instr._detectors.keys():
            msg = ('Images do not match the panel ids!\n' +
                   'Images: ' + str(list(self.images_dict.keys())) + '\n' +
                   'PanelIds: ' + str(list(self.instr._detectors.keys())))
            raise Exception(msg)

        self.pixel_size = HexrdConfig().cartesian_pixel_size
        self.warp_dict = {}
        self.detector_corners = {}

        dist = HexrdConfig().cartesian_virtual_plane_distance
        dplane_tvec = np.array([0., 0., -dist])

        rotate_x = HexrdConfig().cartesian_plane_normal_rotate_x
        rotate_y = HexrdConfig().cartesian_plane_normal_rotate_y

        dplane_tilt = np.radians(np.array(([rotate_x, rotate_y, 0.])))

        self.dplane = DisplayPlane(tvec=dplane_tvec, tilt=dplane_tilt)
        self.make_dpanel()
        self.plot_dplane()

    def make_dpanel(self):
        self.dpanel_sizes = self.dplane.panel_size(self.instr)
        self.dpanel = self.dplane.display_panel(self.dpanel_sizes,
                                                self.pixel_size,
                                                self.instr.beam_vector)

    @property
    def extent(self):
        # We might want to use self.dpanel.col_edge_vec and
        # self.dpanel.row_edge_vec here instead.
        # !!! recall that extents are (left, right, bottom, top)
        x_lim = self.dpanel.col_dim / 2
        y_lim = self.dpanel.row_dim / 2
        return -x_lim, x_lim, -y_lim, y_lim

    def update_overlay_data(self):
        if not HexrdConfig().show_overlays:
            # Nothing to do
            return

        # must update self.dpanel from HexrdConfig
        self.pixel_size = HexrdConfig().cartesian_pixel_size
        self.make_dpanel()

        # The overlays for the Cartesian view are made via a fake
        # instrument with a single detector.
        # Make a copy of the instrument and modify.
        temp_instr = copy.deepcopy(self.instr)
        temp_instr._detectors.clear()
        temp_instr._detectors['dpanel'] = self.dpanel

        update_overlay_data(temp_instr, self.type)

    def plot_dplane(self):
        # Cache the image max and min for later use
        images = self.images_dict.values()
        self.min = min([x.min() for x in images])
        self.max = max([x.max() for x in images])

        # Create the warped image for each detector
        for detector_id in self.images_dict.keys():
            self.create_warped_image(detector_id)

        # Generate the final image
        self.generate_image()

    def detector_borders(self, det):
        corners = self.detector_corners.get(det, [])

        # These corners are in pixel coordinates. Convert to Cartesian.
        # Swap x and y first.
        corners = [[y, x] for x, y in corners]
        corners = self.dpanel.pixelToCart(corners)

        x_vals = [x[0] for x in corners]
        y_vals = [x[1] for x in corners]

        if x_vals and y_vals:
            # Double each set of points.
            # This is so that if a point is moved, it won't affect the
            # other line sharing this point.
            x_vals = [x for x in x_vals for _ in (0, 1)]
            y_vals = [y for y in y_vals for _ in (0, 1)]

            # Move the first point to the back to complete the line
            x_vals += [x_vals.pop(0)]
            y_vals += [y_vals.pop(0)]

            # Make sure all points are inside the frame.
            # If there are points outside the frame, move them inside.
            extent = self.extent
            x_range = (extent[0], extent[1])
            y_range = (extent[2], extent[3])

            def out_of_frame(p):
                # Check if point p is out of the frame
                return (not x_range[0] <= p[0] <= x_range[1] or
                        not y_range[0] <= p[1] <= y_range[1])

            def move_point_into_frame(p, p2):
                # Make sure we don't divide by zero
                if p2[0] == p[0]:
                    return

                # Move point p into the frame via its line equation
                # y = mx + b
                m = (p2[1] - p[1]) / (p2[0] - p[0])
                b = p[1] - m * p[0]
                if p[0] < x_range[0]:
                    p[0] = x_range[0]
                    p[1] = m * p[0] + b
                elif p[0] > x_range[1]:
                    p[0] = x_range[1]
                    p[1] = m * p[0] + b

                if p[1] < y_range[0]:
                    p[1] = y_range[0]
                    p[0] = (p[1] - b) / m
                elif p[1] > y_range[1]:
                    p[1] = y_range[1]
                    p[0] = (p[1] - b) / m

            i = 0
            while i < len(x_vals) - 1:
                # We look at pairs of points at a time
                p1 = [x_vals[i], y_vals[i]]
                p2 = [x_vals[i + 1], y_vals[i + 1]]

                if out_of_frame(p1):
                    # Move the point into the frame via its line equation
                    move_point_into_frame(p1, p2)
                    x_vals[i], y_vals[i] = p1[0], p1[1]

                    # Insert a pair of Nones to disconnect the drawing
                    x_vals.insert(i, None)
                    y_vals.insert(i, None)
                    i += 1

                if out_of_frame(p2):
                    # Move the point into the frame via its line equation
                    move_point_into_frame(p2, p1)
                    x_vals[i + 1], y_vals[i + 1] = p2[0], p2[1]

                i += 2

        # The border drawer expects a list of lines.
        # We only have one line here.
        return [(x_vals, y_vals)]

    @property
    def all_detector_borders(self):
        borders = {}
        for det in self.images_dict.keys():
            borders[det] = self.detector_borders(det)

        return borders

    def create_warped_image(self, detector_id):
        img = self.images_dict[detector_id]
        panel = self.instr._detectors[detector_id]

        # map corners
        corners = np.vstack(
            [panel.corner_ll,
             panel.corner_lr,
             panel.corner_ur,
             panel.corner_ul,
             ]
        )
        mp = panel.map_to_plane(corners, self.dplane.rmat,
                                self.dplane.tvec)

        col_edges = self.dpanel.col_edge_vec
        row_edges = self.dpanel.row_edge_vec
        j_col = cellIndices(col_edges, mp[:, 0])
        i_row = cellIndices(row_edges, mp[:, 1])

        src = np.vstack([j_col, i_row]).T

        # Save detector corners in pixel coordinates
        self.detector_corners[detector_id] = src

        dst = panel.cartToPixel(corners, pixels=True)
        dst = dst[:, ::-1]

        tform3 = tf.ProjectiveTransform()
        tform3.estimate(src, dst)

        res = tf.warp(img, tform3,
                      output_shape=(self.dpanel.rows, self.dpanel.cols))
        self.warp_dict[detector_id] = res
        return res

    def generate_image(self):
        img = np.zeros((self.dpanel.rows, self.dpanel.cols))
        for key in self.images_dict.keys():
            img += self.warp_dict[key]

        # Rescale the data to match the scale of the original dataset
        # TODO: try to get create_calibration_image to not rescale the
        # result to be between 0 and 1 in the first place so this will
        # not be necessary.
        self.img = np.interp(img, (img.min(), img.max()), (self.min, self.max))

    def update_detector(self, det):
        # First, convert to the "None" angle convention
        iconfig = HexrdConfig().instrument_config_none_euler_convention

        t_conf = iconfig['detectors'][det]['transform']
        self.instr.detectors[det].tvec = t_conf['translation']
        self.instr.detectors[det].tilt = t_conf['tilt']

        # Update the individual detector image
        self.create_warped_image(det)

        # Generate the final image
        self.generate_image()