Пример #1
0
 def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):
     order_gen = b.baker(b.fixed_timer, kwargs={
         'count': self.config_dict["N"], 't': self.config_dict["t"]},
                         position_to_pass_through=(0, 3))
     return _exp.move_capture(self, {}, order_gen=order_gen,
                              func_list=func_list, save_mode=save_mode,
                              number=1, delay=0)
Пример #2
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):
        print self.scope.stage.position
        end = b.baker(b.max_fifth_col, args=['IMAGE_ARR', self.scope,
                                             self.initial_position])

        # Take measurements and move to position of maximum brightness.
        _exp.move_capture(self, {'x': self.config_dict['raster3d_n_step'],
                                 'y': self.config_dict['raster3d_n_step'],
                                 'z': self.config_dict['raster3d_n_step']},
                          func_list=func_list, save_mode=save_mode,
                          end_func=end)
        print self.scope.stage.position
Пример #3
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):

        end = b.baker(b.max_fifth_col, args=['IMAGE_ARR', self.scope,
                                             self.initial_position])

        # Take measurements and move to position of maximum brightness.
        # end_func is applied at the end of every set of (n, step).
        for n_step in self.config_dict['raster_n_step']:
            _exp.move_capture(self, {'x': [n_step], 'y': [n_step]},
                              func_list=func_list, save_mode=save_mode,
                              end_func=end)
        print self.scope.stage.position
Пример #4
0
 def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):
     order_gen = b.baker(b.fixed_timer,
                         kwargs={
                             'count': self.config_dict["N"],
                             't': self.config_dict["t"]
                         },
                         position_to_pass_through=(0, 3))
     _exp.move_capture(self, {},
                       order_gen=order_gen,
                       func_list=func_list,
                       save_mode=save_mode,
                       number=300,
                       delay=0)
Пример #5
0
 def run(self,
         func_list=b.baker(b.unchanged),
         save_mode='save_final',
         axis='x'):
     """Operates on one axis at a time."""
     # Get default values.
     step_pair = (self.config_dict["parabola_N"],
                  self.config_dict["parabola_step"])
     end = b.baker(b.move_to_parmax, args=['IMAGE_ARR', self.scope, axis])
     _exp.move_capture(self, {axis: [step_pair]},
                       func_list=func_list,
                       save_mode=save_mode,
                       end_func=end)
Пример #6
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):

        end = b.baker(b.max_fifth_col,
                      args=['IMAGE_ARR', self.scope, self.initial_position])

        # Take measurements and move to position of maximum brightness.
        _exp.move_capture(self, {
            'x': self.config_dict['raster_n_step'],
            'y': self.config_dict['raster_n_step']
        },
                          func_list=func_list,
                          save_mode=save_mode,
                          end_func=end)
        print self.scope.stage.position
Пример #7
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):
        """Default is to sleep for 10 minutes."""
        # Do an initial alignment and then take that position as the initial
        # position.

        align = Align(self.scope, self.config_dict, group=self.gr)
        hillwalk = HillWalk(self.scope, self.config_dict, group=self.gr)
        sleep_times = self.config_dict['sleep_times']

        drifts = []
        for i in xrange(len(sleep_times)):
            if i == 0:
                align.run(func_list=func_list, save_mode=save_mode)
            else:
                hillwalk.run()
            pos = self.scope.stage.position
            t.sleep(sleep_times[i])
            if i == 0:
                last_pos = pos
            drift = pos - last_pos
            last_pos = pos
            drifts.append([sleep_times[i], drift])

        # Measure the position after it has drifted by working out how much
        # it needs to move by to re-centre it.
        self.gr.create_dataset('Drift', data=np.array(drifts))
Пример #8
0
    def run(self, save_mode='save_final'):
        # At the end, move to the position of maximum brightness.
        end = b.baker(b.max_fifth_col, args=['IMAGE_ARR', self.scope,
                                             self.initial_position])

        for n_step in self.config_dict['mmt_range']:
            # Allow the iteration to take place as many times as specified
            # in the scope_dict file.
            _exp.move_capture(self, {'z': [n_step]}, save_mode=save_mode,
                              end_func=end)
            print self.scope.stage.position
