From bc94695caf5d7c9681fd9b1c3c3e01e7adb90915 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Wed, 27 May 2026 09:45:48 -0700 Subject: [PATCH 1/3] adds trial metadata field --- schema/aind_behavior_dynamic_foraging.json | 66 +++++++-- .../AindBehaviorDynamicForaging.Generated.cs | 139 ++++++++++++++++-- .../block_based_trial_generator.py | 29 ++-- .../task_logic/trial_models.py | 22 ++- .../test_block_based_trial_generator.py | 20 +-- .../test_coupled_trial_generator.py | 3 +- .../test_uncoupled_trial_generator.py | 3 +- .../test_warmup_trial_generator.py | 3 +- 8 files changed, 231 insertions(+), 54 deletions(-) diff --git a/schema/aind_behavior_dynamic_foraging.json b/schema/aind_behavior_dynamic_foraging.json index 1d73d69..ae0736c 100644 --- a/schema/aind_behavior_dynamic_foraging.json +++ b/schema/aind_behavior_dynamic_foraging.json @@ -3277,16 +3277,14 @@ "title": "Lickspout Offset Delta", "type": "number" }, - "extra_metadata": { - "default": null, - "description": "Additional metadata to include with the trial. This field will NOT be used or validated by the task engine.", - "oneOf": [ - {}, - { - "type": "null" - } - ], - "title": "Extra Metadata" + "trial_metadata": { + "$ref": "#/$defs/TrialMetadata", + "default": { + "block_p_reward_left": null, + "block_p_reward_right": null, + "extra": null + }, + "description": "Metadata fields that will not be used by task engine such as block information." } }, "title": "Trial", @@ -3354,6 +3352,54 @@ } ] }, + "TrialMetadata": { + "description": "Metadata for trial. These fields will NOT be used by the task engine.", + "properties": { + "block_p_reward_left": { + "default": null, + "description": "The block probability of reward on the left side if response is made.", + "oneOf": [ + { + "maximum": 1, + "minimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Block P Reward Left" + }, + "block_p_reward_right": { + "default": null, + "description": "The block probability of reward on the right side if response is made.", + "oneOf": [ + { + "maximum": 1, + "minimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Block P Reward Right" + }, + "extra": { + "default": null, + "description": "Additional metadata to include with the trial. This field will NOT be used or validated by the task engine.", + "oneOf": [ + {}, + { + "type": "null" + } + ], + "title": "Extra" + } + }, + "title": "TrialMetadata", + "type": "object" + }, "TrialOutcome": { "description": "Represents the outcome of a single trial.", "properties": { diff --git a/src/Extensions/AindBehaviorDynamicForaging.Generated.cs b/src/Extensions/AindBehaviorDynamicForaging.Generated.cs index 7fdde9f..90cd49c 100644 --- a/src/Extensions/AindBehaviorDynamicForaging.Generated.cs +++ b/src/Extensions/AindBehaviorDynamicForaging.Generated.cs @@ -5639,7 +5639,7 @@ public partial class Trial private double _lickspoutOffsetDelta; - private object _extraMetadata; + private TrialMetadata _trialMetadata; public Trial() { @@ -5651,6 +5651,7 @@ public Trial() _quiescencePeriodDuration = 0.5D; _interTrialIntervalDuration = 5D; _lickspoutOffsetDelta = 0D; + _trialMetadata = new TrialMetadata(); } protected Trial(Trial other) @@ -5666,7 +5667,7 @@ protected Trial(Trial other) _interTrialIntervalDuration = other._interTrialIntervalDuration; _isAutoResponseRight = other._isAutoResponseRight; _lickspoutOffsetDelta = other._lickspoutOffsetDelta; - _extraMetadata = other._extraMetadata; + _trialMetadata = other._trialMetadata; } /// @@ -5863,21 +5864,20 @@ public double LickspoutOffsetDelta } /// - /// Additional metadata to include with the trial. This field will NOT be used or validated by the task engine. + /// Metadata fields that will not be used by task engine such as block information. /// [System.Xml.Serialization.XmlIgnoreAttribute()] - [Newtonsoft.Json.JsonPropertyAttribute("extra_metadata")] - [System.ComponentModel.DescriptionAttribute("Additional metadata to include with the trial. This field will NOT be used or val" + - "idated by the task engine.")] - public object ExtraMetadata + [Newtonsoft.Json.JsonPropertyAttribute("trial_metadata")] + [System.ComponentModel.DescriptionAttribute("Metadata fields that will not be used by task engine such as block information.")] + public TrialMetadata TrialMetadata { get { - return _extraMetadata; + return _trialMetadata; } set { - _extraMetadata = value; + _trialMetadata = value; } } @@ -5904,7 +5904,7 @@ protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) stringBuilder.Append("InterTrialIntervalDuration = " + _interTrialIntervalDuration + ", "); stringBuilder.Append("IsAutoResponseRight = " + _isAutoResponseRight + ", "); stringBuilder.Append("LickspoutOffsetDelta = " + _lickspoutOffsetDelta + ", "); - stringBuilder.Append("ExtraMetadata = " + _extraMetadata); + stringBuilder.Append("TrialMetadata = " + _trialMetadata); return true; } @@ -6035,6 +6035,119 @@ public override string ToString() } + /// + /// Metadata for trial. These fields will NOT be used by the task engine. + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.9.0.0 (Newtonsoft.Json v13.0.0.0)")] + [System.ComponentModel.DescriptionAttribute("Metadata for trial. These fields will NOT be used by the task engine.")] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] + [Bonsai.CombinatorAttribute(MethodName="Generate")] + public partial class TrialMetadata + { + + private double? _blockPRewardLeft; + + private double? _blockPRewardRight; + + private object _extra; + + public TrialMetadata() + { + } + + protected TrialMetadata(TrialMetadata other) + { + _blockPRewardLeft = other._blockPRewardLeft; + _blockPRewardRight = other._blockPRewardRight; + _extra = other._extra; + } + + /// + /// The block probability of reward on the left side if response is made. + /// + [Newtonsoft.Json.JsonPropertyAttribute("block_p_reward_left")] + [System.ComponentModel.DescriptionAttribute("The block probability of reward on the left side if response is made.")] + public double? BlockPRewardLeft + { + get + { + return _blockPRewardLeft; + } + set + { + _blockPRewardLeft = value; + } + } + + /// + /// The block probability of reward on the right side if response is made. + /// + [Newtonsoft.Json.JsonPropertyAttribute("block_p_reward_right")] + [System.ComponentModel.DescriptionAttribute("The block probability of reward on the right side if response is made.")] + public double? BlockPRewardRight + { + get + { + return _blockPRewardRight; + } + set + { + _blockPRewardRight = value; + } + } + + /// + /// Additional metadata to include with the trial. This field will NOT be used or validated by the task engine. + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + [Newtonsoft.Json.JsonPropertyAttribute("extra")] + [System.ComponentModel.DescriptionAttribute("Additional metadata to include with the trial. This field will NOT be used or val" + + "idated by the task engine.")] + public object Extra + { + get + { + return _extra; + } + set + { + _extra = value; + } + } + + public System.IObservable Generate() + { + return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return(new TrialMetadata(this))); + } + + public System.IObservable Generate(System.IObservable source) + { + return System.Reactive.Linq.Observable.Select(source, _ => new TrialMetadata(this)); + } + + protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) + { + stringBuilder.Append("BlockPRewardLeft = " + _blockPRewardLeft + ", "); + stringBuilder.Append("BlockPRewardRight = " + _blockPRewardRight + ", "); + stringBuilder.Append("Extra = " + _extra); + 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(); + } + } + + /// /// Represents the outcome of a single trial. /// @@ -7908,6 +8021,11 @@ public System.IObservable Process(System.IObservable return Process(source); } + public System.IObservable Process(System.IObservable source) + { + return Process(source); + } + public System.IObservable Process(System.IObservable source) { return Process(source); @@ -8003,6 +8121,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/aind_behavior_dynamic_foraging/task_logic/trial_generators/block_based_trial_generator.py b/src/aind_behavior_dynamic_foraging/task_logic/trial_generators/block_based_trial_generator.py index 3729188..253e2a1 100644 --- a/src/aind_behavior_dynamic_foraging/task_logic/trial_generators/block_based_trial_generator.py +++ b/src/aind_behavior_dynamic_foraging/task_logic/trial_generators/block_based_trial_generator.py @@ -12,12 +12,18 @@ from aind_behavior_services.task.distributions_utils import draw_sample from pydantic import BaseModel, Field -from ..trial_models import Trial +from ..trial_models import Trial, TrialMetadata from ._base import BaseTrialGeneratorSpecModel, ITrialGenerator, TrialOutcome logger = logging.getLogger(__name__) +class BlockBasedTrialMetadata(BaseModel): + """Metadata for block based trial. These fields will NOT be used by the task engine.""" + + is_autowater: bool = Field(default=False, description="Flag indicating if autowater is given for trial.") + + class AutoWaterParameters(BaseModel): min_ignored_trials: int = Field( default=3, ge=0, description="Minimum consecutive ignored trials before auto water is triggered." @@ -171,18 +177,23 @@ def next(self) -> Trial | None: logger.debug("Right baited: %s" % self.is_right_baited) # determine autowater - is_right_autowater = None - if self._are_autowater_conditions_met(): - is_right_autowater = True if self.block.p_right_reward > self.block.p_left_reward else False + is_auto_response_right = None + if is_autowater := self._are_autowater_conditions_met(): + is_auto_response_right = True if self.block.p_right_reward > self.block.p_left_reward else False return Trial( - p_reward_left=1 if (self.is_left_baited and self.spec.is_baiting) else self.block.p_left_reward, - p_reward_right=1 if (self.is_right_baited and self.spec.is_baiting) else self.block.p_right_reward, + p_reward_left=1 if (self.is_left_baited or is_auto_response_right is False) else self.block.p_left_reward, + p_reward_right=1 if (self.is_right_baited or is_auto_response_right) else self.block.p_right_reward, reward_consumption_duration=self.spec.reward_consumption_duration, response_deadline_duration=self.spec.response_duration, quiescence_period_duration=quiescent, inter_trial_interval_duration=iti, - is_auto_response_right=is_right_autowater, + is_auto_response_right=is_auto_response_right, + trial_metadata=TrialMetadata( + block_p_reward_left=self.block.p_left_reward, + block_p_reward_right=self.block.p_right_reward, + extra=BlockBasedTrialMetadata(is_autowater=is_autowater), + ), ) def _are_autowater_conditions_met(self) -> bool: @@ -199,11 +210,11 @@ def _are_autowater_conditions_met(self) -> bool: min_unreward = self.spec.autowater_parameters.min_unrewarded_trials is_ignored = [choice is None for choice in self.is_right_choice_history] - if all(is_ignored[-min_ignore:]): + if len(is_ignored) > min_ignore and all(is_ignored[-min_ignore:]): return True is_unrewarded = [not reward for reward in self.reward_history] - if all(is_unrewarded[-min_unreward:]): + if len(is_unrewarded) > min_unreward and all(is_unrewarded[-min_unreward:]): return True return False diff --git a/src/aind_behavior_dynamic_foraging/task_logic/trial_models.py b/src/aind_behavior_dynamic_foraging/task_logic/trial_models.py index adb8d07..9a75f17 100644 --- a/src/aind_behavior_dynamic_foraging/task_logic/trial_models.py +++ b/src/aind_behavior_dynamic_foraging/task_logic/trial_models.py @@ -43,6 +43,21 @@ class QuickRetractSettings(BaseModel): ) +class TrialMetadata(BaseModel): + """Metadata for trial. These fields will NOT be used by the task engine.""" + + block_p_reward_left: Optional[float] = Field( + default=None, ge=0, le=1, description="The block probability of reward on the left side if response is made." + ) + block_p_reward_right: Optional[float] = Field( + default=None, ge=0, le=1, description="The block probability of reward on the right side if response is made." + ) + extra: Optional[SerializeAsAny[Any]] = Field( + default=None, + description="Additional metadata to include with the trial. This field will NOT be used or validated by the task engine.", + ) + + class Trial(BaseModel): """Represents a single trial that can be instantiated by the Bonsai state machine.""" @@ -84,9 +99,10 @@ class Trial(BaseModel): default=0.0, description="Horizontal delta offset of the lickspouts (in mm) applied in this trial. Positive values move the lickspouts right.", ) - extra_metadata: Optional[SerializeAsAny[Any]] = Field( - default=None, - description="Additional metadata to include with the trial. This field will NOT be used or validated by the task engine.", + trial_metadata: TrialMetadata = Field( + default=TrialMetadata(), + validate_default=True, + description="Metadata fields that will not be used by task engine such as block information.", ) diff --git a/tests/trial_generators/test_block_based_trial_generator.py b/tests/trial_generators/test_block_based_trial_generator.py index 60efd2d..661439d 100644 --- a/tests/trial_generators/test_block_based_trial_generator.py +++ b/tests/trial_generators/test_block_based_trial_generator.py @@ -48,18 +48,6 @@ def test_next_returns_correct_reward_probs(self): self.assertEqual(trial.p_reward_left, self.generator.block.p_left_reward) self.assertEqual(trial.p_reward_right, self.generator.block.p_right_reward) - #### Test unbaited #### - - def test_baiting_disabled_reward_prob_unchanged(self): - """Without baiting, reward probs should equal block probs exactly.""" - self.generator.block = Block(p_right_reward=0.8, p_left_reward=0.2, right_length=10, left_length=10) - self.generator.is_left_baited = True - self.generator.is_right_baited = True - trial = self.generator.next() - - self.assertEqual(trial.p_reward_right, 0.8) - self.assertEqual(trial.p_reward_left, 0.2) - class TestBlockBaseBaitingTrialGenerator(unittest.TestCase): def setUp(self): @@ -80,14 +68,14 @@ def test_baiting_sets_prob_to_1_when_baited(self): def test_baiting_accumulates_when_random_exceeds_prob(self): """Bait should carry over when random number exceeds reward prob.""" self.generator.block = Block(p_right_reward=0.5, p_left_reward=0.5, right_length=10, left_length=10) - self.generator.is_right_baited = False - self.generator.is_left_baited = False + self.generator.is_right_baited = True + self.generator.is_left_baited = True with patch("numpy.random.random", return_value=np.array([0.9, 0.9])): trial = self.generator.next() - self.assertEqual(trial.p_reward_right, 0.5) - self.assertEqual(trial.p_reward_left, 0.5) + self.assertEqual(trial.p_reward_right, 1.0) + self.assertEqual(trial.p_reward_left, 1.0) def test_baiting_triggers_when_random_below_prob(self): """Bait should trigger reward prob of 1.0 when random number is below reward prob.""" diff --git a/tests/trial_generators/test_coupled_trial_generator.py b/tests/trial_generators/test_coupled_trial_generator.py index e4cc59e..9060b4b 100644 --- a/tests/trial_generators/test_coupled_trial_generator.py +++ b/tests/trial_generators/test_coupled_trial_generator.py @@ -3,12 +3,11 @@ from datetime import timedelta import numpy as np +from util import simulate_response from aind_behavior_dynamic_foraging.task_logic.trial_generators import CoupledTrialGeneratorSpec from aind_behavior_dynamic_foraging.task_logic.trial_models import Trial, TrialOutcome -from .util import simulate_response - logging.basicConfig(level=logging.DEBUG) diff --git a/tests/trial_generators/test_uncoupled_trial_generator.py b/tests/trial_generators/test_uncoupled_trial_generator.py index 2b21422..1755214 100644 --- a/tests/trial_generators/test_uncoupled_trial_generator.py +++ b/tests/trial_generators/test_uncoupled_trial_generator.py @@ -2,6 +2,7 @@ import unittest import numpy as np +from util import simulate_response from aind_behavior_dynamic_foraging.task_logic.trial_generators.uncoupled_trial_gnerator import ( Block, @@ -11,8 +12,6 @@ ) from aind_behavior_dynamic_foraging.task_logic.trial_models import Trial -from .util import simulate_response - logging.basicConfig(level=logging.DEBUG) diff --git a/tests/trial_generators/test_warmup_trial_generator.py b/tests/trial_generators/test_warmup_trial_generator.py index e478685..0ca5949 100644 --- a/tests/trial_generators/test_warmup_trial_generator.py +++ b/tests/trial_generators/test_warmup_trial_generator.py @@ -1,14 +1,13 @@ import unittest import numpy as np +from util import simulate_response from aind_behavior_dynamic_foraging.task_logic.trial_generators.coupled_trial_generators.coupled_warmup_trial_generator import ( CoupledWarmupTrialGeneratorSpec, ) from aind_behavior_dynamic_foraging.task_logic.trial_models import Trial, TrialOutcome -from .util import simulate_response - def make_outcome(is_right_choice: bool | None, is_rewarded: bool) -> TrialOutcome: return TrialOutcome(trial=Trial(), is_right_choice=is_right_choice, is_rewarded=is_rewarded) From 55689d93c482bbef487204814deff35792325e54 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Wed, 27 May 2026 09:49:21 -0700 Subject: [PATCH 2/3] changes field name --- schema/aind_behavior_dynamic_foraging.json | 100 +++---- .../AindBehaviorDynamicForaging.Generated.cs | 254 +++++++++--------- .../block_based_trial_generator.py | 4 +- .../task_logic/trial_models.py | 6 +- .../test_coupled_trial_generator.py | 3 +- .../test_uncoupled_trial_generator.py | 3 +- .../test_warmup_trial_generator.py | 3 +- 7 files changed, 188 insertions(+), 185 deletions(-) diff --git a/schema/aind_behavior_dynamic_foraging.json b/schema/aind_behavior_dynamic_foraging.json index ae0736c..97f61b9 100644 --- a/schema/aind_behavior_dynamic_foraging.json +++ b/schema/aind_behavior_dynamic_foraging.json @@ -2105,6 +2105,54 @@ "title": "Measurement", "type": "object" }, + "Metadata": { + "description": "Metadata for trial. These fields will NOT be used by the task engine.", + "properties": { + "block_p_reward_left": { + "default": null, + "description": "The block probability of reward on the left side if response is made.", + "oneOf": [ + { + "maximum": 1, + "minimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Block P Reward Left" + }, + "block_p_reward_right": { + "default": null, + "description": "The block probability of reward on the right side if response is made.", + "oneOf": [ + { + "maximum": 1, + "minimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Block P Reward Right" + }, + "extra": { + "default": null, + "description": "Additional metadata to include with the trial. This field will NOT be used or validated by the task engine.", + "oneOf": [ + {}, + { + "type": "null" + } + ], + "title": "Extra" + } + }, + "title": "Metadata", + "type": "object" + }, "MicrostepResolution": { "description": "Microstep resolution available", "enum": [ @@ -3277,8 +3325,8 @@ "title": "Lickspout Offset Delta", "type": "number" }, - "trial_metadata": { - "$ref": "#/$defs/TrialMetadata", + "metadata": { + "$ref": "#/$defs/Metadata", "default": { "block_p_reward_left": null, "block_p_reward_right": null, @@ -3352,54 +3400,6 @@ } ] }, - "TrialMetadata": { - "description": "Metadata for trial. These fields will NOT be used by the task engine.", - "properties": { - "block_p_reward_left": { - "default": null, - "description": "The block probability of reward on the left side if response is made.", - "oneOf": [ - { - "maximum": 1, - "minimum": 0, - "type": "number" - }, - { - "type": "null" - } - ], - "title": "Block P Reward Left" - }, - "block_p_reward_right": { - "default": null, - "description": "The block probability of reward on the right side if response is made.", - "oneOf": [ - { - "maximum": 1, - "minimum": 0, - "type": "number" - }, - { - "type": "null" - } - ], - "title": "Block P Reward Right" - }, - "extra": { - "default": null, - "description": "Additional metadata to include with the trial. This field will NOT be used or validated by the task engine.", - "oneOf": [ - {}, - { - "type": "null" - } - ], - "title": "Extra" - } - }, - "title": "TrialMetadata", - "type": "object" - }, "TrialOutcome": { "description": "Represents the outcome of a single trial.", "properties": { diff --git a/src/Extensions/AindBehaviorDynamicForaging.Generated.cs b/src/Extensions/AindBehaviorDynamicForaging.Generated.cs index 90cd49c..46874f3 100644 --- a/src/Extensions/AindBehaviorDynamicForaging.Generated.cs +++ b/src/Extensions/AindBehaviorDynamicForaging.Generated.cs @@ -3894,6 +3894,119 @@ public override string ToString() } + /// + /// Metadata for trial. These fields will NOT be used by the task engine. + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.9.0.0 (Newtonsoft.Json v13.0.0.0)")] + [System.ComponentModel.DescriptionAttribute("Metadata for trial. These fields will NOT be used by the task engine.")] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] + [Bonsai.CombinatorAttribute(MethodName="Generate")] + public partial class Metadata + { + + private double? _blockPRewardLeft; + + private double? _blockPRewardRight; + + private object _extra; + + public Metadata() + { + } + + protected Metadata(Metadata other) + { + _blockPRewardLeft = other._blockPRewardLeft; + _blockPRewardRight = other._blockPRewardRight; + _extra = other._extra; + } + + /// + /// The block probability of reward on the left side if response is made. + /// + [Newtonsoft.Json.JsonPropertyAttribute("block_p_reward_left")] + [System.ComponentModel.DescriptionAttribute("The block probability of reward on the left side if response is made.")] + public double? BlockPRewardLeft + { + get + { + return _blockPRewardLeft; + } + set + { + _blockPRewardLeft = value; + } + } + + /// + /// The block probability of reward on the right side if response is made. + /// + [Newtonsoft.Json.JsonPropertyAttribute("block_p_reward_right")] + [System.ComponentModel.DescriptionAttribute("The block probability of reward on the right side if response is made.")] + public double? BlockPRewardRight + { + get + { + return _blockPRewardRight; + } + set + { + _blockPRewardRight = value; + } + } + + /// + /// Additional metadata to include with the trial. This field will NOT be used or validated by the task engine. + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + [Newtonsoft.Json.JsonPropertyAttribute("extra")] + [System.ComponentModel.DescriptionAttribute("Additional metadata to include with the trial. This field will NOT be used or val" + + "idated by the task engine.")] + public object Extra + { + get + { + return _extra; + } + set + { + _extra = value; + } + } + + public System.IObservable Generate() + { + return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return(new Metadata(this))); + } + + public System.IObservable Generate(System.IObservable source) + { + return System.Reactive.Linq.Observable.Select(source, _ => new Metadata(this)); + } + + protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) + { + stringBuilder.Append("BlockPRewardLeft = " + _blockPRewardLeft + ", "); + stringBuilder.Append("BlockPRewardRight = " + _blockPRewardRight + ", "); + stringBuilder.Append("Extra = " + _extra); + 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(); + } + } + + /// /// Settings for the quick retract feature. /// @@ -5639,7 +5752,7 @@ public partial class Trial private double _lickspoutOffsetDelta; - private TrialMetadata _trialMetadata; + private Metadata _metadata; public Trial() { @@ -5651,7 +5764,7 @@ public Trial() _quiescencePeriodDuration = 0.5D; _interTrialIntervalDuration = 5D; _lickspoutOffsetDelta = 0D; - _trialMetadata = new TrialMetadata(); + _metadata = new Metadata(); } protected Trial(Trial other) @@ -5667,7 +5780,7 @@ protected Trial(Trial other) _interTrialIntervalDuration = other._interTrialIntervalDuration; _isAutoResponseRight = other._isAutoResponseRight; _lickspoutOffsetDelta = other._lickspoutOffsetDelta; - _trialMetadata = other._trialMetadata; + _metadata = other._metadata; } /// @@ -5867,17 +5980,17 @@ public double LickspoutOffsetDelta /// Metadata fields that will not be used by task engine such as block information. /// [System.Xml.Serialization.XmlIgnoreAttribute()] - [Newtonsoft.Json.JsonPropertyAttribute("trial_metadata")] + [Newtonsoft.Json.JsonPropertyAttribute("metadata")] [System.ComponentModel.DescriptionAttribute("Metadata fields that will not be used by task engine such as block information.")] - public TrialMetadata TrialMetadata + public Metadata Metadata { get { - return _trialMetadata; + return _metadata; } set { - _trialMetadata = value; + _metadata = value; } } @@ -5904,7 +6017,7 @@ protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) stringBuilder.Append("InterTrialIntervalDuration = " + _interTrialIntervalDuration + ", "); stringBuilder.Append("IsAutoResponseRight = " + _isAutoResponseRight + ", "); stringBuilder.Append("LickspoutOffsetDelta = " + _lickspoutOffsetDelta + ", "); - stringBuilder.Append("TrialMetadata = " + _trialMetadata); + stringBuilder.Append("Metadata = " + _metadata); return true; } @@ -6035,119 +6148,6 @@ public override string ToString() } - /// - /// Metadata for trial. These fields will NOT be used by the task engine. - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.9.0.0 (Newtonsoft.Json v13.0.0.0)")] - [System.ComponentModel.DescriptionAttribute("Metadata for trial. These fields will NOT be used by the task engine.")] - [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] - [Bonsai.CombinatorAttribute(MethodName="Generate")] - public partial class TrialMetadata - { - - private double? _blockPRewardLeft; - - private double? _blockPRewardRight; - - private object _extra; - - public TrialMetadata() - { - } - - protected TrialMetadata(TrialMetadata other) - { - _blockPRewardLeft = other._blockPRewardLeft; - _blockPRewardRight = other._blockPRewardRight; - _extra = other._extra; - } - - /// - /// The block probability of reward on the left side if response is made. - /// - [Newtonsoft.Json.JsonPropertyAttribute("block_p_reward_left")] - [System.ComponentModel.DescriptionAttribute("The block probability of reward on the left side if response is made.")] - public double? BlockPRewardLeft - { - get - { - return _blockPRewardLeft; - } - set - { - _blockPRewardLeft = value; - } - } - - /// - /// The block probability of reward on the right side if response is made. - /// - [Newtonsoft.Json.JsonPropertyAttribute("block_p_reward_right")] - [System.ComponentModel.DescriptionAttribute("The block probability of reward on the right side if response is made.")] - public double? BlockPRewardRight - { - get - { - return _blockPRewardRight; - } - set - { - _blockPRewardRight = value; - } - } - - /// - /// Additional metadata to include with the trial. This field will NOT be used or validated by the task engine. - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - [Newtonsoft.Json.JsonPropertyAttribute("extra")] - [System.ComponentModel.DescriptionAttribute("Additional metadata to include with the trial. This field will NOT be used or val" + - "idated by the task engine.")] - public object Extra - { - get - { - return _extra; - } - set - { - _extra = value; - } - } - - public System.IObservable Generate() - { - return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return(new TrialMetadata(this))); - } - - public System.IObservable Generate(System.IObservable source) - { - return System.Reactive.Linq.Observable.Select(source, _ => new TrialMetadata(this)); - } - - protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) - { - stringBuilder.Append("BlockPRewardLeft = " + _blockPRewardLeft + ", "); - stringBuilder.Append("BlockPRewardRight = " + _blockPRewardRight + ", "); - stringBuilder.Append("Extra = " + _extra); - 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(); - } - } - - /// /// Represents the outcome of a single trial. /// @@ -7966,6 +7966,11 @@ public System.IObservable Process(System.IObservable source return Process(source); } + public System.IObservable Process(System.IObservable source) + { + return Process(source); + } + public System.IObservable Process(System.IObservable source) { return Process(source); @@ -8021,11 +8026,6 @@ public System.IObservable Process(System.IObservable return Process(source); } - public System.IObservable Process(System.IObservable source) - { - return Process(source); - } - public System.IObservable Process(System.IObservable source) { return Process(source); @@ -8110,6 +8110,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))] @@ -8121,7 +8122,6 @@ 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/aind_behavior_dynamic_foraging/task_logic/trial_generators/block_based_trial_generator.py b/src/aind_behavior_dynamic_foraging/task_logic/trial_generators/block_based_trial_generator.py index 253e2a1..fb45582 100644 --- a/src/aind_behavior_dynamic_foraging/task_logic/trial_generators/block_based_trial_generator.py +++ b/src/aind_behavior_dynamic_foraging/task_logic/trial_generators/block_based_trial_generator.py @@ -12,7 +12,7 @@ from aind_behavior_services.task.distributions_utils import draw_sample from pydantic import BaseModel, Field -from ..trial_models import Trial, TrialMetadata +from ..trial_models import Metadata, Trial from ._base import BaseTrialGeneratorSpecModel, ITrialGenerator, TrialOutcome logger = logging.getLogger(__name__) @@ -189,7 +189,7 @@ def next(self) -> Trial | None: quiescence_period_duration=quiescent, inter_trial_interval_duration=iti, is_auto_response_right=is_auto_response_right, - trial_metadata=TrialMetadata( + metadata=Metadata( block_p_reward_left=self.block.p_left_reward, block_p_reward_right=self.block.p_right_reward, extra=BlockBasedTrialMetadata(is_autowater=is_autowater), diff --git a/src/aind_behavior_dynamic_foraging/task_logic/trial_models.py b/src/aind_behavior_dynamic_foraging/task_logic/trial_models.py index 9a75f17..1f7cea5 100644 --- a/src/aind_behavior_dynamic_foraging/task_logic/trial_models.py +++ b/src/aind_behavior_dynamic_foraging/task_logic/trial_models.py @@ -43,7 +43,7 @@ class QuickRetractSettings(BaseModel): ) -class TrialMetadata(BaseModel): +class Metadata(BaseModel): """Metadata for trial. These fields will NOT be used by the task engine.""" block_p_reward_left: Optional[float] = Field( @@ -99,8 +99,8 @@ class Trial(BaseModel): default=0.0, description="Horizontal delta offset of the lickspouts (in mm) applied in this trial. Positive values move the lickspouts right.", ) - trial_metadata: TrialMetadata = Field( - default=TrialMetadata(), + metadata: Metadata = Field( + default=Metadata(), validate_default=True, description="Metadata fields that will not be used by task engine such as block information.", ) diff --git a/tests/trial_generators/test_coupled_trial_generator.py b/tests/trial_generators/test_coupled_trial_generator.py index 9060b4b..e4cc59e 100644 --- a/tests/trial_generators/test_coupled_trial_generator.py +++ b/tests/trial_generators/test_coupled_trial_generator.py @@ -3,11 +3,12 @@ from datetime import timedelta import numpy as np -from util import simulate_response from aind_behavior_dynamic_foraging.task_logic.trial_generators import CoupledTrialGeneratorSpec from aind_behavior_dynamic_foraging.task_logic.trial_models import Trial, TrialOutcome +from .util import simulate_response + logging.basicConfig(level=logging.DEBUG) diff --git a/tests/trial_generators/test_uncoupled_trial_generator.py b/tests/trial_generators/test_uncoupled_trial_generator.py index 1755214..2b21422 100644 --- a/tests/trial_generators/test_uncoupled_trial_generator.py +++ b/tests/trial_generators/test_uncoupled_trial_generator.py @@ -2,7 +2,6 @@ import unittest import numpy as np -from util import simulate_response from aind_behavior_dynamic_foraging.task_logic.trial_generators.uncoupled_trial_gnerator import ( Block, @@ -12,6 +11,8 @@ ) from aind_behavior_dynamic_foraging.task_logic.trial_models import Trial +from .util import simulate_response + logging.basicConfig(level=logging.DEBUG) diff --git a/tests/trial_generators/test_warmup_trial_generator.py b/tests/trial_generators/test_warmup_trial_generator.py index 0ca5949..e478685 100644 --- a/tests/trial_generators/test_warmup_trial_generator.py +++ b/tests/trial_generators/test_warmup_trial_generator.py @@ -1,13 +1,14 @@ import unittest import numpy as np -from util import simulate_response from aind_behavior_dynamic_foraging.task_logic.trial_generators.coupled_trial_generators.coupled_warmup_trial_generator import ( CoupledWarmupTrialGeneratorSpec, ) from aind_behavior_dynamic_foraging.task_logic.trial_models import Trial, TrialOutcome +from .util import simulate_response + def make_outcome(is_right_choice: bool | None, is_rewarded: bool) -> TrialOutcome: return TrialOutcome(trial=Trial(), is_right_choice=is_right_choice, is_rewarded=is_rewarded) From 03b69f01fc4c2679a8e256a9952251deff62cb22 Mon Sep 17 00:00:00 2001 From: Micah Woodard Date: Wed, 27 May 2026 10:18:37 -0700 Subject: [PATCH 3/3] set metadata as optional and renames block prob --- schema/aind_behavior_dynamic_foraging.json | 29 ++++++++------- .../AindBehaviorDynamicForaging.Generated.cs | 37 +++++++++---------- .../block_based_trial_generator.py | 4 +- .../task_logic/trial_models.py | 18 ++++++--- 4 files changed, 48 insertions(+), 40 deletions(-) diff --git a/schema/aind_behavior_dynamic_foraging.json b/schema/aind_behavior_dynamic_foraging.json index 97f61b9..681846e 100644 --- a/schema/aind_behavior_dynamic_foraging.json +++ b/schema/aind_behavior_dynamic_foraging.json @@ -2108,9 +2108,9 @@ "Metadata": { "description": "Metadata for trial. These fields will NOT be used by the task engine.", "properties": { - "block_p_reward_left": { + "p_reward_left": { "default": null, - "description": "The block probability of reward on the left side if response is made.", + "description": "Metadata for block probability of reward on the left side if response is made.", "oneOf": [ { "maximum": 1, @@ -2121,11 +2121,11 @@ "type": "null" } ], - "title": "Block P Reward Left" + "title": "P Reward Left" }, - "block_p_reward_right": { + "p_reward_right": { "default": null, - "description": "The block probability of reward on the right side if response is made.", + "description": "Metadata for the probability of reward on the right side if response is made.", "oneOf": [ { "maximum": 1, @@ -2136,7 +2136,7 @@ "type": "null" } ], - "title": "Block P Reward Right" + "title": "P Reward Right" }, "extra": { "default": null, @@ -3326,13 +3326,16 @@ "type": "number" }, "metadata": { - "$ref": "#/$defs/Metadata", - "default": { - "block_p_reward_left": null, - "block_p_reward_right": null, - "extra": null - }, - "description": "Metadata fields that will not be used by task engine such as block information." + "default": null, + "description": "Metadata fields that will not be used by task engine such as block information.", + "oneOf": [ + { + "$ref": "#/$defs/Metadata" + }, + { + "type": "null" + } + ] } }, "title": "Trial", diff --git a/src/Extensions/AindBehaviorDynamicForaging.Generated.cs b/src/Extensions/AindBehaviorDynamicForaging.Generated.cs index 46874f3..fd4cb0f 100644 --- a/src/Extensions/AindBehaviorDynamicForaging.Generated.cs +++ b/src/Extensions/AindBehaviorDynamicForaging.Generated.cs @@ -3904,9 +3904,9 @@ public override string ToString() public partial class Metadata { - private double? _blockPRewardLeft; + private double? _pRewardLeft; - private double? _blockPRewardRight; + private double? _pRewardRight; private object _extra; @@ -3916,42 +3916,42 @@ public Metadata() protected Metadata(Metadata other) { - _blockPRewardLeft = other._blockPRewardLeft; - _blockPRewardRight = other._blockPRewardRight; + _pRewardLeft = other._pRewardLeft; + _pRewardRight = other._pRewardRight; _extra = other._extra; } /// - /// The block probability of reward on the left side if response is made. + /// Metadata for block probability of reward on the left side if response is made. /// - [Newtonsoft.Json.JsonPropertyAttribute("block_p_reward_left")] - [System.ComponentModel.DescriptionAttribute("The block probability of reward on the left side if response is made.")] - public double? BlockPRewardLeft + [Newtonsoft.Json.JsonPropertyAttribute("p_reward_left")] + [System.ComponentModel.DescriptionAttribute("Metadata for block probability of reward on the left side if response is made.")] + public double? PRewardLeft { get { - return _blockPRewardLeft; + return _pRewardLeft; } set { - _blockPRewardLeft = value; + _pRewardLeft = value; } } /// - /// The block probability of reward on the right side if response is made. + /// Metadata for the probability of reward on the right side if response is made. /// - [Newtonsoft.Json.JsonPropertyAttribute("block_p_reward_right")] - [System.ComponentModel.DescriptionAttribute("The block probability of reward on the right side if response is made.")] - public double? BlockPRewardRight + [Newtonsoft.Json.JsonPropertyAttribute("p_reward_right")] + [System.ComponentModel.DescriptionAttribute("Metadata for the probability of reward on the right side if response is made.")] + public double? PRewardRight { get { - return _blockPRewardRight; + return _pRewardRight; } set { - _blockPRewardRight = value; + _pRewardRight = value; } } @@ -3986,8 +3986,8 @@ public System.IObservable Generate(System.IObservable