Calibration

Calibration Module

The calibration module of this library is used to generate the metadata necessary configure and run calibration workflows for different assets/devices.

The metadata follows the general logic of the library by implementing the three core classes:

A fourth class Calibration, specific to the Calibration module, is also implemented to keep track of the calibration metrics. This class was written to be aligned to the Calibration class in aind-data-schemas. An application example will be provided below.

While we use the base AindBehaviorSessionModel class to keep track of the session metadata, both AindBehaviorRigModel and AindBehaviorTaskLogicModel are expected to be sub-classed to specify the necessary dependencies of the calibration workflow.

Sub-classing Calibration

Sub-classing Calibration boils down to providing a subtype of the input and output fields. These fields are expected to be of a sub-type of ~pydantic.BaseModel and define the structure of the calibration outcome. Conceptually, input is the pre-process data that resulted from the calibration workflow (i.e. the weight of delivered water), whereas output is used to represent a post-processed version of the calibration outcome (e.g. a linear model that relates valve-opening times to water volume).

An example of a sub-class of Calibration is provided below:

from pydantic import BaseModel, Field
from typing import List, Literal
from aind_behavior_services.calibration import Calibration


class BarContainer(BaseModel):
   baz: string = Field(..., description="Baz value")
   bar: float = Field(..., description="Bar value")


class DeviceCalibrationInput(BaseModel):
   measured_foo: List[int] = Field(..., description="Measurements of Foo")
   bar_container: List[BarContainer] = Field(..., description="Bar container")


class DeviceCalibrationOutput(BaseModel):
   param_a = float = Field(default=1, description="Parameter A")
   param_b = float = Field(default=0, description="Parameter B")


class DeviceCalibration(Calibration):
   device_name: Literal["MyDevice"] = "MyDevice"
   description: Literal["Stores the calibration of a device"] = "Stores the calibration of a device"
   input: DeviceCalibrationInput = Field(..., title="Input of the calibration")
   output: DeviceCalibrationOutput = Field(..., title="Output of the calibration")

Sub-classing AindBehaviorRigModel

We adopt the following pattern to sub-class the AindBehaviorRigModel class:

from aind_behavior_services.rig import AindBehaviorRigModel, Device

RIG_VERSION = "1.0.0" # Use SemVer

class FooDevice(Device):
   calibration: DeviceCalibration = Field(..., title="Calibration of the device foo")


class CalibrationRig(AindBehaviorRigModel):
   version: Literal[RIG_VERSION] = RIG_VERSION
   device_foo: FooDevice = Field(..., title="Device Foo")
   device_bar: Device = Field(..., title="Device Bar")

For an example see aind_behavior_services.calibration.olfactometer.CalibrationRig.

Sub-classing AindBehaviorTaskLogicModel

The same way a AindBehaviorTaskLogicModel is used to define the settings to run a behavior task, it is also used to define the settings to run a calibration workflow. It will thus fallow an identical sub-classing pattern:

from aind_behavior_services.task_logic import AindBehaviorTaskLogicModel, TaskParameters

TASK_LOGIC_VERSION = "0.1.0"

class CalibrationParameters(TaskParameters):
   n_iterations: int = Field(default=10, description="Number of iterations to run the calibration")
   channels_to_calibrate: List[Literal[1,2,3]] = Field(default=[1], description="List of channels to calibrate")

class CalibrationLogic(AindBehaviorTaskLogicModel):
   name: Literal["CalibrationLogic"] = "CalibrationLogic
   version: Literal[TASK_LOGIC_VERSION] = TASK_LOGIC_VERSION
   task_parameters: CalibrationParameters = Field(default=CalibrationParameters(), title="Task parameters", validate_default=True)

For an example see aind_behavior_services.calibration.olfactometer.CalibrationLogic.