Пример #9
0
    def run(self, save_mode='save_final'):
        # At the end, move to the position of maximum brightness.
        end = b.baker(b.max_fifth_col,
                      args=['IMAGE_ARR', self.scope, self.initial_position])

        for n_step in self.config_dict['mmt_range']:
            # Allow the iteration to take place as many times as specified
            # in the scope_dict file.
            _exp.move_capture(self, {'z': [n_step]},
                              save_mode=save_mode,
                              end_func=end)
            print self.scope.stage.position
Пример #10
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final',
            max_step=10, min_step=1, number=100, delay=0):
        raster = RasterXY(self.scope, self.config_dict, group=self.gr,
                          group_name='KeepCentred')
        raster.run(func_list=func_list, save_mode=save_mode)

        hilly = HillWalk(self.scope, self.config_dict, group=self.gr)
        while True:
            try:
                hilly.run(max_step=max_step, min_step=min_step, number=number,
                          delay=delay)
            except KeyboardInterrupt:
                break
Пример #11
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):
        raster = RasterXY(self.scope,
                          self.config_dict,
                          group=self.gr,
                          group_name='KeepCentred')
        raster.run(func_list=func_list, save_mode=save_mode)

        # TODO Insert a better algorithm here for fine alignment!
        align_fine = HillWalk(self.scope, self.config_dict, group=self.gr)
        while True:
            try:
                align_fine.run()
            except KeyboardInterrupt:
                break
Пример #12
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):
        """Algorithm for alignment is to iterate the RasterXY procedure several
        times with decreasing width and increasing precision, and then using
        the parabola of brightness to try and find the maximum point by
        shifting slightly."""

        raster_set = RasterXY(self.scope, self.config_dict, group=self.gr,
                              raster_n_step=self.config_dict['n_steps'])
        # Take measurements and move to position of maximum brightness. All
        # arrays measurements will be taken before moving on.
        raster_set.run(func_list=func_list, save_mode=save_mode)

        hilly = HillWalk(self.scope, self.config_dict, group=self.gr)
        hilly.run()
Пример #13
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):
        """Algorithm for alignment is to iterate the RasterXY procedure several
        times with decreasing width and increasing precision, and then using
        the parabola of brightness to try and find the maximum point by
        shifting slightly."""

        raster_set = RasterXY(self.scope,
                              self.config_dict,
                              group=self.gr,
                              raster_n_step=self.config_dict['n_steps'])
        # Take measurements and move to position of maximum brightness. All
        # arrays measurements will be taken before moving on.
        raster_set.run(func_list=func_list, save_mode=save_mode)

        par = ParabolicMax(self.scope, self.config_dict, group=self.gr)
        for i in xrange(self.config_dict["parabola_iterations"]):
            for ax in ['x', 'y']:
                par.run(func_list=func_list, save_mode=save_mode, axis=ax)
Пример #14
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final'):
        for i in range(2):
            # TODO GENERALISE THE NUMBER
            if i == 0:
                raster_2d = RasterXY(self.scope,
                                     self.config_dict,
                                     group=self.gr,
                                     raster_n_step=[[39, 500]])
            else:
                raster_2d = RasterXY(self.scope,
                                     self.config_dict,
                                     group=self.gr)
            raster_2d.run(func_list=func_list, save_mode=save_mode)

            along_z = AlongZ(self.scope,
                             self.config_dict,
                             group=self.gr,
                             mmt_range=self.config_dict["mmt_range"])
            along_z.run(save_mode=save_mode)
