From 3b45ce10a4cfc22f2893b842f4cb3a2717f54ce5 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 28 Sep 2023 20:56:22 +0100 Subject: [PATCH 1/5] Update operator category --- src/Aeon.Environment/RoomLightController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Aeon.Environment/RoomLightController.cs b/src/Aeon.Environment/RoomLightController.cs index fc54789..2bbdd21 100644 --- a/src/Aeon.Environment/RoomLightController.cs +++ b/src/Aeon.Environment/RoomLightController.cs @@ -13,7 +13,6 @@ namespace Aeon.Environment { [Combinator] - [WorkflowElementCategory(ElementCategory.Source)] [Description("Creates and configures a connection to the room light controller over Brainboxes Ethernet to Serial.")] public class RoomLightController { From a3e93a9f4421dd76eac4c80e14ea2d9bbc8fc11f Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 28 Sep 2023 21:35:26 +0100 Subject: [PATCH 2/5] Include embedded resource name in assert message --- src/Aeon.Tests/AssertWorkflow.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Aeon.Tests/AssertWorkflow.cs b/src/Aeon.Tests/AssertWorkflow.cs index 119639f..6489bde 100644 --- a/src/Aeon.Tests/AssertWorkflow.cs +++ b/src/Aeon.Tests/AssertWorkflow.cs @@ -33,7 +33,10 @@ public static void CanBuildEmbeddedResources(Assembly assembly) workflowElement.GetType().Name != nameof(AeonCapture)) #pragma warning restore CS0612 // Type or member is obsolete { - Assert.IsNotInstanceOfType(workflowElement, typeof(UnknownTypeBuilder)); + Assert.IsNotInstanceOfType( + workflowElement, + typeof(UnknownTypeBuilder), + $"Embedded workflow: {name}."); } return builder; }, recurse: true); From cf7b68f51ad5ecb45826248b21fc61894da47399 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 28 Sep 2023 21:38:21 +0100 Subject: [PATCH 3/5] Remove redundant operators --- src/Aeon.Acquisition/ActivityTracking.bonsai | 4 +- src/Aeon.Acquisition/Aeon.Acquisition.csproj | 2 +- src/Aeon.Acquisition/CreateTimestamped.cs | 20 ------ src/Aeon.Acquisition/TransformTimestamped.cs | 71 -------------------- src/Aeon.Environment/Aeon.Environment.csproj | 2 +- src/Aeon.Environment/LightController.bonsai | 40 +++++------ src/Aeon.Environment/WeightScale.bonsai | 6 +- src/Aeon.Tests/OperatorTests.cs | 2 +- 8 files changed, 26 insertions(+), 121 deletions(-) delete mode 100644 src/Aeon.Acquisition/CreateTimestamped.cs delete mode 100644 src/Aeon.Acquisition/TransformTimestamped.cs diff --git a/src/Aeon.Acquisition/ActivityTracking.bonsai b/src/Aeon.Acquisition/ActivityTracking.bonsai index 8e2e3d7..c0fdb94 100644 --- a/src/Aeon.Acquisition/ActivityTracking.bonsai +++ b/src/Aeon.Acquisition/ActivityTracking.bonsai @@ -4,7 +4,7 @@ xmlns:cv="clr-namespace:Bonsai.Vision;assembly=Bonsai.Vision" xmlns:dsp="clr-namespace:Bonsai.Dsp;assembly=Bonsai.Dsp" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" - xmlns:aeon="clr-namespace:Aeon.Acquisition;assembly=Aeon.Acquisition" + xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" xmlns="https://bonsai-rx.org/2018/workflow"> Measures pixel motion in the specified region of interest. @@ -54,7 +54,7 @@ - + diff --git a/src/Aeon.Acquisition/Aeon.Acquisition.csproj b/src/Aeon.Acquisition/Aeon.Acquisition.csproj index ffcb1df..7fad82f 100644 --- a/src/Aeon.Acquisition/Aeon.Acquisition.csproj +++ b/src/Aeon.Acquisition/Aeon.Acquisition.csproj @@ -6,7 +6,7 @@ Bonsai Rx Project Aeon Acquisition net472 0.5.0 - build230923 + build230924 diff --git a/src/Aeon.Acquisition/CreateTimestamped.cs b/src/Aeon.Acquisition/CreateTimestamped.cs deleted file mode 100644 index b4317b8..0000000 --- a/src/Aeon.Acquisition/CreateTimestamped.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Bonsai; -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai.Harp; - -namespace Aeon.Acquisition -{ - [Combinator] - [Description("Creates a timestamped structure from a value-timestamp pair.")] - [WorkflowElementCategory(ElementCategory.Transform)] - public class CreateTimestamped - { - public IObservable> Process(IObservable> source) - { - return source.Select(value => Timestamped.Create(value.Item1, value.Item2)); - } - } -} \ No newline at end of file diff --git a/src/Aeon.Acquisition/TransformTimestamped.cs b/src/Aeon.Acquisition/TransformTimestamped.cs deleted file mode 100644 index 1283a29..0000000 --- a/src/Aeon.Acquisition/TransformTimestamped.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Linq.Expressions; -using System.Reactive.Linq; -using Bonsai; -using Bonsai.Expressions; -using Bonsai.Harp; - -namespace Aeon.Acquisition -{ - [DefaultProperty(nameof(IncludeTimestamp))] - [Description("Applies a transformation to the elements of an observable sequence using the encapsulated workflow and preserves the timestamp of each element.")] - public class TransformTimestamped : WorkflowExpressionBuilder - { - static readonly Range argumentRange = Range.Create(lowerBound: 1, upperBound: 1); - - public TransformTimestamped() - { - } - - public TransformTimestamped(ExpressionBuilderGraph workflow) - : base(workflow) - { - } - - public override Range ArgumentRange => argumentRange; - - public bool IncludeTimestamp { get; set; } - - public override Expression Build(IEnumerable arguments) - { - var source = arguments.FirstOrDefault(); - if (source == null) - { - throw new InvalidOperationException("There must be at least one input to the transform workflow."); - } - - var parameterType = source.Type.GetGenericArguments()[0]; - if (!parameterType.IsGenericType || parameterType.GetGenericTypeDefinition() != typeof(Timestamped<>)) - { - throw new InvalidOperationException("The input to the transform workflow must be Harp timestamped."); - } - - // Assign input - var timestampedType = parameterType.GetGenericArguments()[0]; - var selectorParameter = Expression.Parameter(IncludeTimestamp ? source.Type : typeof(IObservable<>).MakeGenericType(timestampedType)); - return BuildWorkflow(arguments, selectorParameter, selectorBody => - { - var selector = Expression.Lambda(selectorBody, selectorParameter); - var selectorObservableType = selector.ReturnType.GetGenericArguments()[0]; - return Expression.Call(GetType(), nameof(Process), new[] { timestampedType, selectorObservableType }, source, selector); - }); - } - - static IObservable> Process(IObservable> source, Func>, IObservable> selector) - { - return source.Publish(ps => ps - .Select(value => value.Seconds) - .Zip(selector(ps), (seconds, value) => Timestamped.Create(value, seconds))); - } - - static IObservable> Process(IObservable> source, Func, IObservable> selector) - { - return source.Publish(ps => ps - .Select(input => input.Seconds) - .Zip(selector(ps.Select(input => input.Value)), (seconds, value) => Timestamped.Create(value, seconds))); - } - } -} diff --git a/src/Aeon.Environment/Aeon.Environment.csproj b/src/Aeon.Environment/Aeon.Environment.csproj index 69efdaa..2eb4217 100644 --- a/src/Aeon.Environment/Aeon.Environment.csproj +++ b/src/Aeon.Environment/Aeon.Environment.csproj @@ -7,7 +7,7 @@ Bonsai Rx Project Aeon Environment net472 0.1.0 - build230926 + build230927 diff --git a/src/Aeon.Environment/LightController.bonsai b/src/Aeon.Environment/LightController.bonsai index 32b26fc..f8d8332 100644 --- a/src/Aeon.Environment/LightController.bonsai +++ b/src/Aeon.Environment/LightController.bonsai @@ -5,7 +5,7 @@ xmlns:osc="clr-namespace:Bonsai.Osc;assembly=Bonsai.Osc" xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" - xmlns:aeon="clr-namespace:Aeon.Acquisition;assembly=Aeon.Acquisition" + xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns="https://bonsai-rx.org/2018/workflow"> Provides control and acquisition functionality for automated room lighting. @@ -33,16 +33,13 @@ Item1 as Channel, Item2 as Value) - GlobalTrigger - - - Seconds + SynchronizerEvents - + @@ -90,22 +87,21 @@ Item2 as Value) - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Aeon.Environment/WeightScale.bonsai b/src/Aeon.Environment/WeightScale.bonsai index 5e1bf6c..aaa6624 100644 --- a/src/Aeon.Environment/WeightScale.bonsai +++ b/src/Aeon.Environment/WeightScale.bonsai @@ -4,7 +4,7 @@ xmlns:port="clr-namespace:Bonsai.IO.Ports;assembly=Bonsai.System" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" - xmlns:aeon="clr-namespace:Aeon.Acquisition;assembly=Aeon.Acquisition" + xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns="https://bonsai-rx.org/2018/workflow"> Contains acquisition functionality for automated arena weighing scales. @@ -62,7 +62,7 @@ Item2.Contains("?") ? 0.0 : 1.0 as Confidence) - + @@ -100,7 +100,7 @@ Item2.Contains("?") ? 0.0 : 1.0 as Confidence) - + diff --git a/src/Aeon.Tests/OperatorTests.cs b/src/Aeon.Tests/OperatorTests.cs index 0b83de6..3789e1b 100644 --- a/src/Aeon.Tests/OperatorTests.cs +++ b/src/Aeon.Tests/OperatorTests.cs @@ -11,7 +11,7 @@ public class OperatorTests [TestMethod] public void Build_Workflows() { - var acquisition = typeof(CreateTimestamped).Assembly; + var acquisition = typeof(GroupByTime).Assembly; var environment = typeof(EnvironmentState).Assembly; var foraging = typeof(WheelDisplacement).Assembly; AssertWorkflow.CanBuildEmbeddedResources(acquisition); From cf84c01008fd795219fc1bf7865004a9c665b0af Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 28 Sep 2023 21:44:40 +0100 Subject: [PATCH 4/5] Remove custom CSV writer operator --- src/Aeon.Acquisition/AeonWriter.cs | 120 ---------------------------- src/Aeon.Acquisition/LogData.bonsai | 3 +- 2 files changed, 1 insertion(+), 122 deletions(-) delete mode 100644 src/Aeon.Acquisition/AeonWriter.cs diff --git a/src/Aeon.Acquisition/AeonWriter.cs b/src/Aeon.Acquisition/AeonWriter.cs deleted file mode 100644 index 8b3a5e2..0000000 --- a/src/Aeon.Acquisition/AeonWriter.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Bonsai.IO; -using System; -using System.ComponentModel; -using System.IO; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Text; -using System.Xml; -using System.Xml.Serialization; - -namespace Aeon.Acquisition -{ - [Description("Writes all elements of the sequence to a text file, ensuring stream is flushed after source is silent for a specified duration.")] - public class AeonWriter : CsvWriter - { - [XmlIgnore] - [Description("The time duration after which to flush the stream if no new value is generated.")] - public TimeSpan FlushDuration { get; set; } - - [Browsable(false)] - [XmlElement(nameof(FlushDuration))] - public string FlushDurationXml - { - get { return XmlConvert.ToString(FlushDuration); } - set { FlushDuration = XmlConvert.ToTimeSpan(value); } - } - - IObservable Process(IObservable source, string header, Action writeAction) - { - return Observable.Create(observer => - { - var fileName = FileName; - if (string.IsNullOrEmpty(fileName)) - { - throw new InvalidOperationException("A valid file path must be specified."); - } - - PathHelper.EnsureDirectory(fileName); - fileName = PathHelper.AppendSuffix(fileName, Suffix); - if (File.Exists(fileName) && !Overwrite && !Append) - { - throw new IOException(string.Format("The file '{0}' already exists.", fileName)); - } - - var disposable = new WriterDisposable(); - disposable.Schedule(() => - { - try - { - var writer = new StreamWriter(fileName, Append, Encoding.ASCII); - if (!string.IsNullOrEmpty(header)) writer.WriteLine(header); - disposable.Writer = writer; - } - catch (Exception ex) - { - observer.OnError(ex); - } - }); - - var process = source.Do(input => - { - disposable.Schedule(() => - { - try { writeAction(input, disposable.Writer); } - catch (Exception ex) - { - observer.OnError(ex); - } - }); - }); - - var flushInterval = FlushDuration; - if (flushInterval > TimeSpan.Zero) - { - process = process.Publish(ps => ps.Merge( - ps.Throttle(flushInterval).Do(value => - { - disposable.Schedule(() => - { - try { disposable.Writer.Flush(); } - catch (Exception ex) - { - observer.OnError(ex); - } - }); - }).IgnoreElements())); - } - - return new CompositeDisposable(process.SubscribeSafe(observer), disposable); - }); - } - - class WriterDisposable : IDisposable - { - readonly EventLoopScheduler scheduler = new EventLoopScheduler(); - - public StreamWriter Writer { get; set; } - - public void Schedule(Action action) - { - scheduler.Schedule(action); - } - - void DisposeWriter() - { - Writer?.Dispose(); - } - - public void Dispose() - { - scheduler.Schedule(() => - { - DisposeWriter(); - scheduler.Dispose(); - }); - } - } - } -} diff --git a/src/Aeon.Acquisition/LogData.bonsai b/src/Aeon.Acquisition/LogData.bonsai index cc85c50..19ba4b5 100644 --- a/src/Aeon.Acquisition/LogData.bonsai +++ b/src/Aeon.Acquisition/LogData.bonsai @@ -74,12 +74,11 @@ - + false false None true - PT1M From f7242175784a109fe06b521dada3ca20c86e1c05 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 28 Sep 2023 21:49:21 +0100 Subject: [PATCH 5/5] Rename room light device node to avoid ambiguity --- .../{RoomLightController.cs => RoomLightDevice.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Aeon.Environment/{RoomLightController.cs => RoomLightDevice.cs} (98%) diff --git a/src/Aeon.Environment/RoomLightController.cs b/src/Aeon.Environment/RoomLightDevice.cs similarity index 98% rename from src/Aeon.Environment/RoomLightController.cs rename to src/Aeon.Environment/RoomLightDevice.cs index 2bbdd21..8c4641b 100644 --- a/src/Aeon.Environment/RoomLightController.cs +++ b/src/Aeon.Environment/RoomLightDevice.cs @@ -14,7 +14,7 @@ namespace Aeon.Environment { [Combinator] [Description("Creates and configures a connection to the room light controller over Brainboxes Ethernet to Serial.")] - public class RoomLightController + public class RoomLightDevice { readonly CreateSerialPort serialPort = new();