diff --git a/examples/rig.py b/examples/rig.py index 4216c5d..67b3237 100644 --- a/examples/rig.py +++ b/examples/rig.py @@ -2,7 +2,6 @@ from aind_behavior_services.rig import cameras from aind_behavior_services.rig.aind_manipulator import ( - AindManipulator, AindManipulatorCalibration, Axis, AxisConfiguration, @@ -15,7 +14,12 @@ ) from aind_behavior_services.rig.water_valve import Measurement, calibrate_water_valves -from aind_behavior_dynamic_foraging.rig import AindDynamicForagingRig, DynamicForagingSoundCard, RigCalibration +from aind_behavior_dynamic_foraging.rig import ( + AindDynamicForagingRig, + DynamicForagingAindManipulator, + DynamicForagingSoundCard, + RigCalibration, +) manipulator_calibration = AindManipulatorCalibration( full_step_to_mm=(ManipulatorPosition(x=0.010, y1=0.010, y2=0.010, z=0.010)), @@ -60,7 +64,7 @@ harp_lickometer_left=None, harp_lickometer_right=None, harp_clock_generator=HarpWhiteRabbit(port_name="COM11"), - manipulator=AindManipulator(port_name="COM9", calibration=manipulator_calibration), + manipulator=DynamicForagingAindManipulator(port_name="COM9", calibration=manipulator_calibration), calibration=RigCalibration( water_valve_left=water_valve_calibration, water_valve_right=water_valve_calibration, diff --git a/schema/aind_behavior_dynamic_foraging.json b/schema/aind_behavior_dynamic_foraging.json index 2107795..4b61a1b 100644 --- a/schema/aind_behavior_dynamic_foraging.json +++ b/schema/aind_behavior_dynamic_foraging.json @@ -107,7 +107,7 @@ ] }, "manipulator": { - "$ref": "#/$defs/AindManipulator", + "$ref": "#/$defs/DynamicForagingAindManipulator", "description": "Manipulator" }, "calibration": { @@ -218,114 +218,6 @@ "title": "AindDynamicForagingTaskParameters", "type": "object" }, - "AindManipulator": { - "description": "AindManipulator device definition", - "properties": { - "device_type": { - "const": "StepperDriver", - "default": "StepperDriver", - "title": "Device Type", - "type": "string" - }, - "calibration": { - "$ref": "#/$defs/AindManipulatorCalibration", - "default": { - "description": "AindManipulator calibration and settings", - "full_step_to_mm": { - "x": 0.01, - "y1": 0.01, - "y2": 0.01, - "z": 0.01 - }, - "axis_configuration": [ - { - "axis": 2, - "max_limit": 25.0, - "maximum_step_interval": 2000, - "microstep_resolution": 0, - "min_limit": -0.01, - "motor_operation_mode": 0, - "step_acceleration_interval": 100, - "step_interval": 100 - }, - { - "axis": 3, - "max_limit": 25.0, - "maximum_step_interval": 2000, - "microstep_resolution": 0, - "min_limit": -0.01, - "motor_operation_mode": 0, - "step_acceleration_interval": 100, - "step_interval": 100 - }, - { - "axis": 1, - "max_limit": 25.0, - "maximum_step_interval": 2000, - "microstep_resolution": 0, - "min_limit": -0.01, - "motor_operation_mode": 0, - "step_acceleration_interval": 100, - "step_interval": 100 - }, - { - "axis": 4, - "max_limit": 25.0, - "maximum_step_interval": 2000, - "microstep_resolution": 0, - "min_limit": -0.01, - "motor_operation_mode": 0, - "step_acceleration_interval": 100, - "step_interval": 100 - } - ], - "homing_order": [ - 2, - 3, - 1, - 4 - ], - "initial_position": { - "x": 0.0, - "y1": 0.0, - "y2": 0.0, - "z": 0.0 - } - }, - "description": "Calibration for the device." - }, - "who_am_i": { - "const": 1130, - "default": 1130, - "title": "Who Am I", - "type": "integer" - }, - "serial_number": { - "default": null, - "description": "Device serial number", - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Serial Number" - }, - "port_name": { - "description": "Device port name", - "title": "Port Name", - "type": "string" - } - }, - "required": [ - "port_name" - ], - "title": "AindManipulator", - "type": "object", - "x-sgen-typename": "AllenNeuralDynamics.AindManipulator.AindManipulator" - }, "AindManipulatorCalibration": { "description": "AindManipulator calibration class", "properties": { @@ -1470,6 +1362,123 @@ "title": "Distribution", "x-sgen-typename": "AllenNeuralDynamics.AindBehaviorServices.Distributions.Distribution" }, + "DynamicForagingAindManipulator": { + "description": "A calibrated manipulator for the dynamic foraging rig. This is a subclass of the AindManipulator that includes an offset of the subject position relative to the initial position.", + "properties": { + "device_type": { + "const": "StepperDriver", + "default": "StepperDriver", + "title": "Device Type", + "type": "string" + }, + "calibration": { + "$ref": "#/$defs/AindManipulatorCalibration", + "default": { + "description": "AindManipulator calibration and settings", + "full_step_to_mm": { + "x": 0.01, + "y1": 0.01, + "y2": 0.01, + "z": 0.01 + }, + "axis_configuration": [ + { + "axis": 2, + "max_limit": 25.0, + "maximum_step_interval": 2000, + "microstep_resolution": 0, + "min_limit": -0.01, + "motor_operation_mode": 0, + "step_acceleration_interval": 100, + "step_interval": 100 + }, + { + "axis": 3, + "max_limit": 25.0, + "maximum_step_interval": 2000, + "microstep_resolution": 0, + "min_limit": -0.01, + "motor_operation_mode": 0, + "step_acceleration_interval": 100, + "step_interval": 100 + }, + { + "axis": 1, + "max_limit": 25.0, + "maximum_step_interval": 2000, + "microstep_resolution": 0, + "min_limit": -0.01, + "motor_operation_mode": 0, + "step_acceleration_interval": 100, + "step_interval": 100 + }, + { + "axis": 4, + "max_limit": 25.0, + "maximum_step_interval": 2000, + "microstep_resolution": 0, + "min_limit": -0.01, + "motor_operation_mode": 0, + "step_acceleration_interval": 100, + "step_interval": 100 + } + ], + "homing_order": [ + 2, + 3, + 1, + 4 + ], + "initial_position": { + "x": 0.0, + "y1": 0.0, + "y2": 0.0, + "z": 0.0 + } + }, + "description": "Calibration for the device." + }, + "who_am_i": { + "const": 1130, + "default": 1130, + "title": "Who Am I", + "type": "integer" + }, + "serial_number": { + "default": null, + "description": "Device serial number", + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Serial Number" + }, + "port_name": { + "description": "Device port name", + "title": "Port Name", + "type": "string" + }, + "subject_offset": { + "$ref": "#/$defs/ManipulatorPosition", + "default": { + "x": 0.0, + "y1": 0.0, + "y2": 0.0, + "z": 0.0 + }, + "description": "Offset of the subject position relative to the `initial_position` parameter" + } + }, + "required": [ + "port_name" + ], + "title": "DynamicForagingAindManipulator", + "type": "object" + }, "DynamicForagingSoundCard": { "description": "A calibrated sound card for the dynamic foraging rig. This is a subclass of the HarpSoundCard that includes the sound card calibration.", "properties": { diff --git a/src/Extensions/AindBehaviorDynamicForaging.Generated.cs b/src/Extensions/AindBehaviorDynamicForaging.Generated.cs index 0479d06..9810505 100644 --- a/src/Extensions/AindBehaviorDynamicForaging.Generated.cs +++ b/src/Extensions/AindBehaviorDynamicForaging.Generated.cs @@ -43,7 +43,7 @@ public partial class AindDynamicForagingRig private HarpEnvironmentSensor _harpEnvironmentSensor; - private AllenNeuralDynamics.AindManipulator.AindManipulator _manipulator; + private DynamicForagingAindManipulator _manipulator; private RigCalibration _calibration; @@ -55,7 +55,7 @@ public AindDynamicForagingRig() _harpBehavior = new HarpBehavior(); _harpClockGenerator = new HarpWhiteRabbit(); _harpSoundCard = new DynamicForagingSoundCard(); - _manipulator = new AllenNeuralDynamics.AindManipulator.AindManipulator(); + _manipulator = new DynamicForagingAindManipulator(); _calibration = new RigCalibration(); } @@ -324,7 +324,7 @@ public HarpEnvironmentSensor HarpEnvironmentSensor [System.Xml.Serialization.XmlIgnoreAttribute()] [Newtonsoft.Json.JsonPropertyAttribute("manipulator", Required=Newtonsoft.Json.Required.Always)] [System.ComponentModel.DescriptionAttribute("Manipulator")] - public AllenNeuralDynamics.AindManipulator.AindManipulator Manipulator + public DynamicForagingAindManipulator Manipulator { get { @@ -2661,6 +2661,180 @@ protected override bool PrintMembers(System.Text.StringBuilder stringBuilder) } + /// + /// A calibrated manipulator for the dynamic foraging rig. This is a subclass of the AindManipulator that includes an offset of the subject position relative to the initial position. + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.9.0.0 (Newtonsoft.Json v13.0.0.0)")] + [System.ComponentModel.DescriptionAttribute("A calibrated manipulator for the dynamic foraging rig. This is a subclass of the " + + "AindManipulator that includes an offset of the subject position relative to the " + + "initial position.")] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] + [Bonsai.CombinatorAttribute(MethodName="Generate")] + public partial class DynamicForagingAindManipulator + { + + private string _deviceType; + + private AllenNeuralDynamics.AindManipulator.AindManipulatorCalibration _calibration; + + private int _whoAmI; + + private string _serialNumber; + + private string _portName; + + private AllenNeuralDynamics.AindManipulator.ManipulatorPosition _subjectOffset; + + public DynamicForagingAindManipulator() + { + _deviceType = "StepperDriver"; + _calibration = new AllenNeuralDynamics.AindManipulator.AindManipulatorCalibration(); + _whoAmI = 1130; + _subjectOffset = new AllenNeuralDynamics.AindManipulator.ManipulatorPosition(); + } + + protected DynamicForagingAindManipulator(DynamicForagingAindManipulator other) + { + _deviceType = other._deviceType; + _calibration = other._calibration; + _whoAmI = other._whoAmI; + _serialNumber = other._serialNumber; + _portName = other._portName; + _subjectOffset = other._subjectOffset; + } + + [Newtonsoft.Json.JsonPropertyAttribute("device_type")] + public string DeviceType + { + get + { + return _deviceType; + } + set + { + _deviceType = value; + } + } + + /// + /// Calibration for the device. + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + [Newtonsoft.Json.JsonPropertyAttribute("calibration")] + [System.ComponentModel.DescriptionAttribute("Calibration for the device.")] + public AllenNeuralDynamics.AindManipulator.AindManipulatorCalibration Calibration + { + get + { + return _calibration; + } + set + { + _calibration = value; + } + } + + [Newtonsoft.Json.JsonPropertyAttribute("who_am_i")] + public int WhoAmI + { + get + { + return _whoAmI; + } + set + { + _whoAmI = value; + } + } + + /// + /// Device serial number + /// + [Newtonsoft.Json.JsonPropertyAttribute("serial_number")] + [System.ComponentModel.DescriptionAttribute("Device serial number")] + public string SerialNumber + { + get + { + return _serialNumber; + } + set + { + _serialNumber = value; + } + } + + /// + /// Device port name + /// + [Newtonsoft.Json.JsonPropertyAttribute("port_name", Required=Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DescriptionAttribute("Device port name")] + public string PortName + { + get + { + return _portName; + } + set + { + _portName = value; + } + } + + /// + /// Offset of the subject position relative to the `initial_position` parameter + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + [Newtonsoft.Json.JsonPropertyAttribute("subject_offset")] + [System.ComponentModel.DescriptionAttribute("Offset of the subject position relative to the `initial_position` parameter")] + public AllenNeuralDynamics.AindManipulator.ManipulatorPosition SubjectOffset + { + get + { + return _subjectOffset; + } + set + { + _subjectOffset = value; + } + } + + public System.IObservable Generate() + { + return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return(new DynamicForagingAindManipulator(this))); + } + + public System.IObservable Generate(System.IObservable source) + { + return System.Reactive.Linq.Observable.Select(source, _ => new DynamicForagingAindManipulator(this)); + } + + protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) + { + stringBuilder.Append("DeviceType = " + _deviceType + ", "); + stringBuilder.Append("Calibration = " + _calibration + ", "); + stringBuilder.Append("WhoAmI = " + _whoAmI + ", "); + stringBuilder.Append("SerialNumber = " + _serialNumber + ", "); + stringBuilder.Append("PortName = " + _portName + ", "); + stringBuilder.Append("SubjectOffset = " + _subjectOffset); + return true; + } + + public override string ToString() + { + System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder(); + stringBuilder.Append(GetType().Name); + stringBuilder.Append(" { "); + if (PrintMembers(stringBuilder)) + { + stringBuilder.Append(" "); + } + stringBuilder.Append("}"); + return stringBuilder.ToString(); + } + } + + /// /// A calibrated sound card for the dynamic foraging rig. This is a subclass of the HarpSoundCard that includes the sound card calibration. /// @@ -7634,6 +7808,11 @@ public System.IObservable Process(System.IObservable(source); } + public System.IObservable Process(System.IObservable source) + { + return Process(source); + } + public System.IObservable Process(System.IObservable source) { return Process(source); @@ -7804,6 +7983,7 @@ public System.IObservable Process(System.IObservable source) [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] + [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] diff --git a/src/Extensions/Hardware.bonsai b/src/Extensions/Hardware.bonsai index 83ea506..c1730e2 100644 --- a/src/Extensions/Hardware.bonsai +++ b/src/Extensions/Hardware.bonsai @@ -4,11 +4,12 @@ xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" xmlns:p1="clr-namespace:AllenNeuralDynamics.WhiteRabbit;assembly=AllenNeuralDynamics.WhiteRabbit" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" + xmlns:p2="clr-namespace:;assembly=Extensions" xmlns:beh="clr-namespace:Harp.Behavior;assembly=Harp.Behavior" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:cv="clr-namespace:Bonsai.Vision;assembly=Bonsai.Vision" - xmlns:p2="clr-namespace:OpenCV.Net;assembly=OpenCV.Net" - xmlns:p3="clr-namespace:AllenNeuralDynamics.Core;assembly=AllenNeuralDynamics.Core" + xmlns:p3="clr-namespace:OpenCV.Net;assembly=OpenCV.Net" + xmlns:p4="clr-namespace:AllenNeuralDynamics.Core;assembly=AllenNeuralDynamics.Core" xmlns:spk="clr-namespace:Bonsai.Spinnaker;assembly=Bonsai.Spinnaker" xmlns="https://bonsai-rx.org/2018/workflow"> @@ -132,7 +133,10 @@ RigSchema - Manipulator.Calibration + Manipulator + + + RigSchema @@ -228,24 +232,25 @@ - - + + - - + + - - + + - - + + - - + + - + + @@ -399,7 +404,7 @@ - + RegionOfInterest 0 @@ -414,21 +419,21 @@ - + Default - 19000 - 0 - 1 - - Mono8 - - 0 - 0 - 0 - 0 - - + 19000 + 0 + 1 + + Mono8 + + 0 + 0 + 0 + 0 + + diff --git a/src/Extensions/Logging.bonsai b/src/Extensions/Logging.bonsai index 26f8a32..fffea67 100644 --- a/src/Extensions/Logging.bonsai +++ b/src/Extensions/Logging.bonsai @@ -775,22 +775,6 @@ AdditionalSoftwareEvents - - ManipulatorPosition - - - - 1 - - - - - InitialManipulatorPosition - - - - SoftwareEvent - RngSeedValue @@ -822,12 +806,9 @@ - + - - - diff --git a/src/Extensions/OffsetInitialPositionWithSubject.cs b/src/Extensions/OffsetInitialPositionWithSubject.cs new file mode 100644 index 0000000..e79f0f0 --- /dev/null +++ b/src/Extensions/OffsetInitialPositionWithSubject.cs @@ -0,0 +1,30 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using AindDynamicForagingDataSchema; +using AllenNeuralDynamics.AindManipulator; +using Newtonsoft.Json; + +[Combinator] +[Description("Offsets the initial position of the manipulator with the subject's calibration.")] +[WorkflowElementCategory(ElementCategory.Transform)] +public class OffsetInitialPositionWithSubject +{ + public IObservable Process(IObservable source) + { + return source.Select(value => + { + // Make a quick deep-copy just in case. + var calibration = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value.Calibration)); + if (calibration == null) + { + throw new InvalidOperationException("Manipulator does not contain a valid initial position."); + } + calibration.InitialPosition = calibration.InitialPosition + value.SubjectOffset; + return calibration; + } + ); + } +} diff --git a/src/Extensions/OperationControl.bonsai b/src/Extensions/OperationControl.bonsai index 9735b8a..7931b7d 100644 --- a/src/Extensions/OperationControl.bonsai +++ b/src/Extensions/OperationControl.bonsai @@ -632,7 +632,7 @@ - Manipulator + ManipulatorTask @@ -882,6 +882,117 @@ + + ManipulatorInitialization + + + + + 0 + + + + + PT0.5S + + + + + This is a publish subject! + + + + HomeMotors + + + WaitForHomed + + + + IsHomed + + + + PT0.2S + + + + + + + Source1 + + + + + + + + + + + + + + PT10S + + + + + 1 + + + + + + + + + + + + + + + + GoToInit + + + + Source1 + + + + GoToInitialPosition + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + CameraControl @@ -1516,8 +1627,8 @@ - - + + \ No newline at end of file diff --git a/src/aind_behavior_dynamic_foraging/rig.py b/src/aind_behavior_dynamic_foraging/rig.py index c1a94f0..8608a95 100644 --- a/src/aind_behavior_dynamic_foraging/rig.py +++ b/src/aind_behavior_dynamic_foraging/rig.py @@ -62,6 +62,16 @@ class DynamicForagingSoundCard(harp.HarpSoundCard): ) +class DynamicForagingAindManipulator(aind_manipulator.AindManipulator): + """A calibrated manipulator for the dynamic foraging rig. This is a subclass of the AindManipulator that includes an offset of the subject position relative to the initial position.""" + + subject_offset: aind_manipulator.ManipulatorPosition = Field( + aind_manipulator.ManipulatorPosition(x=0, y1=0, y2=0, z=0), + description="Offset of the subject position relative to the `initial_position` parameter", + validate_default=True, + ) + + class AindDynamicForagingRig(rig.Rig): version: Literal[__semver__] = __semver__ triggered_camera_controller: cameras.CameraController[cameras.SpinnakerCamera] = Field( @@ -79,5 +89,5 @@ class AindDynamicForagingRig(rig.Rig): harp_environment_sensor: Optional[harp.HarpEnvironmentSensor] = Field( default=None, description="Harp environment sensor" ) - manipulator: aind_manipulator.AindManipulator = Field(description="Manipulator") + manipulator: DynamicForagingAindManipulator = Field(description="Manipulator") calibration: RigCalibration = Field(description="Calibration models")