Пример #15
0
    def run(self, func_list=b.baker(b.unchanged), save_mode='save_final',
            number=1000, delay=0.1, initial_align=False):
        """Default is to measure for 100s. See the config file for sleep
        times."""
        # Do an initial alignment and then take that position as the initial
        # position.

        align = Align(self.scope, self.config_dict, group=self.gr)
        hill_walk = HillWalk(self.scope, self.config_dict, group=self.gr)
        timed_mmts = TimedMeasurements(self.scope, self.config_dict,
                                       group=self.gr, N=1, t=0)
        sleep_times = self.config_dict['sleep_times']

        drifts = []
        for i in xrange(len(sleep_times)):
            if i == 0 and initial_align:
                align.run(func_list=func_list, save_mode=save_mode)
            else:
                hill_walk.run()
            pos = self.scope.stage.position
            sleep_start = t.time()
            timed_list = []
            while _exp.elapsed(sleep_start) < sleep_times[i]:
                timed_list.append(timed_mmts.run(save_mode=None))
            self.gr.create_dataset('timed_run', data=np.array(timed_list),
                                   attrs={'number': number, 'delay': delay,
                                          'sleep_time': sleep_times[i]})
            if i == 0:
                last_pos = pos
            drift = pos - last_pos
            last_pos = pos
            drifts.append([sleep_times[i], drift[0], drift[1], drift[2]])

        # Measure the position after it has drifted by working out how much
        # it needs to move by to re-centre it.
        self.gr.create_dataset('Drift', data=np.array(drifts), attrs={
            'number': number, 'delay': delay})
