Example #1
0
 def points_from_axis_point(dim, index, length):
     points = Points()
     dimension_points = {axis: np.full(length, dim.positions[axis][index]) for axis in dim.positions}
     lower, upper = dim.get_bounds(index)
     points.positions.update(dimension_points)
     points.lower.update({axis: np.full(length, lower[axis]) for axis in lower})
     points.upper.update({axis: np.full(length, upper[axis]) for axis in upper})
     points.indexes = np.full(length, index)
     return points
Example #2
0
    def get_points(self, start, finish):
        """
        Retrieve a Points object: a wrapper for an array of Point from the generator

        Args:
            start (int), finish (int): indices of the first point and final+1th point to include
            i.e. get_points(1, 5) would return a Points of Point 1, 2, 3 & 4 but not 5.
        Returns:
            Points: a wrapper object with the data of the requested Point [plural]
        """
        if not self._prepared:
            raise ValueError("CompoundGenerator has not been prepared")
        ''' 
        situations:
            dim N constant, dim N+1 constant (e.g. 1,1 -> 1,1)
            dim N constant, dim N+1 increasing: (e.g. n,1->n,5)
            dim N increasing, dim N+1 increasing: (n,1->n+1,2) N[n],N+1[start]->N[n],N+1[max]->N[n+1],N+1[0]->...->N[K],N+1[finish]
            dim N increasing, dim N+1 decreasing: (n,2->n+1,1) as above
            dim N increasing, dim N+1 constant: (n,1->n+1,1) as above
            for each pair of consecutive dim N, N+1
                => must be first dim M where changes (even if it's outermost).
                => All dimensions outside M must have single point
                => M must be within single dimension "run"
                => All dimensions inside M must tile
                => M behaves like all dimensions within it
            innermost dim must be moving
        '''
        if finish == start:
            return Points()
        indices = np.arange(start, finish, np.sign(finish - start))
        indices = np.where(indices < 0, indices + self.size, indices)
        if max(indices) >= self.size:
            raise IndexError("Requested points extend out of range")
        length = len(indices)
        points = Points()

        for dim in self.dimensions:
            point_repeat = int(self._dim_meta[dim]["repeat"])
            point_indices = indices // point_repeat  # Number of point this step is on
            found_m = np.any(
                point_indices != point_indices[0])  # For alternating case
            if found_m:
                points.extract(self._points_from_below_m(dim, point_indices))
            else:
                points.extract(
                    self._points_above_m(dim, point_indices[0], length))
        points.duration = np.full(length, self.duration)
        points.delay_after = np.full(length, self.delay_after)
        for m in self.mutators:
            points = m.mutate(points, indices)
        return points
 def test_simple_points(self):
     points = self.comp.get_points(7, 12)
     self.assertEqual(
         list([[0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 0], [0, 2, 1]]),
         points.indexes.tolist())
     # Lower = Bounds for outer dimension
     self.assertEqual(list([0., 0., 0., 0., 0.]),
                      points.lower['x'].tolist())
     # Lower != bounds for innermost dimension
     self.assertEqual(list([1.25, 1.25, 1.25, 2.5, 2.5]),
                      points.lower['y'].tolist())
     self.assertEqual(list([1.875, 3.125, 4.375, -0.625, 0.625]),
                      points.lower['z'].tolist())
     self.assertTrue(np.all(np.full(5, 5) == points.duration))
     self.assertTrue(np.all(np.full(5, 7) == points.delay_after))
Example #4
0
    def mask_points(self, points, epsilon=1e-15):
        cphi = cos(self.angle)
        sphi = sin(self.angle)
        x = points[0] - self.start[0]
        y = points[1] - self.start[1]

        # test for being past segment end-points
        dp = x * cphi + y * sphi
        mask = np.full(len(x), True, dtype=np.int8)
        mask &= dp >= 0
        mask &= dp <= self.length

        # distance is scalar projection (dot-product) of
        # point difference to line normal (normal = (sphi, -cphi))
        dp = np.abs(x * sphi + y * -cphi)
        mask &= dp < epsilon

        # include all points in an epsilon circle around endpoints
        x *= x
        y *= y
        mask |= x + y <= epsilon * epsilon
        x = points[0] - (self.start[0] + self.length * cphi)
        y = points[1] - (self.start[1] + self.length * sphi)
        x *= x
        y *= y
        mask |= x + y <= epsilon * epsilon
        return mask
Example #5
0
 def test_apply_excluders_over_multiple_gens(self):
     gx_pos = np.array([1, 2, 3, 4, 5])
     gy_pos = np.zeros(5)
     hx_pos = np.zeros(5)
     hy_pos = np.array([-1, -2, -3])
     mask = np.full(15, 1, dtype=np.int8)
     e = Mock(axes=["gx", "hy"], create_mask=Mock(return_value=mask))
     g = Mock(axes=["gx", "gy"],
              positions={
                  "gx": gx_pos,
                  "gy": gy_pos
              },
              size=len(gx_pos),
              alternate=False)
     h = Mock(axes=["hx", "hy"],
              positions={
                  "hx": hx_pos,
                  "hy": hy_pos
              },
              size=len(hy_pos),
              alternate=False)
     d = Dimension(g)
     d.generators = [g, h]
     d.size = g.size * h.size
     d.apply_excluder(e)
     d._masks[0]["mask"] = d._masks[0]["mask"].tolist()
     self.assertEqual([{
         "repeat": 1,
         "tile": 1,
         "mask": mask.tolist()
     }], d._masks)
     self.assertTrue((np.repeat(np.array([1, 2, 3, 4, 5]),
                                3) == e.create_mask.call_args[0][0]).all())
     self.assertTrue((np.tile(np.array([-1, -2, -3]),
                              5) == e.create_mask.call_args[0][1]).all())
