def connect(self): """Connect producer with consumers.""" started = [] for not_started in self.consumers: started.append(not_started()) inject(self.producer(), broadcast(*started))
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))
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)
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)
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)
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)
def test_broadcast(self): self.producer(broadcast(self.consume(), self.consume_2())) self.assertEqual(self.data, 4) self.assertEqual(self.data_2, 4)
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)