Пример #16
0
def move_capture(exp_obj, positions_dict, func_list=b.baker(b.unchanged),
                 order_gen=b.baker(b.raster, position_to_pass_through=(0, 3)),
                 save_mode='save_subset', end_func=b.baker(b.unchanged),
                 valid_keys=('x', 'y', 'z'), number=100, delay=0):
    """Function to carry out a sequence of measurements as per iter_list,
    take an image at each position, post-process it and return a final result.

    :param exp_obj: The experiment object.

    :param positions_dict: A dictionary of lists of 2-tuples to indicate all
    positions where images should be taken:
        {'x': [(n_x1, step_x1), ...], 'y': [(n_y1, step_y1), ...], 'z': ...],
    where each key indicates the axis to move, 'n' is the number of times
    to step (resulting in n+1 images) and 'step' is the number of microsteps
    between each subsequent image. So {'x': [(3, 100)]} would move only the
    x-axis of the microscope, taking 4 images at x=-150, x=-50, x=50,
    x=150 microsteps relative to the initial position. Note that:
    - Not all keys need to be specified.
    - All lists must be the same length.
    - All measurements will be taken symmetrically about the initial position.

    The position of each tuple in the list is important. If we have
    {'x': [(1, 100), (0, 100)],
     'y': [(2,  50), (3,  40)]},
    then tuples of the same index from each list will be combined into an
    array. This means that for the 0th index of the list, for x we have the
    positions [-50, 50] and [0] and for y [-50, 0, 50] and [-60, -20, 20, 60]
    respectively. [-50, 50] and [-50, 0, 50] will be combined to get the
    resulting array of [[-50, -50], [-50, 0], [-50, 50], [50, -50], [50, 0],
    [50, 50]], and the latter two to get [[0, -60], [0, -20], [0, 20],
    [0, 60]]. These are all the positions the stage will move to (the format
    here is [x, y]), iterating through each array in the order given before
    applying end_func ONCE RIGHT AT THE END. To run after each array,
    run this function multiple times.

    If you prefer to take images once for all the 'x' and, separately, once
    for all the 'y', run this function twice, once for 'x', once for 'y'.

    :param func_list: The post-processing curried function list, created using
    the baker function in the _experiments.py module.

    :param order_gen: A generator object that determines the order to visit
    each position. Takes arguments (x_positions_array, y_positions_arry,
    z_positions_array, intial_position_vector).

    :param save_mode: How to save at the end of each iteration.
    - 'save_each': Every single measurement is saved: {'x': [(3, 100)]}
      would result in 4 post-processed results being saved, which is useful if
      the post-processed results are image arrays.
    - 'save_final': Every single measurement is made before the entire set of
      results, as an array, is saved along with their positions, in the
      format [[x-column], [y-column], [z-column], [measurements-column]]. This
      is good for the post-processed results that are single numerical value.
    - 'save_subset': Each array is measured before being saved (for example, in
      the description of iter_dict, there are two arrays being iterated
      through).
    - None: Data is not saved at all, but is returned. Might be useful if
      this is intermediate step.

    :param end_func: A curried function, which is executed on the array of
    final results. This can be useful to move to a position where the final
    measurement is maximised. Note: if save_mode is 'save_each', the results
    array will be empty so end_func must be None.

    :param valid_keys: A tuple of strings containing all keys that are valid
    ways to move. if rotational degrees of freedom are introduced, this can
    include theta, etc.

    :param number: The number of measurements to average over at each position.

    :param delay: The time delay between each individual measurement taken
    at a specific position."""

    exp_obj.scope.sensor.ignore_saturation = False

    # Verify positions_dict format. The num_arrays is the number of arrays
    # of positions that will be measured in sequence.
    num_arrays = _verify_positions(positions_dict, valid_keys=valid_keys)

    # Get initial position, which may not be [0, 0, 0] if scope object
    # has been used for something else prior to this experiment.
    initial_position = exp_obj.scope.stage.position
    # A set of results to be collected if save_mode == 'save_final'.
    results = []

    for i in xrange(num_arrays):
        # For the length of each list, combine every group of tuples in the
        # same position to get array of positions to move to.
        move_by = {}
        for key in valid_keys:
            try:
                (n, steps) = positions_dict[key][i]
                move_by[key] = np.linspace(-n / 2. * steps, n / 2. * steps,
                                           n + 1)
            except KeyError:
                # If key does not exist, then keep this axis fixed.
                move_by[key] = np.array([0])

        # Generate array of positions to move to.
        pos = order_gen(move_by['x'], move_by['y'],
                        move_by['z'], initial_position)
        try:
            # For each position in the range specified, take an image, apply
            # all the functions in func_list on it, then either save the
            # measurement if save_mode = 'save_final', or append the
            # calculation to a results file and save it all at the end.
            while True:
                results = read_move_save(exp_obj, pos, func_list, save_mode,
                                         number, delay, results)

        except (StopIteration, KeyboardInterrupt) as e:
            # Iterations finished - save the subset of results.
            if save_mode == 'save_subset' or (save_mode == 'save_final'
                                              and e is KeyboardInterrupt):
                save_results(exp_obj, results, number, delay, why_ended=str(e))
            if e is KeyboardInterrupt:
                # Move to original position and exit program.
                print "Aborted, moving back to initial position. Exiting " \
                      "program."
                exp_obj.scope.stage.move_to_pos(initial_position)
                sys.exit()

        except b.NonZeroReading:
            # After reading a non-zero value stay at that current position,
            # save all results up to that point.
            print "Non-zero value read."

        except b.Saturation:
            # If the max value has been read, then the user must turn down
            # the gain. Once turned down, this entire move_sequence must be
            # re-measured.
            return move_capture(exp_obj, positions_dict, func_list, order_gen,
                                save_mode, end_func, valid_keys, number, delay)

    exp_obj.scope.sensor.ignore_saturation = False
    if save_mode != 'save_each':
        if save_mode == 'save_final':
            save_results(exp_obj, results, number, delay, 'brightness_final')
        elif save_mode != 'save_subset' and save_mode is not None:
            raise ValueError('Invalid save mode.')

        # Process the result and return it. Remember end_func is unchanged
        # by default, which returns the array as-is.
        return end_func(np.array(results))
    elif save_mode == 'save_each':
        if end_func is not None or end_func is not b.baker(b.unchanged):
            w.warn('end_func will not be used when the save_mode is '
                   '\'save_each\', as it is here.')
    else:
        raise ValueError('Invalid save_mode.')
