def test_excluder_spread_axes(self):
        sp = SpiralGenerator(["s1", "s2"], ["mm", "mm"], centre=[0, 0], radius=1, scale=0.5, alternate=True)
        y = LineGenerator("y", "mm", 0, 1, 3, True)
        z = LineGenerator("z", "mm", -2, 3, 6, True)
        e = ROIExcluder([CircularROI([0., 0.], 1.0)], ["s1", "z"])
        g = CompoundGenerator([z, y, sp], [e], [])

        g.prepare()

        s1_pos, s2_pos = sp.positions["s1"], sp.positions["s2"]
        s1_pos = np.tile(np.append(s1_pos, s1_pos[::-1]), 9)
        s2_pos = np.tile(np.append(s2_pos, s2_pos[::-1]), 9)
        y_pos = np.tile(np.repeat(np.array([0, 0.5, 1.0, 1.0, 0.5, 0]), sp.size), 3)
        z_pos = np.repeat(np.array([-2, -1, 0, 1, 2, 3]), sp.size * 3)

        mask_func = lambda ps1, pz: ps1**2 + pz**2 <= 1
        mask = mask_func(s1_pos, z_pos)

        expected_s1 = s1_pos[mask]
        expected_s2 = s2_pos[mask]
        expected_y = y_pos[mask]
        expected_z = z_pos[mask]
        expected_positions = [{'s1':ps1, 's2':ps2, 'y':py, 'z':pz}
                for (ps1, ps2, py, pz) in zip(expected_s1, expected_s2, expected_y, expected_z)]
        positions = [point.positions for point in list(g.iterator())]

        self.assertEqual(positions, expected_positions)
Exemplo n.º 2
0
    def test_mixed_alternating_generators(self):
        x_pos = np.array([0, 1, 2])
        x_bounds = np.array([-0.5, 0.5, 1.5, 2.5])
        y_pos = np.array([10, 11, 12])
        z_pos = np.array([20, 21, 22])
        w_pos = np.array([30, 31, 32])
        gx = Mock(axes=["x"],
                  positions={"x": x_pos},
                  bounds={"x": x_bounds},
                  size=3,
                  alternate=True)
        gy = Mock(axes=["y"], positions={"y": y_pos}, size=3, alternate=True)
        gz = Mock(axes=["z"], positions={"z": z_pos}, size=3, alternate=False)
        gw = Mock(axes=["w"], positions={"w": w_pos}, size=3, alternate=False)

        mask = np.array([1, 1, 0, 1, 1, 1, 1, 0, 1] * 9)
        indices = np.nonzero(mask)[0]
        e = Mock(axes=["x", "y", "z", "w"])
        e.create_mask.return_value = mask

        d = Dimension([gw, gz, gy, gx], [e])
        d.prepare()

        expected_x = np.append(np.tile(np.append(x_pos, x_pos[::-1]), 13),
                               x_pos)[indices]
        expected_y = np.repeat(
            np.append(np.tile(np.append(y_pos, y_pos[::-1]), 4), y_pos),
            3)[indices]
        expected_z = np.tile(np.repeat(z_pos, 9), 3)[indices]
        expected_w = np.repeat(w_pos, 27)[indices]
        self.assertEqual(False, d.alternate)
        self.assertEqual(expected_x.tolist(), d.get_positions("x").tolist())
        self.assertEqual(expected_y.tolist(), d.get_positions("y").tolist())
        self.assertEqual(expected_z.tolist(), d.get_positions("z").tolist())
        self.assertEqual(expected_w.tolist(), d.get_positions("w").tolist())
