Exemple #1
0
    def connect(self):
        """Connect producer with consumers."""
        started = []
        for not_started in self.consumers:
            started.append(not_started())

        inject(self.producer(), broadcast(*started))
Exemple #2
0
    def __call__(self):
        """Run the acquisition, i.e. connect the producer and consumer."""
        LOG.debug("Running acquisition '{}'".format(self))

        started = []
        for not_started in self.consumers:
            started.append(not_started())

        inject(self.generator(), broadcast(*started))
Exemple #3
0
    async def test_broadcast(self):
        async def consume(producer):
            nonlocal data
            async for item in producer:
                data = item

        data = None
        coros = broadcast(self.produce(), self.consume, consume)
        await asyncio.gather(*coros)
        self.assertEqual(self.data, 4)
        self.assertEqual(data, 4)
Exemple #4
0
    async def test_broadcast_consumer_break(self):
        async def consume(producer):
            nonlocal data
            async for item in producer:
                data = item
                break

        data = None
        coros = broadcast(self.produce(), self.consume, consume)
        await asyncio.gather(*coros)
        # First gets the whole stream, the breaking one just the item before break
        self.assertEqual(self.data, 4)
        self.assertEqual(data, 0)
Exemple #5
0
    async def __call__(self):
        """Run the acquisition, i.e. acquire the data and connect the producer and consumers."""
        LOG.debug(f"Running acquisition '{self.name}'")
        consumers = self.consumers
        if not consumers:
            LOG.debug(f"`{self.name}' has no consumers, using null")
            consumers = [null]

        if self.acquire:
            await self.acquire()

        coros = broadcast(self.producer(), *consumers)
        await asyncio.gather(*coros, return_exceptions=False)
def plot_exposure_scan(min_exposure=1*q.ms, max_exposure=500*q.ms, num_points=10):
    """
    Plot the mean value of the detector image for exposure times between
    *min_exposure* and *max_exposure*, use *num_points* data points.

    Returns: a tuple with exposure times and corresponding mean values.
    """
    accum = Accumulate()
    region = Region(camera['exposure_time'], np.linspace(min_exposure, max_exposure, num_points))

    with camera.recording():
        inject(resolve(get_exposure_result(region)), broadcast(PyplotViewer(style='-o')(), accum()))

    return zip(*accum.items)
Exemple #7
0
    async def test_broadcast_cancel(self):
        produced = asyncio.Event()

        async def produce():
            for i in range(3):
                yield i
                produced.set()
                # Allow future.cancel() to kick in
                await asyncio.sleep(0)

        coros = broadcast(produce(), self.consume)
        future = asyncio.gather(*coros)
        await produced.wait()
        future.cancel()
        await asyncio.wait([future])
        self.assertEqual(self.data, 0)
Exemple #8
0
 def test_broadcast(self):
     self.producer(broadcast(self.consume(), self.consume_2()))
     self.assertEqual(self.data, 4)
     self.assertEqual(self.data_2, 4)