Example #6
0
 def mask_points(self, points):
     x = points[0]
     y = points[1]
     v1x, v1y = self.points_x[-1], self.points_y[-1]
     mask = np.full(len(x), False, dtype=np.int8)
     for v2x, v2y in zip(self.points_x, self.points_y):
         # skip horizontal edges
         if (v2y != v1y):
             vmask = np.full(len(x), False, dtype=np.int8)
             vmask |= ((y < v2y) & (y >= v1y))
             vmask |= ((y < v1y) & (y >= v2y))
             t = (y - v1y) / (v2y - v1y)
             vmask &= x < v1x + t * (v2x - v1x)
             mask ^= vmask
         v1x, v1y = v2x, v2y
     return mask
    def prepare(self):
        """
        Create and return a mask for every point in the dimension

        e.g. (with [y1, y2, y3] and [x1, x2, x3] both alternating)
        y:    y1, y1, y1, y2, y2, y2, y3, y3, y3
        x:    x1, x2, x3, x3, x2, x1, x1, x2, x3
        mask: m1, m2, m3, m4, m5, m6, m7, m8, m9

        Returns:
            np.array(int8): One dimensional mask array
        """
        if self._prepared:
            return
        mask = np.full(self._max_length, True, dtype=np.int8)
        for m in self._masks:
            assert len(m["mask"]) * m["repeat"] * m["tile"] == len(mask), \
                "Mask lengths are not consistent"
            expanded = np.repeat(m["mask"], m["repeat"])
            if m["tile"] % 1 != 0:
                ex = np.tile(expanded, int(m["tile"]))
                expanded = np.append(ex, expanded[:int(len(expanded) // 2)])
            else:
                expanded = np.tile(expanded, int(m["tile"]))
            mask &= expanded
        # we have to assume the "returned" mask may be edited in place
        # so we have to store a copy
        self.mask = mask
        self.indices = self.mask.nonzero()[0]
        self.size = len(self.indices)
        self._prepared = True
Example #8
0
 def mask_points(self, points):
     x = points[0]
     y = points[1]
     v1 = self.points[-1]
     mask = np.full(len(x), False, dtype=bool)
     for i in range_(0, len(self.points)):
         v2 = self.points[i]
         if (v2[1] == v1[1]):
             # skip horizontal edges
             v1 = v2
             continue
         vmask = ((y < v2[1]) & (y >= v1[1])) | ((y < v1[1]) & (y >= v2[1]))
         t = (y - v2[1]) / (v2[1] - v1[1])
         vmask &= x < v1[0] + t * (v2[0] - v1[0])
         mask ^= vmask
         v1 = v2
     return mask
Example #9
0
 def test_apply_excluders_with_scaling(self):
     g1_pos = np.array([1, 2, 3])
     g2_pos = np.array([-1, -2])
     mask_func = lambda px, py: np.full(len(px), 1, dtype=np.int8)
     g1 = Mock(axes=["g1"], positions={"g1":g1_pos}, size=len(g1_pos))
     g2 = Mock(axes=["g2"], positions={"g2":g2_pos}, size=len(g2_pos))
     e = Mock(axes=["g1", "g2"], create_mask=Mock(side_effect=mask_func))
     d = Dimension(g1)
     d.alternate = True
     d.generators = [Mock(size=5, axes=[]), g1, g2, Mock(size=7, axes=[])]
     d.size = 5 * len(g1_pos) * len(g2_pos) * 7
     d.apply_excluder(e)
     d._masks[0]["mask"] = d._masks[0]["mask"].tolist()
     expected_mask = [1] * 12
     self.assertEqual([{"repeat":7, "tile":2.5, "mask":expected_mask}], d._masks)
     self.assertTrue((np.repeat(np.append(g1_pos, g1_pos[::-1]), 2) == e.create_mask.call_args[0][0]).all())
     self.assertTrue((np.tile(np.append(g2_pos, g2_pos[::-1]), 3) == e.create_mask.call_args[0][1]).all())
 def mask_points(self, points):
     x = points[0].copy()
     y = points[1].copy()
     x -= self.centre[0]
     y -= self.centre[1]
     r2 = (np.square(x) + np.square(y))
     phi_0, phi_1 = self.constrain_angles(self.angles)
     # phi_0 <= phi_1, phi_0 in [0, 2pi), phi_1 < 4pi
     phi_x = np.arctan2(y, x)
     # translate phi_x to range [0, 2pi]
     phi_x = (2 * pi + phi_x) % (2 * pi)
     # define phi_s and phi_x "offset from phi_0"
     phi_s = phi_1 - phi_0
     phi_x -= phi_0 + 2 * pi
     phi_x %= 2 * pi
     mask = np.full(len(x), 1, dtype=np.int8)
     mask &= r2 <= self.radii[1]
     mask &= r2 >= self.radii[0]
     mask &= (phi_x <= phi_s)
     return mask
Example #11
0
    def prepare(self):
        self.num = 1
        self.dimensions = []
        # we're going to mutate these structures
        excluders = list(self.excluders)
        generators = list(self.generators)

        # special case if we have rectangular regions on line generators
        # we should restrict the resulting grid rather than merge dimensions
        # this changes the alternating case a little (without doing this, we
        # may have started in reverse direction)
        for rect in [r for r in excluders \
                if isinstance(r.roi, RectangularROI) and r.roi.angle == 0]:
            axis_1, axis_2 = rect.scannables[0], rect.scannables[1]
            gen_1 = [g for g in generators if axis_1 in g.axes][0]
            gen_2 = [g for g in generators if axis_2 in g.axes][0]
            if gen_1 is gen_2:
                continue
            if isinstance(gen_1, LineGenerator) \
                    and isinstance(gen_2, LineGenerator):
                gen_1.produce_points()
                gen_2.produce_points()
                valid = np.full(gen_1.num, True, dtype=np.int8)
                valid &= gen_1.points[
                    axis_1] <= rect.roi.width + rect.roi.start[0]
                valid &= gen_1.points[axis_1] >= rect.roi.start[0]
                points_1 = gen_1.points[axis_1][valid.astype(np.bool)]
                valid = np.full(gen_2.num, True, dtype=np.int8)
                valid &= gen_2.points[
                    axis_2] <= rect.roi.height + rect.roi.start[1]
                valid &= gen_2.points[axis_2] >= rect.roi.start[1]
                points_2 = gen_2.points[axis_2][valid.astype(np.bool)]
                new_gen1 = LineGenerator(gen_1.name, gen_1.units, points_1[0],
                                         points_1[-1], len(points_1),
                                         gen_1.alternate_direction)
                new_gen2 = LineGenerator(gen_2.name, gen_2.units, points_2[0],
                                         points_2[-1], len(points_2),
                                         gen_2.alternate_direction)
                generators[generators.index(gen_1)] = new_gen1
                generators[generators.index(gen_2)] = new_gen2
                excluders.remove(rect)

        for generator in generators:
            generator.produce_points()
            self.axes_points.update(generator.points)
            self.axes_points_lower.update(generator.points_lower)
            self.axes_points_upper.update(generator.points_upper)
            self.num *= generator.num

            dim = {
                "size": generator.num,
                "axes": list(generator.axes),
                "generators": [generator],
                "masks": [],
                "tile": 1,
                "repeat": 1,
                "alternate": generator.alternate_direction
            }
            self.dimensions.append(dim)

        for excluder in excluders:
            axis_1, axis_2 = excluder.scannables
            # ensure axis_1 is "outer" axis (if separate generators)
            gen_1 = [g for g in generators if axis_1 in g.axes][0]
            gen_2 = [g for g in generators if axis_2 in g.axes][0]
            gen_diff = generators.index(gen_1) \
                - generators.index(gen_2)
            if gen_diff < -1 or gen_diff > 1:
                raise ValueError(
                    "Excluders must be defined on axes that are adjacent in " \
                        "generator order")
            if gen_diff == 1:
                gen_1, gen_2 = gen_2, gen_1
                axis_1, axis_2 = axis_2, axis_1
                gen_diff = -1

            #####
            # first check if region spans two dimensions - merge if so
            #####
            dim_1 = [i for i in self.dimensions if axis_1 in i["axes"]][0]
            dim_2 = [i for i in self.dimensions if axis_2 in i["axes"]][0]
            dim_diff = self.dimensions.index(dim_1) \
                - self.dimensions.index(dim_2)
            if dim_diff < -1 or dim_diff > 1:
                raise ValueError(
                    "Excluders must be defined on axes that are adjacent in " \
                        "generator order")
            if dim_diff == 1:
                dim_1, dim_2 = dim_2, dim_1
                dim_diff = -1
            if dim_1["alternate"] != dim_2["alternate"] \
                    and dim_1 is not self.dimensions[0]:
                raise ValueError(
                    "Generators tied by regions must have the same " \
                            "alternate_direction setting")
            # merge "inner" into "outer"
            if dim_diff == -1:
                # dim_1 is "outer" - preserves axis ordering

                # need to appropriately scale the existing masks
                # masks are "tiled" by the size of generators "below" them
                # and their elements are "repeated" by the size of generators
                # above them, so:
                # |mask| * duplicates * repeates == |generators in index|
                scale = 1
                for g in dim_2["generators"]:
                    scale *= g.num
                for m in dim_1["masks"]:
                    m["repeat"] *= scale
                scale = 1
                for g in dim_1["generators"]:
                    scale *= g.num
                for m in dim_2["masks"]:
                    m["tile"] *= scale
                dim_1["masks"] += dim_2["masks"]
                dim_1["axes"] += dim_2["axes"]
                dim_1["generators"] += dim_2["generators"]
                dim_1["size"] *= dim_2["size"]
                dim_1["alternate"] |= dim_2["alternate"]
                self.dimensions.remove(dim_2)
            dim = dim_1

            #####
            # generate the mask for this region
            #####
            # if gen_1 and gen_2 are different then the outer axis will have to
            # have its elements repeated and the inner axis will have to have
            # itself repeated - gen_1 is always inner axis

            points_1 = self.axes_points[axis_1]
            points_2 = self.axes_points[axis_2]

            doubled_mask = False  # used for some cases of alternating generators

            if gen_1 is gen_2 and dim["alternate"]:
                # run *both* axes backwards
                # but our mask will be a factor of 2 too big
                doubled_mask = True
                points_1 = np.append(points_1, points_1[::-1])
                points_2 = np.append(points_2, points_2[::-1])
            elif dim["alternate"]:
                doubled_mask = True
                points_1 = np.append(points_1, points_1[::-1])
                points_2 = np.append(points_2, points_2[::-1])
                points_2 = np.tile(points_2, gen_1.num)
                points_1 = np.repeat(points_1, gen_2.num)
            elif gen_1 is not gen_2:
                points_1 = np.repeat(points_1, gen_2.num)
                points_2 = np.tile(points_2, gen_1.num)
            else:
                # copy the points arrays anyway so the regions can
                # safely perform any array operations in place
                # this is advantageous in the cases above
                points_1 = np.copy(points_1)
                points_2 = np.copy(points_2)

            if axis_1 == excluder.scannables[0]:
                mask = excluder.create_mask(points_1, points_2)
            else:
                mask = excluder.create_mask(points_2, points_1)

            #####
            # Add new mask to index
            #####
            tile = 0.5 if doubled_mask else 1
            repeat = 1
            found_axis = False
            # tile by product of generators "before"
            # repeat by product of generators "after"
            for g in dim["generators"]:
                if axis_1 in g.axes or axis_2 in g.axes:
                    found_axis = True
                else:
                    if found_axis:
                        repeat *= g.num
                    else:
                        tile *= g.num
            m = {"repeat": repeat, "tile": tile, "mask": mask}
            dim["masks"].append(m)
        # end for excluder in excluders
        #####

        tile = 1
        repeat = 1
        #####
        # Generate full index mask and "apply"
        #####
        for dim in self.dimensions:
            mask = np.full(dim["size"], True, dtype=np.int8)
            for m in dim["masks"]:
                assert len(m["mask"]) * m["repeat"] * m["tile"] == len(mask), \
                        "Mask lengths are not consistent"
                expanded = np.repeat(m["mask"], m["repeat"])
                if m["tile"] % 1 != 0:
                    ex = np.tile(expanded, int(m["tile"]))
                    expanded = np.append(ex, expanded[:len(expanded) // 2])
                else:
                    expanded = np.tile(expanded, int(m["tile"]))
                mask &= expanded
            dim["mask"] = mask
            dim["indicies"] = np.nonzero(mask)[0]
            if len(dim["indicies"]) == 0:
                raise ValueError("Regions would exclude entire scan")
            repeat *= len(dim["indicies"])
        self.num = repeat
        for dim in self.dimensions:
            l = len(dim["indicies"])
            repeat /= l
            dim["tile"] = tile
            dim["repeat"] = repeat
            tile *= l

        for dim in self.dimensions:
            tile = 1
            repeat = 1
            for g in dim["generators"]:
                repeat *= g.num
            for g in dim["generators"]:
                repeat /= g.num
                d = {"tile": tile, "repeat": repeat}
                tile *= g.num
                self.generator_dim_scaling[g] = d
Example #12
0
    def prepare(self):
        """
        Prepare data structures required for point generation and
        initialize size, shape, and dimensions attributes.
        Must be called before get_point or iterator are called.
        """
        if self._prepared:
            return
        self.dimensions = []
        self._dim_meta = {}
        self._generator_dim_scaling = {}

        # we're going to mutate these structures
        excluders = list(self.excluders)
        generators = list(self.generators)

        # special case if we have rectangular regions on line generators
        # we should restrict the resulting grid rather than merge dimensions
        # this changes the alternating case a little (without doing this, we
        # may have started in reverse direction)
        for excluder_ in [e for e in excluders if isinstance(e, ROIExcluder)]:
            if len(excluder_.rois) == 1 \
                    and isinstance(excluder_.rois[0], RectangularROI) \
                    and excluder_.rois[0].angle == 0:
                rect = excluder_.rois[0]
                axis_1, axis_2 = excluder_.axes[0], excluder_.axes[1]
                gen_1 = [g for g in generators if axis_1 in g.axes][0]
                gen_2 = [g for g in generators if axis_2 in g.axes][0]
                if gen_1 is gen_2:
                    continue
                if isinstance(gen_1, LineGenerator) \
                        and isinstance(gen_2, LineGenerator):
                    gen_1.prepare_positions()
                    gen_2.prepare_positions()
                    # Filter by axis 1
                    valid = np.full(gen_1.size, True, dtype=np.int8)
                    valid &= \
                        gen_1.positions[axis_1] <= rect.width + rect.start[0]
                    valid &= \
                        gen_1.positions[axis_1] >= rect.start[0]
                    points_1 = gen_1.positions[axis_1][valid.astype(np.bool)]
                    # Filter by axis 2
                    valid = np.full(gen_2.size, True, dtype=np.int8)
                    valid &= \
                        gen_2.positions[axis_2] <= rect.height + rect.start[1]
                    valid &= gen_2.positions[axis_2] >= rect.start[1]
                    points_2 = gen_2.positions[axis_2][valid.astype(np.bool)]
                    # Recreate generators to replace larger generators + ROI
                    new_gen1 = LineGenerator(gen_1.axes, gen_1.units,
                                             points_1[0], points_1[-1],
                                             len(points_1), gen_1.alternate)
                    new_gen2 = LineGenerator(gen_2.axes, gen_2.units,
                                             points_2[0], points_2[-1],
                                             len(points_2), gen_2.alternate)
                    generators[generators.index(gen_1)] = new_gen1
                    generators[generators.index(gen_2)] = new_gen2
                    # Remove Excluder as it is now empty
                    excluders.remove(excluder_)

        for generator in generators:
            generator.prepare_positions()
            self.dimensions.append(Dimension(generator))
        # only the inner-most generator needs to have bounds calculated
        if self.continuous:
            generators[-1].prepare_bounds()

        for excluder in excluders:
            matched_dims = [
                d for d in self.dimensions
                if len(set(d.axes) & set(excluder.axes)) != 0
            ]
            if len(matched_dims) == 0:
                raise ValueError(
                    "Excluder references axes that have not been provided by generators: %s"
                    % str(excluder.axes))
            d_start = self.dimensions.index(matched_dims[0])
            d_end = self.dimensions.index(matched_dims[-1])
            if d_start != d_end:
                # merge all excluders between d_start and d_end (inclusive)
                alternate = self.dimensions[d_end].alternate
                # verify consistent alternate settings (ignoring outermost dimesion where it doesn't matter)
                for d in self.dimensions[max(1, d_start):d_end]:
                    # filter out dimensions consisting of a single NullPointGenerator, since alternation means nothing
                    if len(d.generators) == 1 and isinstance(
                            d.generators[0], NullPointGenerator):
                        continue
                    if alternate != d.alternate:
                        raise ValueError(
                            "Nested generators connected by regions must have the same alternate setting"
                        )
                merged_dim = Dimension.merge_dimensions(
                    self.dimensions[d_start:d_end + 1])
                self.dimensions = self.dimensions[:d_start] + [
                    merged_dim
                ] + self.dimensions[d_end + 1:]
                dim = merged_dim
            else:
                dim = self.dimensions[d_start]
            dim.apply_excluder(excluder)

        self.size = 1
        for dim in self.dimensions:
            self._dim_meta[dim] = {}
            dim.prepare()
            if dim.size == 0:
                raise ValueError("Regions would exclude entire scan")
            self.size *= dim.size

        self.shape = tuple(dim.size for dim in self.dimensions)
        repeat = self.size
        tile = 1
        for dim in self.dimensions:
            repeat /= dim.size
            self._dim_meta[dim]["tile"] = tile
            self._dim_meta[dim]["repeat"] = repeat
            tile *= dim.size

        for dim in self.dimensions:
            tile = 1
            repeat = dim._max_length
            for g in dim.generators:
                repeat /= g.size
                d = {"tile": tile, "repeat": repeat}
                tile *= g.size
                self._generator_dim_scaling[g] = d

        self._prepared = True
Example #13
0
    def prepare(self):
        """
        Prepare data structures required for point generation and
        initialize size, shape, and dimensions attributes.
        Must be called before get_point or iterator are called.
        """
        if self._prepared:
            return
        self.dimensions = []
        self._dim_meta = {}
        self._generator_dim_scaling = {}

        # we're going to mutate these structures
        excluders = list(self.excluders)
        generators = list(self.generators)

        # special case if we have rectangular regions on line generators
        # we should restrict the resulting grid rather than merge dimensions
        # this changes the alternating case a little (without doing this, we
        # may have started in reverse direction)
        for excluder_ in [e for e in excluders if isinstance(e, ROIExcluder)]:
            if len(excluder_.rois) == 1 \
                    and isinstance(excluder_.rois[0], RectangularROI) \
                    and excluder_.rois[0].angle == 0:
                rect = excluder_.rois[0]
                axis_1, axis_2 = excluder_.axes[0], excluder_.axes[1]
                gen_1 = [g for g in generators if axis_1 in g.axes][0]
                gen_2 = [g for g in generators if axis_2 in g.axes][0]
                if gen_1 is gen_2:
                    continue
                if isinstance(gen_1, LineGenerator) \
                        and isinstance(gen_2, LineGenerator):
                    gen_1.prepare_positions()
                    gen_2.prepare_positions()
                    # Filter by axis 1
                    valid = np.full(gen_1.size, True, dtype=np.int8)
                    valid &= \
                        gen_1.positions[axis_1] <= rect.width + rect.start[0]
                    valid &= \
                        gen_1.positions[axis_1] >= rect.start[0]
                    points_1 = gen_1.positions[axis_1][valid.astype(np.bool)]
                    # Filter by axis 2
                    valid = np.full(gen_2.size, True, dtype=np.int8)
                    valid &= \
                        gen_2.positions[axis_2] <= rect.height + rect.start[1]
                    valid &= gen_2.positions[axis_2] >= rect.start[1]
                    points_2 = gen_2.positions[axis_2][valid.astype(np.bool)]
                    # Recreate generators to replace larger generators + ROI
                    new_gen1 = LineGenerator(gen_1.axes, gen_1.units,
                                             points_1[0], points_1[-1],
                                             len(points_1), gen_1.alternate)
                    new_gen2 = LineGenerator(gen_2.axes, gen_2.units,
                                             points_2[0], points_2[-1],
                                             len(points_2), gen_2.alternate)
                    generators[generators.index(gen_1)] = new_gen1
                    generators[generators.index(gen_2)] = new_gen2
                    # Remove Excluder as it is now empty
                    excluders.remove(excluder_)

        for generator in generators:
            generator.prepare_positions()
            self.dimensions.append(Dimension(generator))
        # only the inner-most generator needs to have bounds calculated
        generators[-1].prepare_bounds()

        for excluder in excluders:
            axis_1, axis_2 = excluder.axes
            gen_1 = [g for g in generators if axis_1 in g.axes][0]
            gen_2 = [g for g in generators if axis_2 in g.axes][0]
            gen_diff = generators.index(gen_1) \
                - generators.index(gen_2)
            if gen_diff < -1 or gen_diff > 1:
                raise ValueError(
                    "Excluders must be defined on axes that are adjacent in " \
                        "generator order")

            # merge dimensions if region spans two
            dim_1 = [i for i in self.dimensions if axis_1 in i.axes][0]
            dim_2 = [i for i in self.dimensions if axis_2 in i.axes][0]
            dim_diff = self.dimensions.index(dim_1) \
                - self.dimensions.index(dim_2)
            if dim_diff == 1:
                dim_1, dim_2 = dim_2, dim_1
                dim_diff = -1
            if dim_1.alternate != dim_2.alternate \
                    and dim_1 is not self.dimensions[0]:
                raise ValueError(
                    "Generators tied by regions must have the same " \
                            "alternate setting")
            # merge "inner" into "outer"
            if dim_diff == -1:
                # dim_1 is "outer" - preserves axis ordering
                new_dim = Dimension.merge_dimensions(dim_1, dim_2)
                self.dimensions[self.dimensions.index(dim_1)] = new_dim
                self.dimensions.remove(dim_2)
                dim = new_dim
            else:
                dim = dim_1

            dim.apply_excluder(excluder)

        self.size = 1
        for dim in self.dimensions:
            self._dim_meta[dim] = {}
            dim.prepare()
            if dim.size == 0:
                raise ValueError("Regions would exclude entire scan")
            self.size *= dim.size

        self.shape = tuple(dim.size for dim in self.dimensions)
        repeat = self.size
        tile = 1
        for dim in self.dimensions:
            repeat /= dim.size
            self._dim_meta[dim]["tile"] = tile
            self._dim_meta[dim]["repeat"] = repeat
            tile *= dim.size

        for dim in self.dimensions:
            tile = 1
            repeat = dim._max_length
            for g in dim.generators:
                repeat /= g.size
                d = {"tile": tile, "repeat": repeat}
                tile *= g.size
                self._generator_dim_scaling[g] = d

        self._prepared = True
Example #14
0
    def prepare(self):
        """
        Prepare data structures required to determine size and
        filtered positions of the dimension.
        Must be called before get_positions or get_mesh_map are called.
        """
        axis_positions = {}
        axis_bounds_lower = {}
        axis_bounds_upper = {}
        masks = []
        # scale up all position arrays
        # inner generators are tiled by the size of out generators
        # outer generators have positions repeated by the size of inner generators
        repeats, tilings, dim_size = 1, 1, 1
        for g in self.generators:
            repeats *= g.size
            dim_size *= g.size

        for gen in self.generators:
            repeats /= gen.size
            for axis in gen.axes:
                positions = gen.positions[axis]
                if gen.alternate:
                    positions = np.append(positions, positions[::-1])
                    positions = np.repeat(positions, repeats)
                    p = np.tile(positions, (tilings // 2))
                    if tilings % 2 != 0:
                        positions = np.append(
                            p, positions[:int(len(positions) // 2)])
                    else:
                        positions = p
                else:
                    positions = np.repeat(positions, repeats)
                    positions = np.tile(positions, tilings)
                axis_positions[axis] = positions
            tilings *= gen.size

        # produce excluder masks
        for excl in self.excluders:
            arrays = [axis_positions[axis] for axis in excl.axes]
            excluder_mask = excl.create_mask(*arrays)
            masks.append(excluder_mask)

        # AND all masks together (empty mask is all values selected)
        mask = masks[0] if len(masks) else np.full(
            dim_size, True, dtype=np.int8)
        for m in masks[1:]:
            mask &= m

        gen = self.generators[-1]
        if getattr(gen, "bounds", None):
            tilings = np.prod(np.array([g.size for g in self.generators[:-1]]))
            if gen.alternate:
                tilings /= 2.
            for axis in gen.axes:
                upper_base = gen.bounds[axis][1:]
                lower_base = gen.bounds[axis][:-1]
                upper, lower = upper_base, lower_base
                if gen.alternate:
                    upper = np.append(upper_base, lower_base[::-1])
                    lower = np.append(lower_base, upper_base[::-1])
                upper = np.tile(upper, int(tilings))
                lower = np.tile(lower, int(tilings))
                if tilings % 1 != 0:
                    upper = np.append(upper, upper_base)
                    lower = np.append(lower, lower_base)
                axis_bounds_upper[axis] = upper
                axis_bounds_lower[axis] = lower

        self.mask = mask
        self.indices = self.mask.nonzero()[0]
        self.size = len(self.indices)
        self.positions = {
            axis: axis_positions[axis][self.indices]
            for axis in axis_positions
        }
        self.upper_bounds = {
            axis: self.positions[axis]
            for axis in self.positions
        }
        self.lower_bounds = {
            axis: self.positions[axis]
            for axis in self.positions
        }
        for axis in axis_bounds_lower:
            self.upper_bounds[axis] = axis_bounds_upper[axis][self.indices]
            self.lower_bounds[axis] = axis_bounds_lower[axis][self.indices]
        self._prepared = True
    def prepare(self):
        self.num = 1
        self.dimensions = []
        # we're going to mutate these structures
        excluders = list(self.excluders)
        generators = list(self.generators)

        # special case if we have rectangular regions on line generators
        # we should restrict the resulting grid rather than merge dimensions
        # this changes the alternating case a little (without doing this, we
        # may have started in reverse direction)
        for rect in [r for r in excluders \
                if isinstance(r.roi, RectangularROI) and r.roi.angle == 0]:
            axis_1, axis_2 = rect.scannables[0], rect.scannables[1]
            gen_1 = [g for g in generators if axis_1 in g.axes][0]
            gen_2 = [g for g in generators if axis_2 in g.axes][0]
            if gen_1 is gen_2:
                continue
            if isinstance(gen_1, LineGenerator) \
                    and isinstance(gen_2, LineGenerator):
                gen_1.produce_points()
                gen_2.produce_points()
                valid = np.full(gen_1.num, True, dtype=np.int8)
                valid &= gen_1.points[axis_1] <= rect.roi.width + rect.roi.start[0]
                valid &= gen_1.points[axis_1] >= rect.roi.start[0]
                points_1 = gen_1.points[axis_1][valid.astype(np.bool)]
                valid = np.full(gen_2.num, True, dtype=np.int8)
                valid &= gen_2.points[axis_2] <= rect.roi.height + rect.roi.start[1]
                valid &= gen_2.points[axis_2] >= rect.roi.start[1]
                points_2 = gen_2.points[axis_2][valid.astype(np.bool)]
                new_gen1 = LineGenerator(
                    gen_1.name, gen_1.units, points_1[0], points_1[-1],
                    len(points_1), gen_1.alternate_direction)
                new_gen2 = LineGenerator(
                    gen_2.name, gen_2.units, points_2[0], points_2[-1],
                    len(points_2), gen_2.alternate_direction)
                generators[generators.index(gen_1)] = new_gen1
                generators[generators.index(gen_2)] = new_gen2
                excluders.remove(rect)

        for generator in generators:
            generator.produce_points()
            self.axes_points.update(generator.points)
            self.axes_points_lower.update(generator.points_lower)
            self.axes_points_upper.update(generator.points_upper)
            self.num *= generator.num

            dim = {"size":generator.num,
                "axes":list(generator.axes),
                "generators":[generator],
                "masks":[],
                "tile":1,
                "repeat":1,
                "alternate":generator.alternate_direction}
            self.dimensions.append(dim)

        for excluder in excluders:
            axis_1, axis_2 = excluder.scannables
            # ensure axis_1 is "outer" axis (if separate generators)
            gen_1 = [g for g in generators if axis_1 in g.axes][0]
            gen_2 = [g for g in generators if axis_2 in g.axes][0]
            gen_diff = generators.index(gen_1) \
                - generators.index(gen_2)
            if gen_diff < -1 or gen_diff > 1:
                raise ValueError(
                    "Excluders must be defined on axes that are adjacent in " \
                        "generator order")
            if gen_diff == 1:
                gen_1, gen_2 = gen_2, gen_1
                axis_1, axis_2 = axis_2, axis_1
                gen_diff = -1


            #####
            # first check if region spans two dimensions - merge if so
            #####
            dim_1 = [i for i in self.dimensions if axis_1 in i["axes"]][0]
            dim_2 = [i for i in self.dimensions if axis_2 in i["axes"]][0]
            dim_diff = self.dimensions.index(dim_1) \
                - self.dimensions.index(dim_2)
            if dim_diff < -1 or dim_diff > 1:
                raise ValueError(
                    "Excluders must be defined on axes that are adjacent in " \
                        "generator order")
            if dim_diff == 1:
                dim_1, dim_2 = dim_2, dim_1
                dim_diff = -1
            if dim_1["alternate"] != dim_2["alternate"] \
                    and dim_1 is not self.dimensions[0]:
                raise ValueError(
                    "Generators tied by regions must have the same " \
                            "alternate_direction setting")
            # merge "inner" into "outer"
            if dim_diff == -1:
                # dim_1 is "outer" - preserves axis ordering

                # need to appropriately scale the existing masks
                # masks are "tiled" by the size of generators "below" them
                # and their elements are "repeated" by the size of generators
                # above them, so:
                # |mask| * duplicates * repeates == |generators in index|
                scale = 1
                for g in dim_2["generators"]:
                    scale *= g.num
                for m in dim_1["masks"]:
                    m["repeat"] *= scale
                scale = 1
                for g in dim_1["generators"]:
                    scale *= g.num
                for m in dim_2["masks"]:
                    m["tile"] *= scale
                dim_1["masks"] += dim_2["masks"]
                dim_1["axes"] += dim_2["axes"]
                dim_1["generators"] += dim_2["generators"]
                dim_1["size"] *= dim_2["size"]
                dim_1["alternate"] |= dim_2["alternate"]
                self.dimensions.remove(dim_2)
            dim = dim_1

            #####
            # generate the mask for this region
            #####
            # if gen_1 and gen_2 are different then the outer axis will have to
            # have its elements repeated and the inner axis will have to have
            # itself repeated - gen_1 is always inner axis

            points_1 = self.axes_points[axis_1]
            points_2 = self.axes_points[axis_2]

            doubled_mask = False # used for some cases of alternating generators

            if gen_1 is gen_2 and dim["alternate"]:
                # run *both* axes backwards
                # but our mask will be a factor of 2 too big
                doubled_mask = True
                points_1 = np.append(points_1, points_1[::-1])
                points_2 = np.append(points_2, points_2[::-1])
            elif dim["alternate"]:
                doubled_mask = True
                points_1 = np.append(points_1, points_1[::-1])
                points_2 = np.append(points_2, points_2[::-1])
                points_2 = np.tile(points_2, gen_1.num)
                points_1 = np.repeat(points_1, gen_2.num)
            elif gen_1 is not gen_2:
                points_1 = np.repeat(points_1, gen_2.num)
                points_2 = np.tile(points_2, gen_1.num)
            else:
                # copy the points arrays anyway so the regions can
                # safely perform any array operations in place
                # this is advantageous in the cases above
                points_1 = np.copy(points_1)
                points_2 = np.copy(points_2)


            if axis_1 == excluder.scannables[0]:
                mask = excluder.create_mask(points_1, points_2)
            else:
                mask = excluder.create_mask(points_2, points_1)

            #####
            # Add new mask to index
            #####
            tile = 0.5 if doubled_mask else 1
            repeat = 1
            found_axis = False
            # tile by product of generators "before"
            # repeat by product of generators "after"
            for g in dim["generators"]:
                if axis_1 in g.axes or axis_2 in g.axes:
                    found_axis = True
                else:
                    if found_axis:
                        repeat *= g.num
                    else:
                        tile *= g.num
            m = {"repeat":repeat, "tile":tile, "mask":mask}
            dim["masks"].append(m)
        # end for excluder in excluders
        #####

        tile = 1
        repeat = 1
        #####
        # Generate full index mask and "apply"
        #####
        for dim in self.dimensions:
            mask = np.full(dim["size"], True, dtype=np.int8)
            for m in dim["masks"]:
                assert len(m["mask"]) * m["repeat"] * m["tile"] == len(mask), \
                        "Mask lengths are not consistent"
                expanded = np.repeat(m["mask"], m["repeat"])
                if m["tile"] % 1 != 0:
                    ex = np.tile(expanded, int(m["tile"]))
                    expanded = np.append(ex, expanded[:len(expanded)//2])
                else:
                    expanded = np.tile(expanded, int(m["tile"]))
                mask &= expanded
            dim["mask"] = mask
            dim["indicies"] = np.nonzero(mask)[0]
            if len(dim["indicies"]) == 0:
                raise ValueError("Regions would exclude entire scan")
            repeat *= len(dim["indicies"])
        self.num = repeat
        for dim in self.dimensions:
            l = len(dim["indicies"])
            repeat /= l
            dim["tile"] = tile
            dim["repeat"] = repeat
            tile *= l

        for dim in self.dimensions:
            tile = 1
            repeat = 1
            for g in dim["generators"]:
                repeat *= g.num
            for g in dim["generators"]:
                repeat /= g.num
                d = {"tile":tile, "repeat":repeat}
                tile *= g.num
                self.generator_dim_scaling[g] = d