# Experiments¶

Experiments connect data acquisition and processing. They can be run multiple times by the base.Experiment.run(), they take care of proper file structure and logging output.

## Acquisition¶

Experiments consist of Acquisition objects which encapsulate data generator and consumers for a particular experiment part (dark fields, radiographs, ...). This way the experiments can be broken up into smaller logical pieces. A single acquisition object needs to be reproducible in order to repeat an experiment more times, thus we specify its generator and consumers as callables which return the actual generator or consumer. We need to do this because generators cannot be “restarted”. An example of an acquisition could look like this:

from concert.coroutines.base import coroutine
from concert.experiments import Acquisition

# This is a real generator, num_items is provided somewhere in our session
def produce():
for i in range(num_items):
yield i

# A simple data forwarder filter, next_consumer has to be already defined
@coroutine
def consumer():
while True:
item = yield
next_consumer.send(item)

acquisition = Acquisition('foo', produce, consumer_callers=[consumer])
# Now we can run the acquisition
acquisition()

class concert.experiments.base.Acquisition(name, generator_caller, consumer_callers=None)

An acquisition object connects data generator to consumers.

generator_caller

a callable which returns a generator once called

consumer_callers

a list of callables which return a coroutine once started

## Base¶

Base base.Experiment makes sure a directory for each run is created and logger output goes to that directory.

class concert.experiments.base.Experiment(acquisitions, walker, name_fmt='scan_{:>04}')

Experiment base class. An experiment can be run multiple times with the output data and log stored on disk. You can prepare every run by prepare() and finsh the run by finish(). These methods do nothing by default. They can be useful e.g. if you need to reinitialize some experiment parts or want to attach some logging output.

acquisitions

A list of acquisitions this experiment is composed of

walker

A concert.storage.Walker stores experimental data and logging output

name_fmt

Since experiment can be run multiple times each iteration will have a separate entry on the disk. The entry consists of a name and a number of the current iteration, so the parameter is a formattable string.

acquire()

Acquire data by running the acquisitions. This is the method which implements the data acquisition and should be overriden if more functionality is required, unlike run().

acquisitions

Acquisitions is a read-only attribute which has to be manipulated by explicit methods provided by this class.

Add acquisition to the acquisition list and make it accessible as attribute, e.g.:

frames = Acquisition(...) experiment.add(frames) # This is possible experiment.frames

finish()

Gets executed after every experiment run.

get_acquisition(name)

Get acquisition by its name. In case there are more like it, the first one is returned.

prepare()

Gets executed before every experiment run.

remove(acquisition)

Remove acquisition from experiment.

run()

Compute the next iteration and run the acquire().

swap(first, second)

Swap acquisition first with second. If there are more occurences of either of them then the ones which are found first in the acquisitions list are swapped.

## Imaging¶

Imaging experiments all subclass imaging.Experiment, which makes sure all the acquired frames are written to disk.

class concert.experiments.imaging.Experiment(acquisitions, walker, name_fmt='scan_{:>04}')

Imaging experiment stores images acquired in acquisitions on disk automatically.

acquire()

Run the experiment. Add writers to acquisitions dynamically.

A basic frame acquisition generator which triggers the camera itself is provided by frames()

concert.experiments.imaging.frames(num_frames, camera, callback=None)

A generator which takes num_frames using camera. callback is called after every taken frame.

There are tomography helper functions which make it easier to define the proper settings for conducting a tomographic experiment.

concert.experiments.imaging.tomo_angular_step(frame_width)

Get the angular step required for tomography so that every pixel of the frame rotates no more than one pixel per rotation step. frame_width is frame size in the direction perpendicular to the axis of rotation.

concert.experiments.imaging.tomo_projections_number(frame_width)

Get the minimum number of projections required by a tomographic scan in order to provide enough data points for every distance from the axis of rotation. The minimum angular step is considered to be needed smaller than one pixel in the direction perpendicular to the axis of rotation. The number of pixels in this direction is given by frame_width.

concert.experiments.imaging.tomo_max_speed(frame_width, frame_rate)

Get the maximum rotation speed which introduces motion blur less than one pixel. frame_width is the width of the frame in the direction perpendicular to the rotation and frame_rate defines the time required for recording one frame.

_Note:_ frame rate is required instead of exposure time because the exposure time is usually shorter due to the camera chip readout time. We need to make sure that by the next exposure the sample hasn’t moved more than one pixel from the previous frame, thus we need to take into account the whole frame taking procedure (exposure + readout).

## Control¶

Experiment automation based on on-line data analysis.

class concert.experiments.control.ClosedLoop

An abstract feedback loop which acquires data, analyzes it on-line and provides feedback to the experiment. The data acquisition procedure is done iteratively until the result of some metric converges to a satisfactory value. Schematically, the class is doing the following in an iterative way:

initialize -> measure -> compare -> OK -> success
^            |
|           NOK
|            |
-- control <--

compare()

Return True if the metric is satisfied, False otherwise. This is the decision making process.

control()

React on the result of a measurement.

initialize()

Bring the experimental setup to some defined initial (reference) state.

measure()

Conduct a measurement from data acquisition to analysis.

run(self, max_iterations=10)

Run the loop until the metric is satisfied, if we don’t converge in max_iterations then the run is considered unsuccessful and False is returned, otherwise True.

class concert.experiments.control.DummyLoop

A dummy optimization loop.