From 5fa939e310c8c13850ee9bfaf6d5cfab7b1f1700 Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Fri, 24 Feb 2017 14:10:02 +0000 Subject: [PATCH 01/11] Replace scanpointgenerator copy with state at e5bea226 from spg repo --- .../scripts/scanpointgenerator/compat.py | 2 +- .../core/compoundgenerator.py | 340 +++++++----------- .../scanpointgenerator/core/dimension.py | 107 ++++++ .../scanpointgenerator/core/generator.py | 32 +- .../scanpointgenerator/core/mutator.py | 13 +- .../scanpointgenerator/generators/__init__.py | 1 - .../generators/arraygenerator.py | 161 --------- .../generators/linegenerator.py | 121 +++---- .../generators/lissajousgenerator.py | 88 ++--- .../generators/spiralgenerator.py | 106 ++---- .../mutators/fixeddurationmutator.py | 14 +- .../mutators/randomoffsetmutator.py | 122 ++----- .../scanpointgenerator/plotgenerator.py | 24 +- 13 files changed, 433 insertions(+), 698 deletions(-) create mode 100644 org.eclipse.scanning.points/scripts/scanpointgenerator/core/dimension.py delete mode 100644 org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/compat.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/compat.py index fbe664331..023dce4a7 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/compat.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/compat.py @@ -8,7 +8,7 @@ if os.name == 'java': - import scanpointgenerator.numjy as numpy + import scisoftpy as numpy else: import numpy diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/core/compoundgenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/core/compoundgenerator.py index 1e3c306d9..61486a618 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/core/compoundgenerator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/core/compoundgenerator.py @@ -1,12 +1,7 @@ import logging -import sys -if sys.platform.startswith('java'): - Lock = object # Workaround for GDA -else: - from threading import Lock - from scanpointgenerator.compat import range_, np +from scanpointgenerator.core.dimension import Dimension from scanpointgenerator.core.generator import Generator from scanpointgenerator.core.point import Point from scanpointgenerator.core.excluder import Excluder @@ -15,11 +10,12 @@ from scanpointgenerator.generators import LineGenerator -@Generator.register_subclass("scanpointgenerator:generator/CompoundGenerator:1.0") -class CompoundGenerator(Generator): +class CompoundGenerator(object): """Nest N generators, apply exclusion regions to relevant generator pairs and apply any mutators before yielding points""" + typeid = "scanpointgenerator:generator/CompoundGenerator:1.0" + def __init__(self, generators, excluders, mutators): """ Args: @@ -31,14 +27,11 @@ def __init__(self, generators, excluders, mutators): self.excluders = excluders self.mutators = mutators self.axes = [] - self.position_units = {} - self.axes_points = {} - self.axes_points_lower = {} - self.axes_points_upper = {} + self.units = {} self.dimensions = [] - self.alternate_direction = [g.alternate_direction for g in generators] - self.num = 1 - + self.size = 1 + self._dim_meta = {} + self._prepared = False for generator in generators: logging.debug("Generator passed to Compound init") logging.debug(generator.to_dict()) @@ -46,16 +39,24 @@ def __init__(self, generators, excluders, mutators): raise TypeError("CompoundGenerators cannot be nested, nest" "its constituent parts instead") self.axes += generator.axes - self.position_units.update(generator.position_units) + self.units.update(generator.units) if len(self.axes) != len(set(self.axes)): raise ValueError("Axis names cannot be duplicated") self.generators = generators - self.generator_dim_scaling = {} + self._generator_dim_scaling = {} def prepare(self): - self.num = 1 + """ + Prepare data structures and masks required for point generation. + 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) @@ -73,45 +74,36 @@ def prepare(self): 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)] + gen_1.prepare_positions() + gen_2.prepare_positions() + valid = np.full(gen_1.size, True, dtype=np.int8) + valid &= gen_1.positions[axis_1] \ + <= rect.roi.width + rect.roi.start[0] + valid &= gen_1.positions[axis_1] >= rect.roi.start[0] + points_1 = gen_1.positions[axis_1][valid.astype(np.bool)] + valid = np.full(gen_2.size, True, dtype=np.int8) + valid &= gen_2.positions[axis_2] \ + <= rect.roi.height + rect.roi.start[1] + valid &= gen_2.positions[axis_2] >= rect.roi.start[1] + points_2 = gen_2.positions[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) + gen_1.axes, gen_1.units, points_1[0], points_1[-1], + len(points_1), gen_1.alternate) new_gen2 = LineGenerator( - gen_2.name, gen_2.units, points_2[0], points_2[-1], - len(points_2), gen_2.alternate_direction) + 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 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) + 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.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) \ @@ -120,203 +112,138 @@ def prepare(self): 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] + # 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 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"] \ + 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") + "alternate 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"] + new_dim = Dimension.merge_dimensions(dim_1, dim_2) + self.dimensions[self.dimensions.index(dim_1)] = new_dim 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) + dim = new_dim 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) - + dim = dim_1 - if axis_1 == excluder.scannables[0]: - mask = excluder.create_mask(points_1, points_2) - else: - mask = excluder.create_mask(points_2, points_1) + dim.apply_excluder(excluder) - ##### - # 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" - ##### + self.size = 1 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: + self._dim_meta[dim] = {} + mask = dim.create_dimension_mask() + indices = np.nonzero(mask)[0] + if len(indices) == 0: raise ValueError("Regions would exclude entire scan") - repeat *= len(dim["indicies"]) - self.num = repeat + self.size *= len(indices) + self._dim_meta[dim]["mask"] = mask + self._dim_meta[dim]["indices"] = indices + + repeat = self.size + tile = 1 for dim in self.dimensions: - l = len(dim["indicies"]) - repeat /= l - dim["tile"] = tile - dim["repeat"] = repeat - tile *= l + dim_length = len(self._dim_meta[dim]["indices"]) + repeat /= dim_length + self._dim_meta[dim]["tile"] = tile + self._dim_meta[dim]["repeat"] = repeat + tile *= dim_length 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 + repeat = dim.size + for g in dim.generators: + repeat /= g.size d = {"tile":tile, "repeat":repeat} - tile *= g.num - self.generator_dim_scaling[g] = d + tile *= g.size + self._generator_dim_scaling[g] = d + + self._prepared = True def iterator(self): - it = (self.get_point(n) for n in range_(self.num)) - for m in self.mutators: - it = m.mutate(it) + """ + Iterator yielding generator positions at each scan point + + Yields: + Point: The next point + """ + if not self._prepared: + raise ValueError("CompoundGenerator has not been prepared") + it = (self.get_point(n) for n in range_(self.size)) for p in it: yield p def get_point(self, n): - if n >= self.num: + """ + Retrieve the desired point from the generator + + Args: + n (int): point to be generated + Returns: + Point: The requested point + """ + + if not self._prepared: + raise ValueError("CompoundGenerator has not been prepared") + if n >= self.size: raise IndexError("Requested point is out of range") - p = Point() + point = Point() - # need to know how far along each index we are - # and, in the case of alternating indicies, how + # need to know how far along each dimension we are + # and, in the case of alternating indices, how # many times we've run through them - kc = 0 # the "cumulative" k for each index + kc = 0 # the "cumulative" k for each dimension for dim in self.dimensions: - indicies = dim["indicies"] - i = n // dim["repeat"] - r = i // len(indicies) - i %= len(indicies) - k = indicies[i] + indices = self._dim_meta[dim]["indices"] + i = int(n // self._dim_meta[dim]["repeat"]) + i %= len(indices) + k = indices[i] dim_reverse = False - if dim["alternate"] and kc % 2 == 1: - i = len(indicies) - i - 1 + if dim.alternate and kc % 2 == 1: + i = len(indices) - i - 1 dim_reverse = True - kc *= len(indicies) + kc *= len(indices) kc += k - k = indicies[i] - # need point k along each generator in index + k = indices[i] + # need point k along each generator in dimension # in alternating case, need to sometimes go backward - p.indexes.append(i) - for g in dim["generators"]: - j = k // self.generator_dim_scaling[g]["repeat"] - gr = j // g.num - j %= g.num - if dim["alternate"] and g is not dim["generators"][0] and gr % 2 == 1: - j = g.num - j - 1 + point.indexes.append(i) + for g in dim.generators: + j = int(k // self._generator_dim_scaling[g]["repeat"]) + r = int(j // g.size) + j %= g.size + j_lower = j + j_upper = j + 1 + if dim.alternate and g is not dim.generators[0] and r % 2 == 1: + # the top level generator's direction is handled by + # the fact that the reverse direction was appended + j = g.size - j - 1 + j_lower = j + 1 + j_upper = j + elif dim_reverse and g is dim.generators[0]: + # top level generator is running in reverse, + # so bounds are swapped + j_lower, j_upper = j_upper, j_lower for axis in g.axes: - p.positions[axis] = g.points[axis][j] - p.lower[axis] = g.points_lower[axis][j] - p.upper[axis] = g.points_upper[axis][j] - return p + point.positions[axis] = g.positions[axis][j] + if g is self.generators[-1]: + point.lower[axis] = g.bounds[axis][j_lower] + point.upper[axis] = g.bounds[axis][j_upper] + else: + point.lower[axis] = g.positions[axis][j] + point.upper[axis] = g.positions[axis][j] + for m in self.mutators: + point = m.mutate(point, n) + return point def to_dict(self): """Convert object attributes into a dictionary""" @@ -331,6 +258,7 @@ def to_dict(self): def from_dict(cls, d): """ Create a CompoundGenerator instance from a serialised dictionary + Args: d(dict): Dictionary of attributes Returns: diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/core/dimension.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/core/dimension.py new file mode 100644 index 000000000..086c91ee8 --- /dev/null +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/core/dimension.py @@ -0,0 +1,107 @@ +from scanpointgenerator.compat import np + +class Dimension(object): + """A collapsed set of generators joined by excluders""" + def __init__(self, generator): + self.axes = list(generator.axes) + self.generators = [generator] + self.size = generator.size + self.alternate = generator.alternate + self._masks = [] + + def apply_excluder(self, excluder): + """Apply an excluder with axes matching some axes in the dimension to + produce an internal mask""" + axis_inner = excluder.scannables[0] + axis_outer = excluder.scannables[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) + else: + # copy the point arrays so the excluders can perform + # array operations in place (advantageous in the other cases) + points_x = np.copy(points_x) + points_y = np.copy(points_y) + + if axis_inner == excluder.scannables[0]: + mask = excluder.create_mask(points_x, points_y) + else: + 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":mask} + self._masks.append(m) + + def create_dimension_mask(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 + """ + mask = np.full(self.size, 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 + return mask + + @staticmethod + def merge_dimensions(outer, inner): + """Collapse two dimensions into one, appropriate scaling structures""" + dim = Dimension(outer.generators[0]) + # masks in the inner generator are tiled by the size of + # outer generators and outer generators have their elements + # repeated by the size of inner generators + inner_masks = [m.copy() for m in inner._masks] + outer_masks = [m.copy() for m in outer._masks] + scale = inner.size + for m in outer_masks: + m["repeat"] *= scale + scale = outer.size + for m in inner_masks: + m["tile"] *= scale + dim._masks = outer_masks + inner_masks + dim.axes = outer.axes + inner.axes + dim.generators = outer.generators + inner.generators + dim.alternate = outer.alternate or inner.alternate + dim.size = outer.size * inner.size + return dim diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/core/generator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/core/generator.py index 6cdd52b96..29c72dd71 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/core/generator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/core/generator.py @@ -1,35 +1,51 @@ +from scanpointgenerator.compat import np class Generator(object): """Base class for all malcolm scan point generators Attributes: - position_units (dict): Dict of str position_name -> str position_unit + units (dict): Dict of str position_name -> str position_unit for each scannable dimension. E.g. {"x": "mm", "y": "mm"} index_dims (list): List of the int dimension sizes for the dataset. This - will have the same length as the position_units list for square + will have the same length as the units list for square scans but will be shorter for things like spiral scans. E.g. [15] index_names (list): List of the str dimension names for the dataset. This will have the same length as the index_dims. E.g. ["spiral_i"] axes (list): List of scannable names, used in GDA to reconstruct Point in CompoundGenerators """ - alternate_direction = False - position_units = None + alternate = False + units = None index_dims = None index_names = None + positions = None + bounds = None + size = 0 # Lookup table for generator subclasses _generator_lookup = {} axes = [] - def iterator(self): - """An iterator yielding positions at each scan point + def prepare_arrays(self, index_array): + """ + Abstract method to create position or bounds array from provided index + array. index_array will be np.arange(self.size) for positions and + np.arange(self.size + 1) - 0.5 for bounds. + + Args: + index_array (np.array): Index array to produce parameterised points - Yields: - Point: The next scan :class:`Point` + Returns: + Positions: Dictionary of axis names to position/bounds arrays """ raise NotImplementedError + def prepare_positions(self): + self.positions = self.prepare_arrays(np.arange(self.size)) + + def prepare_bounds(self): + self.bounds = self.prepare_arrays(np.arange(self.size + 1) - 0.5) + def to_dict(self): """Abstract method to convert object attributes into a dictionary""" raise NotImplementedError diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/core/mutator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/core/mutator.py index ccd987947..171906e20 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/core/mutator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/core/mutator.py @@ -7,16 +7,17 @@ class Mutator(object): # Lookup table for mutator subclasses _mutator_lookup = {} - def mutate(self, iterator): + def mutate(self, point, index): """ - Abstract method to take each point from the given iterator, apply a - mutation and then yield the new point + Abstract method to take a point, apply a mutation and then return the + new point Args: - iterator(iter): Iterator to mutate + Point: point to mutate + Index: one-dimensional linear index of point - Yields: - Point: Mutated points from generator + Returns: + Point: Mutated point """ raise NotImplementedError diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/__init__.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/__init__.py index 8aafffbc0..7e5b29d1f 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/__init__.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/__init__.py @@ -1,4 +1,3 @@ -from scanpointgenerator.generators.arraygenerator import ArrayGenerator from scanpointgenerator.generators.linegenerator import LineGenerator from scanpointgenerator.generators.lissajousgenerator import LissajousGenerator from scanpointgenerator.generators.spiralgenerator import SpiralGenerator diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py deleted file mode 100644 index 587f02a06..000000000 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py +++ /dev/null @@ -1,161 +0,0 @@ -from scanpointgenerator.compat import range_ -from scanpointgenerator.core import Generator -from scanpointgenerator.core import Point - - -@Generator.register_subclass("scanpointgenerator:generator/ArrayGenerator:1.0") -class ArrayGenerator(Generator): - """Generate a given n-dimensional array of points""" - - def __init__(self, name, units, points, lower_bounds=None, upper_bounds=None): - """ - Args: - name (str/list(str)): ND list of scannable names - e.g. "x" or ["x", "y"] - units (str): The scannable units. E.g. "mm" - points (list): List of ND lists of coordinates - e.g. [1.0, 2.0, 3.0] or [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]] - lower_bounds (list): List of ND lists of lower bound coordinates - upper_bounds (list): List of ND lists of upper bound coordinates - """ - - if not isinstance(name, list): - name = [name] - if not isinstance(points[0], list): - points = [[point] for point in points] - if upper_bounds is not None: - upper_bounds = [[point] for point in upper_bounds] - if lower_bounds is not None: - lower_bounds = [[point] for point in lower_bounds] - - self.name = name - self.points = points - self.upper_bounds = upper_bounds - self.lower_bounds = lower_bounds - - if len(self.name) != len(set(self.name)): - raise ValueError("Axis names cannot be duplicated; given %s" % - name) - - for point in self.points: - if len(point) != len(self.name): - raise ValueError( - "Dimensions of name, start and stop do not match") - if self.upper_bounds is not None: - for point in self.upper_bounds: - if len(point) != len(self.name): - raise ValueError( - "Dimensions of name, start and stop do not match") - if self.lower_bounds is not None: - for point in self.lower_bounds: - if len(point) != len(self.name): - raise ValueError( - "Dimensions of name, start and stop do not match") - - self.num = len(points) - - self.position_units = {} - for dimension in self.name: - self.position_units[dimension] = units - self.index_dims = [self.num] - self.index_names = self.name - - self.axes = self.name # For GDA - - def iterator(self): - - for i in range_(self.num): - - point = Point() - for axis, coordinate in enumerate(self.points[i]): - point.positions[self.name[axis]] = coordinate - - if self.upper_bounds is None: - upper = self._calculate_upper_bound(i, axis, coordinate) - else: - upper = self.upper_bounds[i][axis] - point.upper[self.name[axis]] = upper - - if self.lower_bounds is None: - lower = self._calculate_lower_bound(i, axis, coordinate) - else: - lower = self.lower_bounds[i][axis] - point.lower[self.name[axis]] = lower - - point.indexes = [i] - yield point - - def _calculate_upper_bound(self, index, axis, coordinate): - """ - Calculate upper bound for coordinate; if final coordinate then - calculate lower bound and extrapolate upper - - Args: - index(int): Index of coordinate in list - axis(int): Index of coordinate axis in list - coordinate(float): Coordinate to calculate bounds for - - Returns: - float: Upper bound of coordinate - """ - - if index == self.num - 1: - lower = (coordinate + self.points[index - 1][axis]) / 2 - upper = coordinate + (coordinate - lower) - else: - upper = (self.points[index + 1][axis] + coordinate) / 2 - return upper - - def _calculate_lower_bound(self, index, axis, coordinate): - """ - Calculate lower bound for coordinate; if first coordinate then - calculate upper bound and extrapolate lower - - Args: - index(int): Index of coordinate in list - axis(int): Index of coordinate axis in list - coordinate(float): Coordinate to calculate bounds for - - Returns: - float: Lower bound of coordinate - """ - - if index == 0: - upper = (self.points[index + 1][axis] + coordinate) / 2 - lower = coordinate - (upper - coordinate) - else: - lower = (coordinate + self.points[index - 1][axis]) / 2 - return lower - - def to_dict(self): - """Convert object attributes into a dictionary""" - - d = dict() - d['typeid'] = self.typeid - d['name'] = self.name - d['units'] = list(self.position_units.values())[0] - d['points'] = self.points - d['lower_bounds'] = self.lower_bounds - d['upper_bounds'] = self.upper_bounds - - return d - - @classmethod - def from_dict(cls, d): - """ - Create a ArrayGenerator instance from a serialised dictionary - - Args: - d(dict): Dictionary of attributes - - Returns: - ArrayGenerator: New ArrayGenerator instance - """ - - name = d['name'] - units = d['units'] - points = d['points'] - lower_bounds = d['lower_bounds'] - upper_bounds = d['upper_bounds'] - - return cls(name, units, points, lower_bounds, upper_bounds) diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/linegenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/linegenerator.py index ae54ff9af..32a354528 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/linegenerator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/linegenerator.py @@ -1,6 +1,5 @@ from scanpointgenerator.compat import range_, np from scanpointgenerator.core import Generator -from scanpointgenerator.core import Point def to_list(value): @@ -14,118 +13,78 @@ def to_list(value): class LineGenerator(Generator): """Generate a line of equally spaced N-dimensional points""" - def __init__(self, name, units, start, stop, num, alternate_direction=False): + def __init__(self, axes, units, start, stop, size, alternate=False): """ Args: - name (str/list(str)): The scannable name(s) E.g. "x" or ["x", "y"] - units (str): The scannable units. E.g. "mm" + axes (str/list(str)): The scannable axes E.g. "x" or ["x", "y"] + units (str/list(str)): The scannable units. E.g. "mm" or ["mm", "mm"] start (float/list(float)): The first position to be generated. e.g. 1.0 or [1.0, 2.0] - stop (float or list(float)): The first position to be generated. + stop (float or list(float)): The final position to be generated. e.g. 5.0 or [5.0, 10.0] - num (int): The number of points to generate. E.g. 5 - alternate_direction(bool): Specifier to reverse direction if + size (int): The number of points to generate. E.g. 5 + alternate(bool): Specifier to reverse direction if generator is nested """ - self.name = to_list(name) + self.axes = to_list(axes) self.start = to_list(start) self.stop = to_list(stop) - self.alternate_direction = alternate_direction - self.points = None - self.points_lower = None - self.points_upper = None - self.units = units + self.alternate = alternate + self.units = {d:u for (d, u) in zip(self.axes, to_list(units))} - if len(self.name) != len(set(self.name)): + if len(self.axes) != len(set(self.axes)): raise ValueError("Axis names cannot be duplicated; given %s" % - name) + axes) - if len(self.name) != len(self.start) or \ - len(self.name) != len(self.stop): + if len(self.axes) != len(self.start) or \ + len(self.axes) != len(self.stop): raise ValueError( - "Dimensions of name, start and stop do not match") + "Dimensions of axes, start and stop do not match") - self.num = num - self.num_axes = len(self.name) + self.size = size self.step = [] - if self.num < 2: + if self.size < 2: self.step = [0]*len(self.start) else: for axis in range_(len(self.start)): self.step.append( - (self.stop[axis] - self.start[axis])/(self.num - 1)) + (self.stop[axis] - self.start[axis])/(self.size - 1)) - self.position_units = dict() - for dimension in self.name: - self.position_units[dimension] = units - self.index_dims = [self.num] + self.index_dims = [self.size] - if len(self.name) > 1: + if len(self.axes) > 1: gen_name = "Line" - for axis_name in self.name[::-1]: + for axis_name in self.axes[::-1]: gen_name = axis_name + "_" + gen_name self.index_names = [gen_name] else: - self.index_names = self.name - - self.axes = self.name # For GDA - - def produce_points(self): - self.points = {} - self.points_lower = {} - self.points_upper = {} - for axis in range_(self.num_axes): - axis_name = self.name[axis] - start = self.start[axis] - stop = self.stop[axis] + self.index_names = self.axes + + def prepare_arrays(self, index_array): + arrays = {} + for axis, start, stop in zip(self.axes, self.start, self.stop): d = stop - start - if self.num == 1: - self.points[axis_name] = np.array([start]) - self.points_upper[axis_name] = np.array([start + 0.5 * d]) - self.points_lower[axis_name] = np.array([start - 0.5 * d]) - else: - n = self.num - 1. - s = d / n - upper_start = start + 0.5 * d / n - upper_stop = stop + 0.5 * d / n - lower_start = start - 0.5 * d / n - lower_stop = stop - 0.5 * d / n - self.points[axis_name] = np.linspace( - float(start), float(stop), self.num) - self.points_upper[axis_name] = np.linspace( - float(upper_start), float(upper_stop), self.num) - self.points_lower[axis_name] = np.linspace( - float(lower_start), float(lower_stop), self.num) - - def iterator(self): - for i in range_(self.num): - point = Point() - - for axis_index in range_(self.num_axes): - axis_name = self.name[axis_index] - start = self.start[axis_index] - step = self.step[axis_index] - - point.positions[axis_name] = start + i * step - point.lower[axis_name] = start + (i - 0.5) * step - point.upper[axis_name] = start + (i + 0.5) * step - - point.indexes = [i] - yield point + step = float(d) + # if self.size == 1 then single point case + if self.size > 1: + step /= (self.size - 1) + f = lambda t: (t * step) + start + arrays[axis] = f(index_array) + return arrays def to_dict(self): """Convert object attributes into a dictionary""" d = dict() d['typeid'] = self.typeid - d['name'] = self.name - d['units'] = self.units + d['axes'] = self.axes + d['units'] = [self.units[a] for a in self.axes] d['start'] = self.start d['stop'] = self.stop - d['num'] = self.num - d['alternate_direction'] = self.alternate_direction + d['size'] = self.size + d['alternate'] = self.alternate return d @@ -141,11 +100,11 @@ def from_dict(cls, d): LineGenerator: New LineGenerator instance """ - name = d['name'] + axes = d['axes'] units = d['units'] start = d['start'] stop = d['stop'] - num = d['num'] - alternate_direction = d['alternate_direction'] + size = d['size'] + alternate = d['alternate'] - return cls(name, units, start, stop, num, alternate_direction) + return cls(axes, units, start, stop, size, alternate) diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/lissajousgenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/lissajousgenerator.py index 594aad9d9..cba7a4913 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/lissajousgenerator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/lissajousgenerator.py @@ -9,12 +9,12 @@ class LissajousGenerator(Generator): """Generate the points of a Lissajous curve""" - def __init__(self, names, units, box, num_lobes, - num_points=None, alternate_direction=False): + def __init__(self, axes, units, box, num_lobes, + num_points=None, alternate=False): """ Args: - names (list(str)): The scannable names e.g. ["x", "y"] - units (str): The scannable units e.g. "mm" + axes (list(str)): The scannable axes e.g. ["x", "y"] + units (list(str)): The scannable units e.g. ["mm", "mm"] box(dict): Dictionary of centre, width and height representing box to fill with points num_lobes(int): Number of x-direction lobes for curve; will @@ -23,16 +23,13 @@ def __init__(self, names, units, box, num_lobes, curve. Default is 250 * num_lobes """ - self.names = names - self.units = units - self.points = None - self.points_lower = None - self.points_upper = None - self.alternate_direction = alternate_direction + self.axes = axes + self.units = {d:u for d,u in zip(axes, units)} + self.alternate = alternate - if len(self.names) != len(set(self.names)): + if len(self.axes) != len(set(self.axes)): raise ValueError("Axis names cannot be duplicated; given %s" % - names) + axes) num_lobes = int(num_lobes) @@ -41,65 +38,32 @@ def __init__(self, names, units, box, num_lobes, self.x_max = box['width']/2 self.y_max = box['height']/2 self.centre = box['centre'] - self.num = num_points + self.size = num_points # Phase needs to be 0 for even lobes and pi/2 for odd lobes to start # at centre for odd and at right edge for even self.phase_diff = m.pi/2 * (num_lobes % 2) if num_points is None: - self.num = num_lobes * 250 - self.increment = 2*m.pi/self.num + self.size = num_lobes * 250 + self.increment = 2*m.pi/self.size - self.position_units = {self.names[0]: units, self.names[1]: units} - self.index_dims = [self.num] + self.index_dims = [self.size] gen_name = "Lissajous" - for axis_name in self.names[::-1]: + for axis_name in self.axes[::-1]: gen_name = axis_name + "_" + gen_name self.index_names = [gen_name] - self.axes = self.names # For GDA - - def _calc_arrays(self, offset): + def prepare_arrays(self, index_array): + arrays = {} x0, y0 = self.centre[0], self.centre[1] A, B = self.x_max, self.y_max a, b = self.x_freq, self.y_freq d = self.phase_diff - f = lambda t: y0 + A * np.sin(a * 2 * m.pi * (t+offset)/self.num + d) - x = f(np.arange(self.num)) - f = lambda t: B * np.sin(b * 2 * m.pi * (t+offset)/self.num) - y = f(np.arange(self.num)) - return x, y - - def produce_points(self): - self.points = {} - self.points_lower = {} - self.points_upper = {} - - x = self.names[0] - y = self.names[1] - self.points[x], self.points[y] = self._calc_arrays(0) - self.points_upper[x], self.points_upper[y] = self._calc_arrays(0.5) - self.points_lower[x], self.points_lower[y] = self._calc_arrays(-0.5) - - def _calc(self, i): - """Calculate the coordinate for a given index""" - x = self.centre[0] + \ - self.x_max * m.sin(self.x_freq * i * self.increment + - self.phase_diff) - y = self.centre[1] + \ - self.y_max * m.sin(self.y_freq * i * self.increment) - - return x, y - - def iterator(self): - for i in range_(self.num): - p = Point() - p.positions[self.names[0]], p.positions[self.names[1]] = self._calc(i) - p.lower[self.names[0]], p.lower[self.names[1]] = self._calc(i - 0.5) - p.upper[self.names[0]], p.upper[self.names[1]] = self._calc(i + 0.5) - p.indexes = [i] - - yield p + fx = lambda t: x0 + A * np.sin(a * 2*m.pi * t/self.size + d) + fy = lambda t: y0 + B * np.sin(b * 2*m.pi * t/self.size) + arrays[self.axes[0]] = fx(index_array) + arrays[self.axes[1]] = fy(index_array) + return arrays def to_dict(self): """Convert object attributes into a dictionary""" @@ -111,11 +75,11 @@ def to_dict(self): d = dict() d['typeid'] = self.typeid - d['names'] = self.names - d['units'] = list(self.position_units.values())[0] + d['axes'] = self.axes + d['units'] = [self.units[a] for a in self.axes] d['box'] = box d['num_lobes'] = self.x_freq - d['num_points'] = self.num + d['num_points'] = self.size return d @@ -131,10 +95,10 @@ def from_dict(cls, d): LissajousGenerator: New LissajousGenerator instance """ - names = d['names'] + axes = d['axes'] units = d['units'] box = d['box'] num_lobes = d['num_lobes'] num_points = d['num_points'] - return cls(names, units, box, num_lobes, num_points) + return cls(axes, units, box, num_lobes, num_points) diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/spiralgenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/spiralgenerator.py index 1d763703d..26f886a38 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/spiralgenerator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/spiralgenerator.py @@ -9,111 +9,71 @@ class SpiralGenerator(Generator): """Generate the points of an Archimedean spiral""" - def __init__(self, names, units, centre, radius, scale=1.0, - alternate_direction=False): + def __init__(self, axes, units, centre, radius, scale=1.0, + alternate=False): """ Args: - names (list(str)): The scannable names e.g. ["x", "y"] - units (str): The scannable units e.g. "mm" + axes (list(str)): The scannable axes e.g. ["x", "y"] + units (list(str)): The scannable units e.g. ["mm", "mm"] centre(list): List of two coordinates of centre point of spiral radius(float): Maximum radius of spiral scale(float): Gap between spiral arcs; higher scale gives fewer points for same radius - alternate_direction(bool): Specifier to reverse direction if + alternate(bool): Specifier to reverse direction if generator is nested """ - self.names = names - self.units = units + self.axes = axes self.centre = centre self.radius = radius self.scale = scale - self.alternate_direction = alternate_direction - self.points = None - self.points_lower = None - self.points_upper = None + self.alternate = alternate + self.units = {d:u for d,u in zip(axes, units)} - if len(self.names) != len(set(self.names)): + if len(self.axes) != len(set(self.axes)): raise ValueError("Axis names cannot be duplicated; given %s" % - names) + axes) - self.alpha = m.sqrt(4 * m.pi) # Theta scale factor - self.beta = scale / (2 * m.pi) # Radius scale factor - self.num = self._end_point(self.radius) + 1 - - self.position_units = {names[0]: units, names[1]: units} - self.index_dims = [self._end_point(self.radius)] gen_name = "Spiral" - for axis_name in self.names[::-1]: + for axis_name in self.axes[::-1]: gen_name = axis_name + "_" + gen_name self.index_names = [gen_name] - self.axes = self.names # For GDA - - def _calc_arrays(self, offset): # spiral equation : r = b * phi # scale = 2 * pi * b # parameterise phi with approximation: # phi(t) = k * sqrt(t) (for some k) # number of possible t is solved by sqrt(t) = max_r / b*k - b = self.scale / (2 * m.pi) - k = m.sqrt(4 * m.pi) # magic scaling factor for our angle steps - size = (self.radius) / (b * k) - size *= size - size = int(size) + 1 # TODO: Why the +1 ??? - phi_t = lambda t: k * np.sqrt(t + offset) - phi = phi_t(np.arange(size)) + self.alpha = m.sqrt(4 * m.pi) # Theta scale factor = k + self.beta = scale / (2 * m.pi) # Radius scale factor = b + self.size = int((self.radius / (self.alpha * self.beta)) ** 2) + 1 + + def prepare_arrays(self, index_array): + arrays = {} + b = self.beta + k = self.alpha + size = self.size + # parameterise phi with approximation: + # phi(t) = k * sqrt(t) (for some k) + phi_t = lambda t: k * np.sqrt(t + 0.5) + phi = phi_t(index_array) x = self.centre[0] + b * phi * np.sin(phi) y = self.centre[1] + b * phi * np.cos(phi) - return x, y - - def produce_points(self): - self.points = {} - self.points_lower = {} - self.points_upper = {} - x = self.names[0] - y = self.names[1] - self.points_lower[x], self.points_lower[y] = self._calc_arrays(0) - self.points[x], self.points[y] = self._calc_arrays(0.5) - self.points_upper[x], self.points_upper[y] = self._calc_arrays(1.) - - def _calc(self, i): - """Calculate the coordinate for a given index""" - theta = self.alpha * m.sqrt(i) - radius = self.beta * theta - x = self.centre[0] + radius * m.sin(theta) - y = self.centre[1] + radius * m.cos(theta) - - return x, y - - def _end_point(self, radius): - """Calculate the index of the final point contained by circle""" - return int((radius / (self.alpha * self.beta)) ** 2) - - def iterator(self): - for i in range_(0, self._end_point(self.radius) + 1): - p = Point() - p.indexes = [i] - - i += 0.5 # Offset so lower bound of first point is not less than 0 - - p.positions[self.names[0]], p.positions[self.names[1]] = self._calc(i) - p.upper[self.names[0]], p.upper[self.names[1]] = self._calc(i + 0.5) - p.lower[self.names[0]], p.lower[self.names[1]] = self._calc(i - 0.5) - - yield p + arrays[self.axes[0]] = x + arrays[self.axes[1]] = y + return arrays def to_dict(self): """Convert object attributes into a dictionary""" d = dict() d['typeid'] = self.typeid - d['names'] = self.names - d['units'] = list(self.position_units.values())[0] + d['axes'] = self.axes + d['units'] = [self.units[a] for a in self.axes] d['centre'] = self.centre d['radius'] = self.radius d['scale'] = self.scale - d['alternate_direction'] = self.alternate_direction + d['alternate'] = self.alternate return d @@ -129,11 +89,11 @@ def from_dict(cls, d): SpiralGenerator: New SpiralGenerator instance """ - names = d['names'] + axes = d['axes'] units = d['units'] centre = d['centre'] radius = d['radius'] scale = d['scale'] - alternate_direction = d['alternate_direction'] + alternate = d['alternate'] - return cls(names, units, centre, radius, scale, alternate_direction) + return cls(axes, units, centre, radius, scale, alternate) diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/mutators/fixeddurationmutator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/mutators/fixeddurationmutator.py index dc062cf3f..20111f154 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/mutators/fixeddurationmutator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/mutators/fixeddurationmutator.py @@ -11,20 +11,20 @@ def __init__(self, duration): """ self.duration = duration - def mutate(self, iterator): + def mutate(self, point, index): """ Applies duration to points in the given iterator, yielding them Args: - iterator: Iterator to mutate + Point: Point to mutate + Index: one-dimensional index of point - Yields: - Point: Mutated points + Returns: + Point: Mutated point """ - for p in iterator: - p.duration = self.duration - yield p + point.duration = self.duration + return point def to_dict(self): return {"typeid": self.typeid, "duration": self.duration} diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/mutators/randomoffsetmutator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/mutators/randomoffsetmutator.py index d2bfb07e0..4c4900820 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/mutators/randomoffsetmutator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/mutators/randomoffsetmutator.py @@ -1,5 +1,4 @@ from scanpointgenerator.core import Mutator -from scanpointgenerator.core import random @Mutator.register_subclass("scanpointgenerator:mutator/RandomOffsetMutator:1.0") @@ -18,92 +17,49 @@ def __init__(self, seed, axes, max_offset): """ self.seed = seed - self.RNG = random.Random(seed) self.axes = axes self.max_offset = max_offset - def get_random_number(self): - """ - Return a random number between -1.0 and 1.0 with Gaussian distribution - - Returns: - Float: Random number - """ - random_number = 2.0 - while abs(random_number) > 1.0: - random_number = self.RNG.random() - - return random_number - - def apply_offset(self, point): - """ - Apply a random offset to the Point - - Args: - point(Point): Point to apply random offset to - - Returns: - bool: Whether point was changed - """ - - changed = False + def calc_offset(self, axis, idx): + m = self.max_offset[axis] + x = (idx << 4) + (0 if len(axis) == 0 else ord(axis[0])) + x ^= (self.seed << 12) + # Apply hash algorithm to x for pseudo-randomness + # Robert Jenkins 32 bit hash (avalanches well) + x = (x + 0x7ED55D16) + (x << 12) + x &= 0xFFFFFFFF # act as 32 bit unsigned before doing any right-shifts + x = (x ^ 0xC761C23C) ^ (x >> 19) + x = (x + 0x165667B1) + (x << 5) + x = (x + 0xD3A2646C) ^ (x << 9) + x = (x + 0xFD7046C5) + (x << 3) + x &= 0xFFFFFFFF + x = (x ^ 0xB55A4F09) ^ (x >> 16) + x &= 0xFFFFFFFF + r = float(x) / float(0xFFFFFFFF) # r in interval [0, 1] + r = r * 2 - 1 # r in [-1, 1] + return m * r + + def mutate(self, point, idx): + inner_meta = None + point_offset = None for axis in self.axes: - offset = self.max_offset[axis] - if offset == 0.0: - pass - else: - random_offset = self.get_random_number() * offset - point.positions[axis] += random_offset - changed = True - - return changed - - @staticmethod - def calculate_new_bounds(current_point, next_point): - """ - Take two adjacent points and recalculate their shared bound - - Args: - next_point(Point): Next point - current_point(Point): Current point - """ - - for axis in current_point.positions.keys(): - new_bound = (current_point.positions[axis] + - next_point.positions[axis]) / 2 - - current_point.upper[axis] = new_bound - next_point.lower[axis] = new_bound - - def mutate(self, iterator): - """ - An iterator that takes another iterator, applies a random offset to - each point and then yields it - - Args: - iterator: Iterator to mutate - - Yields: - Point: Mutated points - """ - - next_point = current_point = None - - for next_point in iterator: - changed = self.apply_offset(next_point) - - if current_point is not None: - if changed: - # If point wasn't changed don't update bounds - if next_point.lower == current_point.upper: - # If leaving and re-entering ROI don't update bounds - self.calculate_new_bounds(current_point, next_point) - - yield current_point - - current_point = next_point - - yield next_point + offset = self.calc_offset(axis, idx) + point.positions[axis] += offset + if axis in point.lower and axis in point.upper: + inner_axis = axis + point_offset = offset + if inner_axis is not None: + # recalculate lower bounds + idx -= 1 + prev_offset = self.calc_offset(inner_axis, idx) + offset = (point_offset + prev_offset) / 2 + point.lower[inner_axis] += offset + # recalculate upper bounds + idx += 2 + next_offset = self.calc_offset(inner_axis, idx) + offset = (point_offset + next_offset) / 2 + point.upper[inner_axis] += offset + return point def to_dict(self): """Convert object attributes into a dictionary""" diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/plotgenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/plotgenerator.py index f884b75a5..39684ea9b 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/plotgenerator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/plotgenerator.py @@ -1,3 +1,5 @@ +from scanpointgenerator import CompoundGenerator, RectangularROI, CircularROI + MARKER_SIZE = 10 @@ -10,12 +12,16 @@ def plot_generator(gen, excluder=None, show_indexes=True): if excluder is not None: roi = excluder.roi overlay = plt.subplot(111, aspect='equal') - if roi.name == "Rectangle": - lower_left = (roi.centre[0] - roi.width/2, roi.centre[1] - roi.height/2) - overlay.add_patch(Rectangle(lower_left, roi.width, roi.height, fill=False)) - if roi.name == "Circle": + if isinstance(roi, RectangularROI): + overlay.add_patch(Rectangle(roi.start, roi.width, roi.height, fill=False)) + if isinstance(roi, CircularROI): overlay.add_patch(Circle(roi.centre, roi.radius, fill=False)) + if not isinstance(gen, CompoundGenerator): + excluders = [] if excluder is None else [excluder] + gen = CompoundGenerator([gen], excluders, []) + gen.prepare() + # points for spline generation x, y = [], [] # capture points and indexes @@ -61,9 +67,9 @@ def plot_generator(gen, excluder=None, show_indexes=True): y.append(point.upper.get("y", 0)) # # Plot labels - plt.xlabel("X (%s)" % gen.position_units["x"]) - if "y" in gen.position_units: - plt.ylabel("Y (%s)" % gen.position_units["y"]) + plt.xlabel("X (%s)" % gen.units["x"]) + if "y" in gen.units: + plt.ylabel("Y (%s)" % gen.units["y"]) else: plt.tick_params(left='off', labelleft='off') @@ -100,6 +106,6 @@ def plot_generator(gen, excluder=None, show_indexes=True): for i, x, y in zip(capi, capx, capy): plt.annotate(i, (x, y), xytext=(MARKER_SIZE/2, MARKER_SIZE/2), textcoords='offset points') - indexes = ["%s (size %d)" % z for z in zip(gen.index_names, gen.index_dims)] - plt.title("Dataset: [%s]" % (", ".join(indexes))) + #indexes = ["%s (size %d)" % z for z in zip(gen.index_names, gen.index_dims)] + #plt.title("Dataset: [%s]" % (", ".join(indexes))) plt.show() From c34377fc8d417b35ff1d56f1e187f4dbbf51f425 Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Fri, 24 Feb 2017 14:32:55 +0000 Subject: [PATCH 02/11] Change scisoftpy to numjy in scanpointgenerator compat.py --- .../scripts/scanpointgenerator/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/compat.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/compat.py index 023dce4a7..95378c521 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/compat.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/compat.py @@ -8,7 +8,7 @@ if os.name == 'java': - import scisoftpy as numpy + import numjy as numpy else: import numpy From b713176ebf286dcde4036114d97ae459c26a0fb4 Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Fri, 24 Feb 2017 14:33:31 +0000 Subject: [PATCH 03/11] Adjust jython_spg_interface.py to handle changes to spg interface Generators now need to be accessed through compound generator. CompoundGenerator needs to have its prepare method called. generator.num has been renamed to generator.size Comment out/pass over ArrayGenerator code for the moment, as it is not available in scanpointgenerator right now (it will be restored soon). --- .../scripts/jython_spg_interface.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/org.eclipse.scanning.points/scripts/jython_spg_interface.py b/org.eclipse.scanning.points/scripts/jython_spg_interface.py index 7da70a311..c9e0e80f1 100644 --- a/org.eclipse.scanning.points/scripts/jython_spg_interface.py +++ b/org.eclipse.scanning.points/scripts/jython_spg_interface.py @@ -5,7 +5,7 @@ from java.util import ArrayList from scanpointgenerator import LineGenerator -from scanpointgenerator import ArrayGenerator +#from scanpointgenerator import ArrayGenerator #NOT AVAILABLE YET from scanpointgenerator import SpiralGenerator from scanpointgenerator import LissajousGenerator from scanpointgenerator import CompoundGenerator @@ -66,7 +66,7 @@ def toDict(self): return self.generator.to_dict() def size(self): - return self.generator.num + return self.generator.size class JLineGenerator1D(JavaIteratorWrapper): @@ -78,7 +78,9 @@ def __init__(self, name, units, start, stop, num_points, alternate_direction=Fal super(JLineGenerator1D, self).__init__() self.name = name - self.generator = LineGenerator(name, units, start, stop, num_points, alternate_direction) + line_gen = LineGenerator(name, units, start, stop, num_points, alternate_direction) + self.generator = CompoundGenerator([line_gen], [], []) + self.generator.prepare() logging.debug(self.generator.to_dict()) def _iterator(self): @@ -103,7 +105,9 @@ def __init__(self, names, units, start, stop, num_points, alternate_direction=Fa stop = stop.tolist() self.names = names - self.generator = LineGenerator(names, units, start, stop, num_points, alternate_direction) + line_gen = LineGenerator(names, units, start, stop, num_points, alternate_direction) + self.generator = CompoundGenerator([line_gen], [], []) + self.generator.prepare() logging.debug(self.generator.to_dict()) def _iterator(self): @@ -122,6 +126,7 @@ def _iterator(self): class JArrayGenerator(JavaIteratorWrapper): + pass """ Create an ArrayGenerator and wrap the points into java Scalar objects """ @@ -155,11 +160,12 @@ def __init__(self, names, units, centre, radius, scale=1.0, alternate_direction= super(JSpiralGenerator, self).__init__() self.names = names - self.generator = SpiralGenerator(names, units, centre, radius, scale, alternate_direction) + spiral_gen = SpiralGenerator(names, units, centre, radius, scale, alternate_direction) + self.generator = CompoundGenerator([spiral_gen], [], []) + self.generator.prepare() logging.debug(self.generator.to_dict()) def _iterator(self): - x_name = self.names[0] y_name = self.names[1] @@ -183,7 +189,9 @@ def __init__(self, names, units, box, num_lobes, num_points): super(JLissajousGenerator, self).__init__() self.names = names - self.generator = LissajousGenerator(names, units, box, num_lobes, num_points) + liss_gen = LissajousGenerator(names, units, box, num_lobes, num_points) + self.generator = CompoundGenerator([liss_gen], [], []) + self.generator.prepare() logging.debug(self.generator.to_dict()) def _iterator(self): From 338c70ee349e8bfef388b0134160f014263b44e0 Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Fri, 24 Feb 2017 16:03:30 +0000 Subject: [PATCH 04/11] Add validation to input units in scanpointgenerator --- .../scanpointgenerator/generators/linegenerator.py | 3 ++- .../generators/lissajousgenerator.py | 10 ++++++---- .../scanpointgenerator/generators/spiralgenerator.py | 3 +++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/linegenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/linegenerator.py index 32a354528..ae4e6835f 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/linegenerator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/linegenerator.py @@ -26,7 +26,6 @@ def __init__(self, axes, units, start, stop, size, alternate=False): alternate(bool): Specifier to reverse direction if generator is nested """ - self.axes = to_list(axes) self.start = to_list(start) self.stop = to_list(stop) @@ -41,6 +40,8 @@ def __init__(self, axes, units, start, stop, size, alternate=False): len(self.axes) != len(self.stop): raise ValueError( "Dimensions of axes, start and stop do not match") + if len(self.axes) != len(self.units): + raise ValueError("Provided units do not match number of axes") self.size = size diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/lissajousgenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/lissajousgenerator.py index cba7a4913..75f8d5a04 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/lissajousgenerator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/lissajousgenerator.py @@ -23,14 +23,16 @@ def __init__(self, axes, units, box, num_lobes, curve. Default is 250 * num_lobes """ - self.axes = axes - self.units = {d:u for d,u in zip(axes, units)} - self.alternate = alternate - if len(self.axes) != len(set(self.axes)): raise ValueError("Axis names cannot be duplicated; given %s" % axes) + if len(units) != len(axes): + raise ValueError("Provided units do not match number of axes") + + self.axes = axes + self.units = {d:u for d,u in zip(axes, units)} + self.alternate = alternate num_lobes = int(num_lobes) self.x_freq = num_lobes diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/spiralgenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/spiralgenerator.py index 26f886a38..d06d96b4c 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/spiralgenerator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/spiralgenerator.py @@ -23,6 +23,9 @@ def __init__(self, axes, units, centre, radius, scale=1.0, generator is nested """ + if len(self.axes) != len(set(self.axes)): + raise ValueError("Axis names cannot be duplicated; given %s" % + axes) self.axes = axes self.centre = centre self.radius = radius From 311db9262a08e3a77adb1d81f3763a27299ddcc2 Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Fri, 24 Feb 2017 16:09:54 +0000 Subject: [PATCH 05/11] Add a parse method to Point --- .../eclipse/scanning/api/points/Point.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/org.eclipse.scanning.api/src/org/eclipse/scanning/api/points/Point.java b/org.eclipse.scanning.api/src/org/eclipse/scanning/api/points/Point.java index 3c5136b2e..8ed42286c 100644 --- a/org.eclipse.scanning.api/src/org/eclipse/scanning/api/points/Point.java +++ b/org.eclipse.scanning.api/src/org/eclipse/scanning/api/points/Point.java @@ -16,6 +16,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.scanning.api.annotation.UiHidden; @@ -133,4 +135,21 @@ public Map getIndices() { } return indices; } + + private static final String VERTEX = "([a-zA-Z0-9_])+\\((\\d+)\\)=([-+]?[0-9]*\\.?[0-9]+)"; + private static final Pattern POSITION = Pattern.compile(VERTEX + ", " + VERTEX); + + /** + * Parse a point from the toString() method into an instance of Point + * @param asString + * @return + */ + public static Point parse(String asString) { + Matcher m = POSITION.matcher(asString); + if (m.matches()) { + return new Point(m.group(4), Integer.parseInt(m.group(5)), Double.parseDouble(m.group(6)), + m.group(1), Integer.parseInt(m.group(2)), Double.parseDouble(m.group(3))); + } + throw new RuntimeException("Unparsable string" + asString); + } } From fb36b4cdd42f1b0a1bea108f4b01e1bcf362d57f Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Fri, 24 Feb 2017 16:07:23 +0000 Subject: [PATCH 06/11] Fixup unit tests after scanpointgenerator changes generator.names has been renamed to generator.axes generator.num has been renamed to generator.size Units given to generators now need to be an array matching the length of the given axes array. The random offset algorithm has changed, and value comparisons need to be updated. --- .../eclipse/scanning/points/LineIterator.java | 6 ++- .../scanning/test/points/CompoundTest.java | 19 +++++---- .../test/points/RandomOffsetGridTest.java | 42 +++++++++++-------- .../points/ScanPointGeneratorFactoryTest.java | 42 ++++++++----------- 4 files changed, 56 insertions(+), 53 deletions(-) diff --git a/org.eclipse.scanning.points/src/org/eclipse/scanning/points/LineIterator.java b/org.eclipse.scanning.points/src/org/eclipse/scanning/points/LineIterator.java index c95582807..a1acb3e9a 100644 --- a/org.eclipse.scanning.points/src/org/eclipse/scanning/points/LineIterator.java +++ b/org.eclipse.scanning.points/src/org/eclipse/scanning/points/LineIterator.java @@ -58,12 +58,13 @@ public LineIterator(OneDEqualSpacingGenerator gen) { double yStep = step * Math.sin(line.getAngle()); PyList names = new PyList(Arrays.asList(new String[] {model.getFastAxisName(), model.getSlowAxisName()})); + PyList units = new PyList(Arrays.asList(new String[] {"mm", "mm"})); double[] start = {line.getxStart() + xStep/2, line.getyStart() + yStep/2}; double[] stop = {line.getxStart() + xStep * (numPoints - 0.5), line.getyStart() + yStep * (numPoints - 0.5)}; @SuppressWarnings("unchecked") Iterator iterator = (Iterator) lineGeneratorFactory.createObject( - names, "mm", start, stop, numPoints); + names, units, start, stop, numPoints); pyIterator = iterator; } @@ -78,12 +79,13 @@ public LineIterator(OneDStepGenerator gen) { double yStep = model.getStep() * Math.sin(line.getAngle()); PyList names = new PyList(Arrays.asList(new String[] {model.getFastAxisName(), model.getSlowAxisName()})); + PyList units = new PyList(Arrays.asList(new String[] {"mm", "mm"})); double[] start = {line.getxStart(), line.getyStart()}; double[] stop = {line.getxStart() + xStep * numPoints, line.getyStart() + yStep * numPoints}; @SuppressWarnings("unchecked") Iterator iterator = (Iterator) lineGeneratorFactory.createObject( - names, "mm", start, stop, numPoints); + names, units, start, stop, numPoints); pyIterator = iterator; } diff --git a/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/CompoundTest.java b/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/CompoundTest.java index f1ed3cf8d..d6407439c 100644 --- a/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/CompoundTest.java +++ b/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/CompoundTest.java @@ -11,6 +11,7 @@ *******************************************************************************/ package org.eclipse.scanning.test.points; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -146,11 +147,11 @@ public void testSimpleToDict() throws Exception { PyList gens = (PyList) dict.get("generators"); PyDictionary line1 = (PyDictionary) gens.get(0); - assertEquals("Temperature", (String) ((PyList) line1.get("name")).get(0)); - assertEquals("mm", line1.get("units")); + assertEquals("Temperature", (String) ((PyList) line1.get("axes")).get(0)); + assertArrayEquals(new String[] {"mm"}, ((PyList) line1.get("units")).toArray()); assertEquals(290.0, (double) ((PyList) line1.get("start")).get(0), 1E-10); assertEquals(295.0, (double) ((PyList) line1.get("stop")).get(0), 1E-10); - assertEquals(6, (int) line1.get("num")); + assertEquals(6, (int) line1.get("size")); PyList excluders = (PyList) dict.get("excluders"); PyList mutators = (PyList) dict.get("mutators"); @@ -171,17 +172,17 @@ public void testNestedToDict() throws Exception { PyDictionary line1 = (PyDictionary) gens.get(0); PyDictionary line2 = (PyDictionary) gens.get(1); - assertEquals("Temperature", (String) ((PyList) line1.get("name")).get(0)); - assertEquals("mm", line1.get("units")); + assertEquals("Temperature", (String) ((PyList) line1.get("axes")).get(0)); + assertArrayEquals(new String[] {"mm"}, ((PyList) line1.get("units")).toArray()); assertEquals(290.0, (double) ((PyList) line1.get("start")).get(0), 1E-10); assertEquals(295.0, (double) ((PyList) line1.get("stop")).get(0), 1E-10); - assertEquals(6, (int) line1.get("num")); + assertEquals(6, (int) line1.get("size")); - assertEquals("Position", (String) ((PyList) line2.get("name")).get(0)); - assertEquals("mm", line2.get("units")); + assertEquals("Position", (String) ((PyList) line2.get("axes")).get(0)); + assertArrayEquals(new String[] {"mm"}, ((PyList) line1.get("units")).toArray()); assertEquals(1.0, (double) ((PyList) line2.get("start")).get(0), 1E-10); assertEquals(4.0, (double) ((PyList) line2.get("stop")).get(0), 1E-10); - assertEquals(6, (int) line2.get("num")); + assertEquals(6, (int) line2.get("size")); PyList excluders = (PyList) dict.get("excluders"); PyList mutators = (PyList) dict.get("mutators"); diff --git a/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/RandomOffsetGridTest.java b/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/RandomOffsetGridTest.java index 9d4abcfd4..3e239af36 100644 --- a/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/RandomOffsetGridTest.java +++ b/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/RandomOffsetGridTest.java @@ -12,15 +12,16 @@ package org.eclipse.scanning.test.points; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; -import java.util.List; +import java.util.Iterator; import org.eclipse.scanning.api.points.IPointGenerator; import org.eclipse.scanning.api.points.IPointGeneratorService; import org.eclipse.scanning.api.points.IPosition; -import org.eclipse.scanning.api.points.Point; import org.eclipse.scanning.api.points.models.BoundingBox; +import org.eclipse.scanning.api.points.models.GridModel; import org.eclipse.scanning.api.points.models.RandomOffsetGridModel; import org.eclipse.scanning.points.PointGeneratorService; import org.junit.Before; @@ -44,22 +45,27 @@ public void testSimpleBox() throws Exception { box.setFastAxisLength(5); box.setSlowAxisLength(10); - RandomOffsetGridModel model = new RandomOffsetGridModel("x", "y"); - model.setSlowAxisPoints(5); - model.setFastAxisPoints(5); - model.setBoundingBox(box); - model.setSeed(10); - model.setOffset(25); + RandomOffsetGridModel rm = new RandomOffsetGridModel("x", "y"); + rm.setSlowAxisPoints(5); + rm.setFastAxisPoints(5); + rm.setBoundingBox(box); + rm.setSeed(10); + rm.setOffset(25); + IPointGenerator rg = service.createGenerator(rm); + GeneratorUtil.testGeneratorPoints(rg, 5, 5); - IPointGenerator gen = service.createGenerator(model); - List pointList = gen.createPoints(); - - assertEquals(new Point("x", 0, 0.012403455250000084,"y", 0, 0.09924303325000006), pointList.get(0)); - assertEquals(new Point("x", 1, 0.837235318,"y", 0, 0.1643560529999999), pointList.get(1)); - assertEquals(new Point("x", 2, 2.20470153075,"y", 0, 0.018593022749999966), pointList.get(2)); - assertEquals(new Point("x", 3, 3.057925353,"y", 0, -0.024424061750000003), pointList.get(3)); - assertEquals(new Point("x", 4, 3.78130160075,"y", 0, 0.021858763000000003), pointList.get(4)); - assertEquals(new Point("x", 0, 0.09698760274999996,"y", 1, 1.83863665575), pointList.get(5)); + GridModel m = new GridModel("x", "y"); + m.setSlowAxisPoints(5); + m.setFastAxisPoints(5); + m.setBoundingBox(box); + IPointGenerator g = service.createGenerator(m); + + for (Iterator it1 = rg.iterator(), it2 = g.iterator(); it1.hasNext() && it2.hasNext();) { + IPosition t1 = it1.next(); + IPosition t2 = it2.next(); + assertTrue(Math.abs(t1.getValue("x") - t2.getValue("x")) <= 0.25 * 1.25); + assertTrue(Math.abs(t1.getValue("y") - t2.getValue("y")) <= 0.25 * 2.5); + } } } diff --git a/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/ScanPointGeneratorFactoryTest.java b/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/ScanPointGeneratorFactoryTest.java index beb9be824..5d70ecbfa 100644 --- a/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/ScanPointGeneratorFactoryTest.java +++ b/org.eclipse.scanning.test/src/org/eclipse/scanning/test/points/ScanPointGeneratorFactoryTest.java @@ -64,12 +64,13 @@ public void testJLineGeneratorFactory2D() { JythonObjectFactory lineGeneratorFactory = ScanPointGeneratorFactory.JLineGenerator2DFactory(); PyList names = new PyList(Arrays.asList(new String[] {"X", "Y"})); + PyList units = new PyList(Arrays.asList(new String[] {"mm", "mm"})); double[] start = {1.0, 2.0}; double[] stop = {5.0, 10.0}; @SuppressWarnings("unchecked") Iterator iterator = (Iterator) lineGeneratorFactory.createObject( - names, "mm", start, stop, 5); + names, units, start, stop, 5); List expected_points = new ArrayList(); expected_points.add(new Point("X", 0, 1.0, "Y", 0, 2.0, false)); @@ -160,12 +161,13 @@ public void testJLissajousGeneratorFactory() { box.put("centre", new double[] {0.0, 0.0}); PyList names = new PyList(Arrays.asList(new String[] {"X", "Y"})); + PyList units = new PyList(Arrays.asList(new String[] {"mm", "mm"})); int numLobes = 2; int numPoints = 500; @SuppressWarnings("unchecked") Iterator iterator = (Iterator) lissajousGeneratorFactory.createObject( - names, "mm", box, numLobes, numPoints); + names, units, box, numLobes, numPoints); List expected_points = new ArrayList(); expected_points.add(new Point("X", 0, 0.0, "Y", 0, 0.0, false)); @@ -263,28 +265,20 @@ public void testJCompoundGeneratorFactoryWithMutatedRaster() { generators, excluders, mutators); List expected_points = new ArrayList(); -// expected_points.add(new MapPosition("x:0:1.1984860665000001, y:0:2.0248069105")); -// expected_points.add(new MapPosition("x:1:2.328712106, y:0:1.674470636")); -// expected_points.add(new MapPosition("x:2:3.0371860455, y:0:2.4094030615")); -// expected_points.add(new MapPosition("x:3:3.9511518765, y:0:2.115850706")); -// expected_points.add(new MapPosition("x:4:5.043717526, y:0:1.5626032015")); -// expected_points.add(new MapPosition("x:0:0.6772733115, y:1:4.1939752055")); -// expected_points.add(new MapPosition("x:1:1.5828061555000001, y:1:3.9489767459999996")); -// expected_points.add(new MapPosition("x:2:3.3888981960000004, y:1:3.661987452")); -// expected_points.add(new MapPosition("x:3:3.9093635265, y:1:4.2730717205")); -// expected_points.add(new MapPosition("x:4:4.554744956, y:1:3.8436031415")); - expected_points.add(new Point("x", 0, 1.0248069105000002, "y", 0, 2.1984860665)); - expected_points.add(new Point("x", 1, 1.674470636, "y", 0, 2.328712106)); - expected_points.add(new Point("x", 2, 3.4094030615, "y", 0, 2.0371860455)); - expected_points.add(new Point("x", 3, 4.115850706, "y", 0, 1.9511518765)); - expected_points.add(new Point("x", 4, 4.5626032015, "y", 0, 2.043717526)); - expected_points.add(new Point("x", 0, 1.1939752055, "y", 1, 3.6772733115)); - expected_points.add(new Point("x", 1, 1.9489767459999996, "y", 1, 3.5828061555000001)); - expected_points.add(new Point("x", 2, 2.661987452, "y", 1, 4.3888981960000004)); - expected_points.add(new Point("x", 3, 4.2730717205, "y", 1, 3.9093635265)); - expected_points.add(new Point("x", 4, 4.8436031415, "y", 1, 3.554744956)); - - int index = 0; + // This list of values can be regenerated by running the iterator and printing + // the points. The toString() method is then inverted to a Point using parse. + expected_points.add(Point.parse("y(0)=2.3467273793292063, x(0)=1.3423125841287693")); + expected_points.add(Point.parse("y(0)=1.9389526430608128, x(1)=2.386072452619223")); + expected_points.add(Point.parse("y(0)=2.2767811479924203, x(2)=3.2131047487987914")); + expected_points.add(Point.parse("y(0)=1.585269318447744, x(3)=3.9674925148644236")); + expected_points.add(Point.parse("y(0)=2.397560627408689, x(4)=5.266805527444651")); + expected_points.add(Point.parse("y(1)=3.5015289536680863, x(0)=1.3197151394141176")); + expected_points.add(Point.parse("y(1)=4.341493416307841, x(1)=2.150155718356873")); + expected_points.add(Point.parse("y(1)=4.480657927454603, x(2)=3.377400373778632")); + expected_points.add(Point.parse("y(1)=4.1811883078611425, x(3)=4.067826935687993")); + expected_points.add(Point.parse("y(1)=4.308295570036465, x(4)=4.779800611147611")); + + int index = 0; while (iterator.hasNext() && index < 10){ // Just get first few points Object point = iterator.next(); From af0f34049332660ff030fe18014e66f28719122f Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Fri, 24 Feb 2017 16:10:33 +0000 Subject: [PATCH 07/11] Restore Excluders in CompoundSpgIterator --- .../src/org/eclipse/scanning/points/CompoundSpgIterator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/org.eclipse.scanning.points/src/org/eclipse/scanning/points/CompoundSpgIterator.java b/org.eclipse.scanning.points/src/org/eclipse/scanning/points/CompoundSpgIterator.java index 92800740d..ff3cc3ac6 100644 --- a/org.eclipse.scanning.points/src/org/eclipse/scanning/points/CompoundSpgIterator.java +++ b/org.eclipse.scanning.points/src/org/eclipse/scanning/points/CompoundSpgIterator.java @@ -14,7 +14,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; -import java.util.List; import org.eclipse.dawnsci.analysis.dataset.roi.CircularROI; import org.eclipse.dawnsci.analysis.dataset.roi.EllipticalROI; @@ -70,7 +69,7 @@ public CompoundSpgIterator(CompoundGenerator gen) throws GeneratorException { JythonObjectFactory compoundGeneratorFactory = ScanPointGeneratorFactory.JCompoundGeneratorFactory(); - Object[] excluders = {}; //getExcluders(gen.getModel().getRegions()); TODO put back in when excluders are fixed in Python + Object[] excluders = getExcluders(gen.getModel().getRegions()); Object[] mutators = getMutators(gen.getModel().getMutators()); @SuppressWarnings("unchecked") From ab2b2ed1f8e385224e99def39f38cedb022342d8 Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Wed, 1 Mar 2017 10:31:44 +0000 Subject: [PATCH 08/11] Fix GridWithROI scan test to not exclude all points with its region --- .../src/org/eclipse/scanning/test/command/MScanServletTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.scanning.test/src/org/eclipse/scanning/test/command/MScanServletTest.java b/org.eclipse.scanning.test/src/org/eclipse/scanning/test/command/MScanServletTest.java index 84214e655..81d732cbb 100644 --- a/org.eclipse.scanning.test/src/org/eclipse/scanning/test/command/MScanServletTest.java +++ b/org.eclipse.scanning.test/src/org/eclipse/scanning/test/command/MScanServletTest.java @@ -39,7 +39,7 @@ public void testGridScanNoDetector() throws Exception { @Test public void testGridWithROIScan() throws Exception { - pi.exec("sr = scan_request(grid(axes=('xNex', 'yNex'), start=(0.0, 1.0), stop=(10.0, 12.0), count=(3, 4), snake=False, roi=[circ(origin=(0.0, 0.0), radius=1.0)]), det=detector('mandelbrot', 0.1))"); + pi.exec("sr = scan_request(grid(axes=('xNex', 'yNex'), start=(0.0, 1.0), stop=(10.0, 12.0), count=(3, 4), snake=False, roi=[circ(origin=(0.0, 0.0), radius=2.0)]), det=detector('mandelbrot', 0.1))"); runAndCheck("sr", false, 10); } From 09ccd3f6b30b5f990117250b1a97ab0c7f4cfab2 Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Wed, 1 Mar 2017 11:09:41 +0000 Subject: [PATCH 09/11] Add ArrayGenerator (from WIP branch in ScanPointGenerator) --- .../scanpointgenerator/generators/__init__.py | 1 + .../generators/arraygenerator.py | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/__init__.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/__init__.py index 7e5b29d1f..8aafffbc0 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/__init__.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/__init__.py @@ -1,3 +1,4 @@ +from scanpointgenerator.generators.arraygenerator import ArrayGenerator from scanpointgenerator.generators.linegenerator import LineGenerator from scanpointgenerator.generators.lissajousgenerator import LissajousGenerator from scanpointgenerator.generators.spiralgenerator import SpiralGenerator diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py new file mode 100644 index 000000000..c09f4517e --- /dev/null +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py @@ -0,0 +1,66 @@ +from scanpointgenerator.compat import np +from scanpointgenerator.core import Generator + + +@Generator.register_subclass("scanpointgenerator:generator/ArrayGenerator:1.0") +class ArrayGenerator(Generator): + """Generate points fron a given list of positions""" + + def __init__(self, names, units, points, alternate_direction=False): + self.names = names + self.units = units + self.alternate_direction = alternate_direction + self.points = np.array(points, dtype=np.float64) + if self.points.shape == (len(self.points),): + self.points = self.points.reshape((len(self.points), 1)) + self.size = len(self.points) + self.position_units = {n:self.units for n in names} + if len(self.names) != len(set(self.names)): + raise ValueError("Axis names cannot be duplicated; given %s" % + names) + self.axes = self.names + + gen_name = "Array" + for axis_name in self.names[::-1]: + gen_name = axis_name + "_" + gen_name + self.index_names = [gen_name] + + def prepare_arrays(self, index_array): + points = self.points + # add linear extension to ends of points, representing t=-1 and t=N+1 + v_left = points[0] - (points[1] - points[0]) + v_right = points[-1] + (points[-1] - points[-2]) + points = np.insert(points, 0, v_left, 0) + points = np.append(points, [v_right], 0) + index_floor = np.floor(index_array).astype(np.int32) + epsilon = index_array - index_floor + epsilon = epsilon.reshape((-1, 1)) + + index_floor += 1 + + values = points[index_floor] + epsilon * (points[index_floor+1] - points[index_floor]) + values = values.T + arrays = {} + for (i, name) in enumerate(self.names): + arrays[name] = values[i] + return arrays + + def to_dict(self): + d = { + "typeid":self.typeid, + "names":self.names, + "units":self.units, + "points":self.points.ravel().tolist(), + "alternate_direction":self.alternate_direction, + } + return d + + @classmethod + def from_dict(cls, d): + names = d["names"] + units = d["units"] + alternate_direction = d["alternate_direction"] + flat_points = d["points"] + arr_shape = (int(len(flat_points) // len(names)), len(names)) + points = np.array(flat_points).reshape(arr_shape) + return cls(names, units, points, alternate_direction) From 48d90a07e6831223f9061db296d06592e5837c3e Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Wed, 1 Mar 2017 13:21:25 +0000 Subject: [PATCH 10/11] ArrayGenerator fixes --- .../scripts/jython_spg_interface.py | 7 ++-- .../generators/arraygenerator.py | 40 +++++++++++-------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/org.eclipse.scanning.points/scripts/jython_spg_interface.py b/org.eclipse.scanning.points/scripts/jython_spg_interface.py index c9e0e80f1..b54696db4 100644 --- a/org.eclipse.scanning.points/scripts/jython_spg_interface.py +++ b/org.eclipse.scanning.points/scripts/jython_spg_interface.py @@ -5,7 +5,7 @@ from java.util import ArrayList from scanpointgenerator import LineGenerator -#from scanpointgenerator import ArrayGenerator #NOT AVAILABLE YET +from scanpointgenerator import ArrayGenerator from scanpointgenerator import SpiralGenerator from scanpointgenerator import LissajousGenerator from scanpointgenerator import CompoundGenerator @@ -126,7 +126,6 @@ def _iterator(self): class JArrayGenerator(JavaIteratorWrapper): - pass """ Create an ArrayGenerator and wrap the points into java Scalar objects """ @@ -137,7 +136,9 @@ def __init__(self, name, units, points): points = points.tolist() # Convert from array to list self.name = name - self.generator = ArrayGenerator(name, units, points) + array_gen = ArrayGenerator(name, units, points) + self.generator = CompoundGenerator([array_gen], [], []) + self.generator.prepare() logging.debug(self.generator.to_dict()) def _iterator(self): diff --git a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py index c09f4517e..7f1fcccec 100644 --- a/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py +++ b/org.eclipse.scanning.points/scripts/scanpointgenerator/generators/arraygenerator.py @@ -1,27 +1,30 @@ from scanpointgenerator.compat import np from scanpointgenerator.core import Generator +def to_list(value): + if isinstance(value, list): + return value + else: + return [value] @Generator.register_subclass("scanpointgenerator:generator/ArrayGenerator:1.0") class ArrayGenerator(Generator): """Generate points fron a given list of positions""" - def __init__(self, names, units, points, alternate_direction=False): - self.names = names - self.units = units + def __init__(self, axes, units, points, alternate_direction=False): + self.axes = to_list(axes) + self.units = {d:u for (d, u) in zip(self.axes, to_list(units))} self.alternate_direction = alternate_direction self.points = np.array(points, dtype=np.float64) if self.points.shape == (len(self.points),): self.points = self.points.reshape((len(self.points), 1)) self.size = len(self.points) - self.position_units = {n:self.units for n in names} - if len(self.names) != len(set(self.names)): + if len(self.axes) != len(set(self.axes)): raise ValueError("Axis names cannot be duplicated; given %s" % - names) - self.axes = self.names + axes) gen_name = "Array" - for axis_name in self.names[::-1]: + for axis_name in self.axes[::-1]: gen_name = axis_name + "_" + gen_name self.index_names = [gen_name] @@ -30,8 +33,13 @@ def prepare_arrays(self, index_array): # add linear extension to ends of points, representing t=-1 and t=N+1 v_left = points[0] - (points[1] - points[0]) v_right = points[-1] + (points[-1] - points[-2]) - points = np.insert(points, 0, v_left, 0) - points = np.append(points, [v_right], 0) + shape = points.shape + shape = (shape[0] + 2,) + shape[1:] + extended = np.empty(shape, dtype=points.dtype) + extended[1:-1] = points + extended[0] = v_left + extended[-1] = v_right + points = extended index_floor = np.floor(index_array).astype(np.int32) epsilon = index_array - index_floor epsilon = epsilon.reshape((-1, 1)) @@ -41,15 +49,15 @@ def prepare_arrays(self, index_array): values = points[index_floor] + epsilon * (points[index_floor+1] - points[index_floor]) values = values.T arrays = {} - for (i, name) in enumerate(self.names): + for (i, name) in enumerate(self.axes): arrays[name] = values[i] return arrays def to_dict(self): d = { "typeid":self.typeid, - "names":self.names, - "units":self.units, + "axes":self.axes, + "units":[self.units[a] for a in self.axes], "points":self.points.ravel().tolist(), "alternate_direction":self.alternate_direction, } @@ -57,10 +65,10 @@ def to_dict(self): @classmethod def from_dict(cls, d): - names = d["names"] + axes = d["axes"] units = d["units"] alternate_direction = d["alternate_direction"] flat_points = d["points"] - arr_shape = (int(len(flat_points) // len(names)), len(names)) + arr_shape = (int(len(flat_points) // len(axes)), len(axes)) points = np.array(flat_points).reshape(arr_shape) - return cls(names, units, points, alternate_direction) + return cls(axes, units, points, alternate_direction) From 3c6ad04bca2d32fa5ea8a3b77b1aa3975314761e Mon Sep 17 00:00:00 2001 From: Charles Mita Date: Wed, 1 Mar 2017 13:21:49 +0000 Subject: [PATCH 11/11] Collapse indexes in jython_spg_interface when excluders join axes Matt G. tells me this should work out. --- .../scripts/jython_spg_interface.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/org.eclipse.scanning.points/scripts/jython_spg_interface.py b/org.eclipse.scanning.points/scripts/jython_spg_interface.py index b54696db4..706885c3e 100644 --- a/org.eclipse.scanning.points/scripts/jython_spg_interface.py +++ b/org.eclipse.scanning.points/scripts/jython_spg_interface.py @@ -249,6 +249,15 @@ def __init__(self, iterators, excluders, mutators): scan_name.add(axis) self.dimension_names.add(scan_name) + for excluder in excluders: + # axes connected by excluders are "unrolled" + matched_axes = [a for a in self.axes_ordering if a in excluder.scannables] + if len(matched_axes) == 0: + continue + inner_axis = matched_axes[0] + inner_idx = self.axes_ordering.index(inner_axis) + for a in matched_axes[1:]: + self.index_locations[a] = inner_idx logging.debug("Index Locations:") logging.debug(self.index_locations)