Exemplo n.º 3
0
    def get_mesh_map(self, axis):
        """
        Retrieve the mesh map (indices) for a given axis within the dimension.

        Args:
            axis (str): axis to get positions for
        Returns:
            Positions (np.array): Array of mesh indices
        """
        # the points for this axis must be scaled and then indexed
        if not self._prepared:
            raise ValueError("Must call prepare first")
        # scale up points for axis
        gen = [g for g in self.generators if axis in g.axes][0]
        points = gen.positions[axis]
        # just get index of points instead of actual point value
        points = np.arange(len(points))

        if gen.alternate:
            points = np.append(points, points[::-1])
        tile = 0.5 if self.alternate else 1
        repeat = 1
        for g in self.generators[:self.generators.index(gen)]:
            tile *= g.size
        for g in self.generators[self.generators.index(gen) + 1:]:
            repeat *= g.size
        points = np.repeat(points, repeat)
        if tile % 1 != 0:
            p = np.tile(points, int(tile))
            points = np.append(p, points[:int(len(points) // 2)])
        else:
            points = np.tile(points, int(tile))
        return points[self.indices]
Exemplo n.º 4
0
 def test_apply_excluders_over_single_alternating(self):
     x_pos = np.array([1, 2, 3, 4, 5])
     y_pos = np.array([10, 11, 12, 13, 14, 15])
     g = Mock(axes=["x", "y"], positions={"x":x_pos, "y":y_pos})
     g.alternate = True
     mask = np.array([1, 1, 0, 1, 0, 0], dtype=np.int8)
     e = Mock(axes=["x", "y"], create_mask=Mock(return_value=mask))
     d = Dimension(g)
     d.apply_excluder(e)
     d._masks[0]["mask"] = d._masks[0]["mask"].tolist()
     self.assertEqual([{"repeat":1, "tile":0.5, "mask":mask.tolist()}], d._masks)
     self.assertTrue((np.append(x_pos, x_pos[::-1]) == e.create_mask.call_args[0][0]).all())
     self.assertTrue((np.append(y_pos, y_pos[::-1]) == e.create_mask.call_args[0][1]).all())
Exemplo n.º 5
0
 def test_prepare(self):
     d = Dimension(
         Mock(axes=["x", "y"],
              positions={
                  "x": np.array([0]),
                  "y": np.array([0])
              },
              size=30))
     m1 = np.array([0, 1, 0, 1, 1, 0], dtype=np.int8)
     m2 = np.array([1, 1, 0, 0, 1], dtype=np.int8)
     d._masks = [{
         "repeat": 2,
         "tile": 2.5,
         "mask": m1
     }, {
         "repeat": 2,
         "tile": 3,
         "mask": m2
     }]
     e1 = np.array([0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0], dtype=np.int8)
     e1 = np.append(np.tile(e1, 2), e1[:len(e1) // 2])
     e2 = np.array([1, 1, 1, 1, 0, 0, 0, 0, 1, 1], dtype=np.int8)
     e2 = np.tile(e2, 3)
     expected = e1 & e2
     d.prepare()
     self.assertEqual(expected.tolist(), d.mask.tolist())
Exemplo n.º 6
0
    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
Exemplo n.º 7
0
    def apply_excluder(self, excluder):
        """Apply an excluder with axes matching some axes in the dimension to
        produce an internal mask"""
        if self._prepared:
            raise ValueError("Can not apply excluders after"
                             "prepare has been called")
        axis_inner = excluder.axes[0]
        axis_outer = excluder.axes[1]
        gen_inner = [g for g in self.generators if axis_inner in g.axes][0]
        gen_outer = [g for g in self.generators if axis_outer in g.axes][0]
        points_x = gen_inner.positions[axis_inner]
        points_y = gen_outer.positions[axis_outer]
        if self.generators.index(gen_inner) > self.generators.index(gen_outer):
            gen_inner, gen_outer = gen_outer, gen_inner
            axis_inner, axis_outer = axis_outer, axis_inner
            points_x, points_y = points_y, points_x

        if gen_inner is gen_outer and self.alternate:
            points_x = np.append(points_x, points_x[::-1])
            points_y = np.append(points_y, points_y[::-1])
        elif self.alternate:
            points_x = np.append(points_x, points_x[::-1])
            points_x = np.repeat(points_x, gen_outer.size)
            points_y = np.append(points_y, points_y[::-1])
            points_y = np.tile(points_y, gen_inner.size)
        elif gen_inner is not gen_outer:
            points_x = np.repeat(points_x, gen_outer.size)
            points_y = np.tile(points_y, gen_inner.size)

        if axis_inner == excluder.axes[0]:
            excluder_mask = excluder.create_mask(points_x, points_y)
        else:
            excluder_mask = excluder.create_mask(points_y, points_x)
        tile = 0.5 if self.alternate else 1
        repeat = 1
        found_axis = False
        for g in self.generators:
            if axis_inner in g.axes or axis_outer in g.axes:
                found_axis = True
            else:
                if found_axis:
                    repeat *= g.size
                else:
                    tile *= g.size

        m = {"repeat":repeat, "tile":tile, "mask":excluder_mask}
        self._masks.append(m)
Exemplo n.º 8
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())
Exemplo n.º 9
0
    def prepare_arrays(self, index_array):
        # The ConcatGenerator gets its positions from its sub-generators
        merged_arrays = {}
        for axis in self.axes:
            merged_arrays[axis] = np.array
        first = True

        if index_array.size == self.size + 1:
            # getting bounds
            preparing_bounds = True
        else:
            # getting positions
            preparing_bounds = False

        for generator in self.generators:

            if preparing_bounds:
                # getting bounds
                arr = generator.prepare_arrays(index_array[:generator.size +
                                                           1])
            else:
                # getting positions
                arr = generator.prepare_arrays(index_array[:generator.size])

            for axis in self.axes:
                axis_array = arr[axis]
                if first:
                    merged_arrays[axis] = axis_array
                else:
                    # This avoids appending an ndarray to a list
                    cur_array = merged_arrays[axis]

                    if preparing_bounds:
                        assert np.abs(cur_array[-1] - axis_array[0]) < self.DIFF_LIMIT, \
                                          "Merged generator bounds don't meet" \
                                          " for axis %s (%f, %f)" \
                                          % (str(axis), cur_array[-1],
                                             axis_array[0])
                        cur_array = np.append(cur_array[:-1], axis_array)
                    else:
                        cur_array = np.append(cur_array, axis_array)
                    merged_arrays[axis] = cur_array
            first = False

        return merged_arrays
Exemplo n.º 10
0
 def __add__(self, other):
     """ input:
     other (Point or Points)
     Appends the positions, bounds, indices, duration, delay of another Points or Point to self
     Assumes that dimensions are shared, or that either self or other have no positions.
     returns: self
     """
     if not len(self):
         if isinstance(other, Points):
             return other.__copy__()
         return self.wrap(other)
     if len(other):
         self.positions.update({axis: np.append(self.positions[axis], other.positions[axis])
                                for axis in other.positions})
         self.lower.update({axis: np.append(self.lower[axis], other.lower[axis]) for axis in other.lower})
         self.upper.update({axis: np.append(self.upper[axis], other.upper[axis]) for axis in other.upper})
         if isinstance(other, Point):
             self.indexes = np.vstack((self.indexes, other.indexes))
         elif len(other):
             self.indexes = np.concatenate((self.indexes, other.indexes), axis=0)
         self.duration = np.append(self.duration, other.duration)
         self.delay_after = np.append(self.delay_after, other.delay_after)
     return self
Exemplo n.º 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
Exemplo n.º 12
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
Exemplo n.º 13
0
    def apply_excluder(self, excluder):
        """Apply an excluder with axes matching some axes in the dimension to
        produce an internal mask"""
        if self._prepared:
            raise ValueError("Can not apply excluders after"
                             "prepare has been called")
        # find generators referenced by excluder
        matched_gens = [
            g for g in self.generators
            if len(set(g.axes) & set(excluder.axes)) != 0
        ]
        if len(matched_gens) == 0:
            raise ValueError(
                "Excluder references axes not present in dimension : %s" %
                str(excluder.axes))
        g_start = self.generators.index(matched_gens[0])
        g_end = self.generators.index(matched_gens[-1])
        point_arrays = {
            axis:
            [g for g in matched_gens if axis in g.axes][0].positions[axis]
            for axis in excluder.axes
        }

        if self.alternate:
            for axis in point_arrays.keys():
                arr = point_arrays[axis]
                point_arrays[axis] = np.append(arr, arr[::-1])

        # scale up all point arrays using generators within the range
        # inner generators are tiled by the size of outer generators
        # outer generators have points repeated by the size of inner ones
        axes_tiling = {axis: 1 for axis in excluder.axes}
        axes_repeats = {axis: 1 for axis in excluder.axes}
        axes_seen = []
        axes_to_see = [axis for axis in excluder.axes]
        for g in self.generators[g_start:g_end + 1]:
            found_axes = [axis for axis in g.axes if axis in excluder.axes]
            axes_to_see = [
                axis for axis in axes_to_see if axis not in found_axes
            ]
            for axis in axes_to_see:
                axes_tiling[axis] *= g.size
            for axis in axes_seen:
                axes_repeats[axis] *= g.size
            axes_seen.extend(found_axes)
        for axis in point_arrays.keys():
            arr = point_arrays[axis]
            point_arrays[axis] = np.tile(np.repeat(arr, axes_repeats[axis]),
                                         axes_tiling[axis])

        arrays = [point_arrays[axis] for axis in excluder.axes]
        excluder_mask = excluder.create_mask(*arrays)

        # record the tiling/repeat information for generators outside the axis range
        tile = 0.5 if self.alternate else 1
        repeat = 1
        for g in self.generators[0:g_start]:
            tile *= g.size
        for g in self.generators[g_end + 1:]:
            repeat *= g.size

        m = {"repeat": repeat, "tile": tile, "mask": excluder_mask}
        self._masks.append(m)
Exemplo n.º 14
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