Пример #17
0
    def run(self, save_mode='save_final'):

        initial_pos = self.scope.stage.position
        self.scope.sensor.ignore_saturation = False
        xy_results = []
        maxima_values = []
        z_step = self.step_size[2]
        z_direction = 1

        for i in range(3):
            while np.any(self.step_size[:2] > self.config_dict['min_step']):
                # Changed the algorithm to align well in x and y, then adjust z
                # slightly and see the difference
                for axis in [[1, 0, 0], [0, 1, 0]]:
                    count = 0   # Track how many times that axis has been
                    # measured this time.
                    axis_index = axis.index(1) + 1
                    print 'axis', axis_index
                    while True:
                        try:
                            results = []
                            current_pos = self.scope.stage.position
                            positions = self.next_positions(axis, current_pos)
                            gen = b.yield_pos(positions)

                            func_list = b.baker(b.saturation_reached,
                                                args=['mmt-placeholder',
                                                      self.scope.sensor])
                            count += 1
                            while True:
                                results = _exp.read_move_save(
                                    self, gen, func_list, save_mode, self.number,
                                    self.delay, results)

                        except b.Saturation:
                            # If the measurement saturates, then take that set of
                            # measurements again, with the new values of parameters
                            # that change (step_size, number, delay). Don't ignore
                            # saturation exceptions here, as this is fine
                            # alignment!

                            results = np.array(results)
                            _exp.save_results(self, results, self.number,
                                              self.delay, why_ended='Saturation')
                            self.scope.stage.move_to_pos(current_pos)
                            continue

                        except KeyboardInterrupt:
                            if save_mode == 'save_subset' or save_mode == \
                                    'save_final':
                                _exp.save_results(
                                    self, results, self.number, self.delay,
                                    why_ended=str(KeyboardInterrupt))

                            sys.exit()

                        except StopIteration:
                            # Iterations finished - save the subset of results.
                            results = np.array(results)
                            # Note only unsaturated results are saved in
                            # xy_results, and that later, only results with the
                            # lowest gain are used in calculation.
                            xy_results.append(results)
                            _exp.save_results(
                                self, results, self.number, self.delay,
                                why_ended=str(StopIteration))

                            if self.process_com(results, axis_index) or \
                                    count >= 5:
                                print "breaking"
                                break

                        self.scope.sensor.ignore_saturation = False
                    self.step_size[axis_index - 1] /= 2
                    print "step size is now ", self.step_size

            # After aligning in x and y, note down the position, max brightness
            # and width of the peak. Note we get width from the set of previous
            # xy_results, by looking at where brightness exceeds the half
            # maximum.
            peak_position = self.scope.stage.position
            brightness = self.scope.sensor.average_n(self.number, t=self.delay)
            xy_results = np.array(xy_results)

            # We need to take results with the same value of gain, so separate
            # such that only the lowest values of gain (strongest signals) are
            # kept.
            lowest_gain = xy_results[np.where(xy_results[:, :, 8] == np.min(
                xy_results[:, :, 8]))]
            above_half_max = lowest_gain[np.where(lowest_gain[:, :, 4] >= 0.5 *
                                                  brightness[0])]

            # Get a rough measure of the width of the xy region by just finding
            # the average of the range in each of x and y.
            width = np.mean((self.arr_range(above_half_max[:, :, 1]),
                             self.arr_range(above_half_max[:, :, 2])))

            # Append this to maxima values to store details of the brightness.
            maxima_values.append([peak_position[0], peak_position[1],
                                  peak_position[2], brightness[0],
                                  brightness[1], width])
            print "maxima values", maxima_values

            try:
                # Ensure last reading is outside the range of the previous
                # readings and its error.
                if not (maxima_values[-2][3] - maxima_values[-2][4] <=
                        maxima_values[-1][3] <= maxima_values[-2][3] +
                        maxima_values[-2][4]):

                    if maxima_values[-2][3] >= maxima_values[-1][3] and \
                            maxima_values[-2][5] <= maxima_values[-1][5]:
                        # If the readings are getting dimmer and wider, change
                        # z direction.
                        z_direction *= -1
            except IndexError:
                # Not enough readings to compare this.
                pass

            # Move in z by a step size that decreases slowly. Reset x and y
            # step sizes after each z motion. TODO Allow z step size to reduce.
            self.scope.stage.focus_rel(z_step * z_direction)
            print "moved to pos", self.scope.stage.position
            self.reset()

        _exp.save_results(self, np.array(maxima_values), self.number,
                          self.delay, name='maxima_values',
                          why_ended='complete')
