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, directory_prefix, log=None, log_file_name='experiment.log')

Experiment base class. An experiment can be run multiple times with logging output saved on disk. The log from every run() is saved in the current experiment directory given by directory_prefix.

acquisitions

A list of acquisitions this experiment is composed of

directory_prefix

Directory prefix is either a formattable string in which case the at each experiment run a new directory given by the prefix and the current iteration is created. If the directory_prefix is a simple string then the individual experiment runs are stored in its subdirectories starting with scan_ and suffixed by the run iteration.

log

A logger to which a file handler will be attached in order to store the log output in the current directory

log_file_name

Log file name used for storing logging information.

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().

directory

Current directory for running the experiment.

get_acquisition(name)

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

run()

Create current directory, attach logging output to file and run the acquire(). After the run is complete the logging is cleaned up automatically. This method should not be overriden.

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, directory_prefix, log=None, log_file_name='experiment.log', writer=<function write_tiff at 0x5db9230>)

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).