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())
def test_positions_non_alternate(self): g1, g2, g3 = Mock(), Mock(), Mock() g1.axes, g2.axes, g3.axes = ["g1"], ["g2"], ["g3"] g1.size, g2.size, g3.size = 3, 4, 5 g1.positions = {"g1": np.array([0, 1, 2])} g1.bounds = {"g1": np.array([-0.5, 0.5, 1.5, 2.5])} g2.positions = {"g2": np.array([-1, 0, 1, 2])} g2.bounds = {"g2": np.array([-1.5, -0.5, 0.5, 1.5, 2.5])} g3.positions = {"g3": np.array([-2, 0, 2, 4, 6])} g3.bounds = {"g3": np.array([-3, -1, 1, 3, 5, 7])} g1.alternate = False g2.alternate = False g3.alternate = False e1, e2 = Mock(), Mock() e1.axes = ["g1", "g2"] e2.axes = ["g2", "g3"] m1 = np.repeat(np.array([0, 1, 0, 1, 1, 0]), 10) m2 = np.tile(np.array([1, 0, 0, 1, 1, 1]), 10) e1.create_mask.return_value = m1 e2.create_mask.return_value = m2 d = Dimension([g1, g2, g3], [e1, e2]) d.prepare() expected_mask = m1 & m2 expected_indices = expected_mask.nonzero()[0] expected_g1 = np.repeat(g1.positions["g1"], 5 * 4)[expected_indices] expected_g2 = np.tile(np.repeat(g2.positions["g2"], 5), 3)[expected_indices] expected_g3 = np.tile(g3.positions["g3"], 3 * 4)[expected_indices] self.assertEqual(expected_mask.tolist(), d.mask.tolist()) self.assertEqual(expected_indices.tolist(), d.indices.tolist()) self.assertEqual(expected_g1.tolist(), d.positions["g1"].tolist()) self.assertEqual(expected_g2.tolist(), d.positions["g2"].tolist()) self.assertEqual(expected_g3.tolist(), d.positions["g3"].tolist())
def test_high_dimensional_excluder(self): w_pos = np.array([0, 1, 2, 3, 4, 5]) x_pos = np.array([0, 1, 2, 3, 4, 5]) y_pos = np.array([0, 1, 2, 3, 4, 5]) z_pos = np.array([0, 1, 2, 3, 4, 5]) mask_function = lambda pw, px, py, pz: (pw-2)**2 + (px-2)**2 + (py-1)**2 + (pz-3)**2 <= 1.1 excluder = Mock(axes=["w", "x", "y", "z"], create_mask=Mock(side_effect=mask_function)) gw = Mock(axes=["w"], positions={"w":w_pos}, size=len(w_pos), alternate=False) gx = Mock(axes=["x"], positions={"x":x_pos}, size=len(x_pos), alternate=False) gy = Mock(axes=["y"], positions={"y":y_pos}, size=len(y_pos), alternate=False) gz = Mock(axes=["z"], positions={"z":z_pos}, size=len(z_pos), alternate=False) d = Dimension.merge_dimensions([Dimension(gz), Dimension(gy), Dimension(gx), Dimension(gw)]) d.apply_excluder(excluder) d.prepare() w_positions = np.tile(w_pos, len(x_pos) * len(y_pos) * len(z_pos)) x_positions = np.repeat(np.tile(x_pos, len(y_pos) * len(z_pos)), len(w_pos)) y_positions = np.repeat(np.tile(y_pos, len(z_pos)), len(w_pos) * len(x_pos)) z_positions = np.repeat(z_pos, len(w_pos) * len(x_pos) * len(y_pos)) mask = mask_function(w_positions, x_positions, y_positions, z_positions) w_expected = w_positions[mask].tolist() x_expected = x_positions[mask].tolist() y_expected = y_positions[mask].tolist() z_expected = z_positions[mask].tolist() self.assertEqual(w_expected, d.get_positions("w").tolist()) self.assertEqual(x_expected, d.get_positions("x").tolist()) self.assertEqual(y_expected, d.get_positions("y").tolist()) self.assertEqual(z_expected, d.get_positions("z").tolist())
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)
def test_spread_excluder_multi_axes_per_gen(self): gx1_pos = np.array([1, 2, 3, 4, 5]) gx2_pos = np.array([11, 10, 9, 8, 7]) gy_pos = np.array([-1, 0, 1]) gz_pos = np.array([1, 0, -1, -2, -3]) mask_x1z_func = lambda px, pz: (px-4)**2 + (pz+1)**2 <= 1 exz = Mock(axes=["gx1", "gz"], create_mask=Mock(side_effect=mask_x1z_func)) gx = Mock(axes=["gx1", "gx2"], positions={"gx1":gx1_pos, "gx2":gx2_pos}, size=5, alternate=False) gy = Mock(axes=["gy"], positions={"gy":gy_pos}, size=3, alternate=False) gz = Mock(axes=["gz"], positions={"gz":gz_pos}, size=5, alternate=False) d = Dimension.merge_dimensions([Dimension(gz), Dimension(gy), Dimension(gx)]) d.apply_excluder(exz) d.prepare() x1_positions = np.tile(gx1_pos, 15) x2_positions = np.tile(gx2_pos, 15) y_positions = np.repeat(np.tile(gy_pos, 5), 5) z_positions = np.repeat(gz_pos, 15) mask = mask_x1z_func(x1_positions, z_positions) expected_x1 = x1_positions[mask].tolist() expected_x2 = x2_positions[mask].tolist() expected_y = y_positions[mask].tolist() expected_z = z_positions[mask].tolist() self.assertEqual(expected_x1, d.get_positions("gx1").tolist()) self.assertEqual(expected_x2, d.get_positions("gx2").tolist()) self.assertEqual(expected_y, d.get_positions("gy").tolist()) self.assertEqual(expected_z, d.get_positions("gz").tolist())
def test_excluder_over_spread_axes(self): gw_pos = np.array([0.1, 0.2]) gx_pos = np.array([0, 1, 2, 3]) gy_pos = np.array([10, 11, 12, 13]) gz_pos = np.array([100, 101, 102, 103]) go_pos = np.array([1000, 1001, 1002]) mask_xz_func = lambda px, pz: (px - 1)**2 + (pz - 102)**2 <= 1 exz = Mock(axes=["gx", "gz"], create_mask=Mock(side_effect=mask_xz_func)) gw = Mock(axes=["gw"], positions={"gw": gw_pos}, size=2, alternate=False) gx = Mock(axes=["gx"], positions={"gx": gx_pos}, size=4, alternate=False) gy = Mock(axes=["gy"], positions={"gy": gy_pos}, size=4, alternate=False) gz = Mock(axes=["gz"], positions={"gz": gz_pos}, size=4, alternate=False) go = Mock(axes=["go"], positions={"go": go_pos}, size=3, alternate=False) dw = Dimension(gw) dx = Dimension(gx) dy = Dimension(gy) dz = Dimension(gz) do = Dimension(go) d = Dimension.merge_dimensions([do, dz, dy, dx, dw]) d.apply_excluder(exz) d.prepare() x_positions = np.tile(np.array([0, 1, 2, 3]), 16) y_positions = np.repeat(np.tile(np.array([10, 11, 12, 13]), 4), 4) z_positions = np.repeat(np.array([100, 101, 102, 103]), 16) x_positions = np.tile(np.repeat(x_positions, gw.size), go.size) y_positions = np.tile(np.repeat(y_positions, gw.size), go.size) z_positions = np.tile(np.repeat(z_positions, gw.size), go.size) mask = mask_xz_func(x_positions, z_positions) expected_x = x_positions[mask].tolist() expected_y = y_positions[mask].tolist() expected_z = z_positions[mask].tolist() self.assertEqual(expected_x, d.get_positions("gx").tolist()) self.assertEqual(expected_y, d.get_positions("gy").tolist()) self.assertEqual(expected_z, d.get_positions("gz").tolist())
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]
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())
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
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)
def test_get_positions_single_gen(self): gx_pos = np.tile(np.array([0, 1, 2, 3]), 4) gy_pos = np.repeat(np.array([0, 1, 2, 3]), 4) mask_func = lambda px, py: (px-1)**2 + (py-2)**2 <= 1 g = Mock(axes=["gx", "gy"], positions={"gx":gx_pos, "gy":gy_pos}, size=16, alternate=False) e = Mock(axes=["gx", "gy"], create_mask=Mock(side_effect=mask_func)) d = Dimension(g) d.apply_excluder(e) d.prepare() self.assertEqual([1, 0, 1, 2, 1], d.get_positions("gx").tolist()) self.assertEqual([1, 2, 2, 2, 3], d.get_positions("gy").tolist())
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 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
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 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)
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