Пример #18
0
def move_capture(exp_obj,
                 positions_dict,
                 func_list=b.baker(b.unchanged),
                 order_gen=b.baker(b.raster, position_to_pass_through=(0, 3)),
                 save_mode='save_subset',
                 end_func=b.baker(b.unchanged),
                 valid_keys=('x', 'y', 'z'),
                 number=100,
                 delay=0):
    """Function to carry out a sequence of measurements as per iter_list,
    take an image at each position, post-process it and return a final result.

    :param exp_obj: The experiment object.

    :param positions_dict: A dictionary of lists of 2-tuples to indicate all
    positions where images should be taken:
        {'x': [(n_x1, step_x1), ...], 'y': [(n_y1, step_y1), ...], 'z': ...],
    where each key indicates the axis to move, 'n' is the number of times
    to step (resulting in n+1 images) and 'step' is the number of microsteps
    between each subsequent image. So {'x': [(3, 100)]} would move only the
    x-axis of the microscope, taking 4 images at x=-150, x=-50, x=50,
    x=150 microsteps relative to the initial position. Note that:
    - Not all keys need to be specified.
    - All lists must be the same length.
    - All measurements will be taken symmetrically about the initial position.

    The position of each tuple in the list is important. If we have
    {'x': [(1, 100), (0, 100)],
     'y': [(2,  50), (3,  40)]},
    then tuples of the same index from each list will be combined into an
    array. This means that for the 0th index of the list, for x we have the
    positions [-50, 50] and [0] and for y [-50, 0, 50] and [-60, -20, 20, 60]
    respectively. [-50, 50] and [-50, 0, 50] will be combined to get the
    resulting array of [[-50, -50], [-50, 0], [-50, 50], [50, -50], [50, 0],
    [50, 50]], and the latter two to get [[0, -60], [0, -20], [0, 20],
    [0, 60]]. These are all the positions the stage will move to (the format
    here is [x, y]), iterating through each array in the order given.

    If you prefer to take images once for all the 'x' and, separately, once
    for all the 'y', run this function twice, once for 'x', once for 'y'.

    :param func_list: The post-processing curried function list, created using
    the baker function in the _experiments.py module.

    :param order_gen: A generator object that determines the order to visit
    each position. Takes arguments (x_positions_array, y_positions_arry,
    z_positions_array, intial_position_vector).

    :param save_mode: How to save at the end of each iteration.
    - 'save_each': Every single measurement is saved: {'x': [(3, 100)]}
      would result in 4 post-processed results being saved, which is useful if
      the post-processed results are image arrays.
    - 'save_final': Every single measurement is made before the entire set of
      results, as an array, is saved along with their positions, in the
      format [[x-column], [y-column], [z-column], [measurements-column]]. This
      is good for the post-processed results that are single numerical value.
    - 'save_subset': Each array is measured before being saved (for example, in
      the description of iter_dict, there are two arrays being iterated
      through).
    - None: Data is not saved at all, but is returned. Might be useful if
      this is intermediate step.

    :param end_func: A curried function, which is executed on the array of
    final results. This can be useful to move to a position where the final
    measurement is maximised. Note: if save_mode is 'save_each', the results
    array will be empty so end_func must be None.

    :param valid_keys: A tuple of strings containing all keys that are valid
    ways to move. if rotational degrees of freedom are introduced, this can
    include theta, etc.

    :param number: The number of measurements to average over at each position.

    :param delay: The time delay between each individual measurement taken
    at a specific position."""

    b.ignore_saturation = False

    # Verify positions_dict format. The num_arrays is the number of arrays
    # of positions that will be measured in sequence.
    num_arrays = _verify_positions(positions_dict, valid_keys=valid_keys)

    # Get initial position, which may not be [0, 0, 0] if scope object
    # has been used for something else prior to this experiment.
    initial_position = exp_obj.scope.stage.position
    # A set of results to be collected if save_mode == 'save_final'.
    results = []

    for i in xrange(num_arrays):
        # For the length of each list, combine every group of tuples in the
        # same position to get array of positions to move to.
        move_by = {}
        for key in valid_keys:
            try:
                (n, steps) = positions_dict[key][i]
                move_by[key] = np.linspace(-n / 2. * steps, n / 2. * steps,
                                           n + 1)
            except KeyError:
                # If key does not exist, then keep this axis fixed.
                move_by[key] = np.array([0])
        print "move-by", move_by
        # Generate array of positions to move to.
        pos = order_gen(move_by['x'], move_by['y'], move_by['z'],
                        initial_position)
        attrs = {'mmts_per_reading': number, 'delay_between': delay}
        try:
            # For each position in the range specified, take an image, apply
            # all the functions in func_list on it, then either save the
            # measurement if save_mode = 'save_final', or append the
            # calculation to a results file and save it all at the end.
            while True:
                next_pos = next(pos)  # This returns StopIteration at end.
                exp_obj.scope.stage.move_to_pos(next_pos)

                # Mmt is returned as a tuple of (mean brightness,
                # error_in_mean).
                mmt = exp_obj.scope.sensor.average_n(number, t=delay)
                processed = mmt

                # Post-process. Converting to list first allows even single
                # functions to be understood.
                try:
                    len(func_list)
                except TypeError:
                    func_list = [func_list]

                for function in func_list:
                    processed = function(processed)

                # Save this array in HDF5 file.
                if save_mode == 'save_each':
                    exp_obj.gr.create_dataset('post_processed',
                                              attrs={
                                                  'Position':
                                                  exp_obj.scope.stage.position,
                                                  'mmts_per_reading': number,
                                                  'delay_between': delay
                                              },
                                              data=processed)
                else:
                    # The curried function and 'save_final' both use the
                    # array of final results.
                    reading = [
                        elapsed(exp_obj.scope.start),
                        exp_obj.scope.stage.position[0],
                        exp_obj.scope.stage.position[1],
                        exp_obj.scope.stage.position[2], processed[0],
                        processed[1]
                    ]
                    print reading
                    results.append(reading)

        except StopIteration:
            # Iterations finished - save the subset of results and take the
            # next set.
            if save_mode == 'save_subset':
                # Save after every array of motions.
                results = np.array(results, dtype=np.float)
                exp_obj.gr.create_dataset('brightness_subset',
                                          data=results,
                                          attrs=attrs)

        except KeyboardInterrupt:
            print "Aborted, moving back to initial position."
            exp_obj.scope.stage.move_to_pos(initial_position)
            print "Exiting program."
            sys.exit()

        except b.NonZeroReading:
            # After reading a non-zero value stay at that current position,
            # save all results up to that point.
            print "Non-zero value read."

        except b.Saturation:
            # If the max value has been read, then the user must turn down
            # the gain. Once turned down, this entire move_sequence must be
            # re-measured.
            return move_capture(exp_obj, positions_dict, func_list, order_gen,
                                save_mode, end_func, valid_keys, number, delay)

    results = np.array(results, dtype=np.float)
    b.ignore_saturation = False
    if save_mode != 'save_each':
        if save_mode == 'save_final':
            exp_obj.gr.create_dataset('brightness_final',
                                      data=results,
                                      attrs={
                                          'mmts_per_reading': number,
                                          'delay_between': delay
                                      })
        elif all(save_mode != valid_mode
                 for valid_mode in ['save_subset', None]):
            raise ValueError('Invalid save mode.')

        # Process the result and return it. Remember end_func is unchanged
        # by default, which returns the array as-is.
        return end_func(results)
    elif save_mode == 'save_each':
        if end_func is not None or end_func is not b.baker(b.unchanged):
            w.warn('end_func will not be used when the save_mode is '
                   '\'save_each\', as it is here.')
    else:
        raise ValueError('Invalid save_mode.')