Exemple #9
0
async def align_rotation_axis(camera,
                              rotation_motor,
                              x_motor=None,
                              z_motor=None,
                              get_ellipse_points=find_needle_tips,
                              num_frames=10,
                              metric_eps=None,
                              position_eps=0.1 * q.deg,
                              max_iterations=5,
                              initial_x_coeff=1 * q.dimensionless,
                              initial_z_coeff=1 * q.dimensionless,
                              shutter=None,
                              flat_motor=None,
                              flat_position=None,
                              y_0=0,
                              y_1=None,
                              get_ellipse_points_kwargs=None,
                              frame_consumer=None):
    """
    align_rotation_axis(camera, rotation_motor, x_motor=None, z_motor=None,
    get_ellipse_points=find_needle_tips, num_frames=10, metric_eps=None,
    position_eps=0.1 * q.deg, max_iterations=5, initial_x_coeff=1 * q.dimensionless,
    initial_z_coeff=1 * q.dimensionless, shutter=None, flat_motor=None, flat_position=None,
    y_0=0, y_1=None, get_ellipse_points_kwargs=None, frame_consumer=None)

    Align rotation axis. *camera* is used to obtain frames, *rotation_motor* rotates the sample
    around the tomographic axis of rotation, *x_motor* turns the sample around x-axis, *z_motor*
    turns the sample around z-axis.

    *get_ellipse_points* is a function with one positional argument, a set of images. It computes
    the ellipse points from the sample positions as it rotates around the tomographic axis.  You can
    use e.g. :func:`concert.imageprocessing.find_needle_tips` and
    :func:`concert.imageprocessing.find_sphere_centers` to extract the ellipse points from needle
    tips or sphere centers. You can pass additional keyword arguments to the *get_ellipse_points*
    function in the *get_ellipse_points_kwargs* dictionary.

    *num_frames* defines how many frames are acquired and passed to the *measure*. *metric_eps* is
    the metric threshold for stopping the procedure. If not specified, it is calculated
    automatically to not exceed 0.5 pixels vertically. If *max_iterations* is reached the procedure
    stops as well. *initial_[x|z]_coeff* is the coefficient applied` to the motor motion for the
    first iteration. If we move the camera instead of the rotation stage, it is often necessary to
    acquire fresh flat fields. In order to make an up-to-date flat correction, specify *shutter* if
    you want fresh dark fields and specify *flat_motor* and *flat_position* to acquire flat fields.
    Crop acquired images to *y_0* and *y_1*. *frame_consumer* is a coroutine function which will be
    fed with all acquired frames.

    The procedure finishes when it finds the minimum angle between an ellipse extracted from the
    sample movement and respective axes or the found angle drops below *metric_eps*. The axis of
    rotation after the procedure is (0,1,0), which is the direction perpendicular to the beam
    direction and the lateral direction. *x_motor* and *z_motor* do not have to move exactly by the
    computed angles but their relative motion must be linear with respect to computed angles (e.g.
    if the motors operate with steps it is fine, also rotation direction does not need to be known).
    """
    if get_ellipse_points_kwargs is None:
        get_ellipse_points_kwargs = {}

    if not x_motor and not z_motor:
        raise ProcessError("At least one of the x, z motors must be given")

    async def make_step(i, motor, position_last, angle_last, angle_current,
                        initial_coeff, rotation_type):
        cur_pos = await motor.get_position()
        LOG.debug(
            "%s: i: %d, last angle: %s, angle: %s, last position: %s, position: %s",
            rotation_type, i, angle_last.to(q.deg), angle_current.to(q.deg),
            position_last.to(q.deg), cur_pos.to(q.deg))
        if i > 0:
            # Assume linear mapping between the computed angles and motor motion
            if angle_current == angle_last:
                coeff = 0 * q.dimensionless
            else:
                coeff = (cur_pos - position_last) / (angle_current -
                                                     angle_last)
        else:
            coeff = initial_coeff
        position_last = cur_pos
        angle_last = angle_current

        # Move relative, i.e. if *angle_current* should go to 0, then we need to move in the
        # other direction with *coeff* applied
        LOG.debug("%s coeff: %s, Next position: %s", rotation_type,
                  coeff.to_base_units(),
                  (cur_pos - coeff * angle_current).to(q.deg))
        await motor.move(-coeff * angle_current)

        return (position_last, angle_last)

    async def go_to_best_index(motor, history):
        positions, angles = list(zip(*history))
        best_index = np.argmin(
            np.abs([angle.to_base_units().magnitude for angle in angles]))
        LOG.debug("Best iteration: %d, position: %s, angle: %s", best_index,
                  positions[best_index].to(q.deg),
                  angles[best_index].to(q.deg))
        await motor.set_position(positions[best_index])

    async def extract_points(producer):
        return await get_ellipse_points(producer, **get_ellipse_points_kwargs)

    roll_history = []
    pitch_history = []
    center = None

    if z_motor:
        roll_angle_last = 0 * q.deg
        roll_position_last = await z_motor.get_position()
        roll_continue = True
    if x_motor:
        pitch_angle_last = 0 * q.deg
        pitch_position_last = await x_motor.get_position()
        pitch_continue = True

    frames_result = Result()
    for i in range(max_iterations):
        acq_consumers = [extract_points, frames_result]
        if frame_consumer:
            acq_consumers.append(frame_consumer)
        tips_start = time.perf_counter()
        frame_producer = acquire_frames_360(camera,
                                            rotation_motor,
                                            num_frames,
                                            shutter=shutter,
                                            flat_motor=flat_motor,
                                            flat_position=flat_position,
                                            y_0=y_0,
                                            y_1=y_1)

        coros = broadcast(frame_producer, *acq_consumers)
        try:
            tips = (await asyncio.gather(*coros))[1]
        except Exception as tips_exc:
            raise ProcessError('Error finding reference points') from tips_exc
        LOG.debug('Found %d points in %g s', len(tips),
                  time.perf_counter() - tips_start)
        roll_angle_current, pitch_angle_current, center = rotation_axis(tips)
        coros = []
        x_coro = z_coro = None
        if metric_eps is None:
            metric_eps = np.rad2deg(
                np.arctan(1 / frames_result.result.shape[1])) * q.deg
            LOG.debug('Automatically computed metric epsilon: %s', metric_eps)

        if z_motor and roll_continue:
            z_pos = await z_motor.get_position()
            roll_history.append((z_pos, roll_angle_current))
            if (np.abs(roll_angle_current) >= metric_eps
                    and (np.abs(roll_position_last - z_pos) >= position_eps
                         or i == 0)):
                z_coro = make_step(i, z_motor, roll_position_last,
                                   roll_angle_last, roll_angle_current,
                                   initial_z_coeff, 'roll')
                coros.append(z_coro)
            else:
                LOG.debug("Roll epsilon reached")
                roll_continue = False
        if x_motor and pitch_continue:
            x_pos = await x_motor.get_position()
            pitch_history.append((x_pos, pitch_angle_current))
            if (np.abs(pitch_angle_current) >= metric_eps
                    and (np.abs(pitch_position_last - x_pos) >= position_eps
                         or i == 0)):
                x_coro = make_step(i, x_motor, pitch_position_last,
                                   pitch_angle_last, pitch_angle_current,
                                   initial_x_coeff, 'pitch')
                coros.append(x_coro)
            else:
                LOG.debug("Pitch epsilon reached")
                pitch_continue = False

        if not coros:
            # If there are no coros the motors have reached positions at which the computed
            # angles are below threshold
            break

        step_results = await asyncio.gather(*coros)
        if x_coro:
            # Regardless from x_coro and z_coro to be present, x_coro is always added last, so pop
            # it first
            pitch_position_last, pitch_angle_last = step_results.pop()
        if z_coro:
            roll_position_last, roll_angle_last = step_results.pop()

    if i == max_iterations - 1:
        LOG.info('Maximum iterations reached')

    # Move to the best known position
    coros = []
    if z_motor:
        coros.append(go_to_best_index(z_motor, roll_history))
    if x_motor:
        coros.append(go_to_best_index(x_motor, pitch_history))
    await asyncio.gather(*coros)

    return (roll_history, pitch_history, center)