From 8f889dfab5562ad8329b3383bd201aefdce4e6ba Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 12 Jul 2024 15:54:52 -0400 Subject: [PATCH 1/8] Set up branch - Create ProbeInterface submodule - Add NuGet packages to Design - Add common framework files - Add resources --- .gitmodules | 3 + .../ChannelConfigurationDialog.Designer.cs | 185 ++++ .../ChannelConfigurationDialog.cs | 892 ++++++++++++++++++ .../ChannelConfigurationDialog.resx | 123 +++ .../OpenEphys.Onix.Design/ContactTag.cs | 72 ++ .../OpenEphys.Onix.Design/DesignHelper.cs | 242 +++++ .../GenericDeviceDialog.Designer.cs | 117 +++ .../GenericDeviceDialog.cs | 27 + .../GenericDeviceDialog.resx | 120 +++ .../OpenEphys.Onix.Design.csproj | 3 + .../Properties/Resources.Designer.cs | 123 +++ .../Properties/Resources.resx | 139 +++ .../Resources/StatusBlockedImage.png | Bin 0 -> 268 bytes .../Resources/StatusCriticalImage.png | Bin 0 -> 306 bytes .../Resources/StatusReadyImage.png | Bin 0 -> 309 bytes .../Resources/StatusRefreshImage.png | Bin 0 -> 13423 bytes .../Resources/StatusWarningImage.png | Bin 0 -> 1010 bytes .../Resources/UploadImage.png | Bin 0 -> 7224 bytes OpenEphys.Onix/OpenEphys.Onix.sln | 16 +- .../OpenEphys.Onix/OpenEphys.Onix.csproj | 4 + OpenEphys.Onix/OpenEphys.ProbeInterface | 1 + 21 files changed, 2066 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.Designer.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.resx create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/ContactTag.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.Designer.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.resx create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.Designer.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.resx create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusBlockedImage.png create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusCriticalImage.png create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusReadyImage.png create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusRefreshImage.png create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusWarningImage.png create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/Resources/UploadImage.png create mode 160000 OpenEphys.Onix/OpenEphys.ProbeInterface diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..11cc4839 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "OpenEphys.Onix/OpenEphys.ProbeInterface"] + path = OpenEphys.Onix/OpenEphys.ProbeInterface + url = https://github.com/open-ephys/OpenEphys.ProbeInterface diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.Designer.cs new file mode 100644 index 00000000..ed30d585 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.Designer.cs @@ -0,0 +1,185 @@ +namespace OpenEphys.Onix.Design +{ + partial class ChannelConfigurationDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.zedGraphChannels = new ZedGraph.ZedGraphControl(); + this.menuStrip = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.menuItemOpenFile = new System.Windows.Forms.ToolStripMenuItem(); + this.menuItemSaveFile = new System.Windows.Forms.ToolStripMenuItem(); + this.loadDefaultToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.buttonCancel = new System.Windows.Forms.Button(); + this.buttonOK = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.menuStrip.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.zedGraphChannels); + this.splitContainer1.Panel1.Controls.Add(this.menuStrip); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); + this.splitContainer1.Panel2.Controls.Add(this.buttonOK); + this.splitContainer1.Size = new System.Drawing.Size(686, 717); + this.splitContainer1.SplitterDistance = 657; + this.splitContainer1.TabIndex = 0; + // + // zedGraphChannels + // + this.zedGraphChannels.AutoSize = true; + this.zedGraphChannels.Dock = System.Windows.Forms.DockStyle.Fill; + this.zedGraphChannels.Location = new System.Drawing.Point(0, 33); + this.zedGraphChannels.Margin = new System.Windows.Forms.Padding(6, 8, 6, 8); + this.zedGraphChannels.Name = "zedGraphChannels"; + this.zedGraphChannels.ScrollGrace = 0D; + this.zedGraphChannels.ScrollMaxX = 0D; + this.zedGraphChannels.ScrollMaxY = 0D; + this.zedGraphChannels.ScrollMaxY2 = 0D; + this.zedGraphChannels.ScrollMinX = 0D; + this.zedGraphChannels.ScrollMinY = 0D; + this.zedGraphChannels.ScrollMinY2 = 0D; + this.zedGraphChannels.Size = new System.Drawing.Size(686, 624); + this.zedGraphChannels.TabIndex = 4; + this.zedGraphChannels.UseExtendedPrintDialog = true; + this.zedGraphChannels.ZoomEvent += new ZedGraph.ZedGraphControl.ZoomEventHandler(this.ZoomEvent); + // + // menuStrip + // + this.menuStrip.GripMargin = new System.Windows.Forms.Padding(2, 2, 0, 2); + this.menuStrip.ImageScalingSize = new System.Drawing.Size(24, 24); + this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem}); + this.menuStrip.Location = new System.Drawing.Point(0, 0); + this.menuStrip.Name = "menuStrip"; + this.menuStrip.Size = new System.Drawing.Size(686, 33); + this.menuStrip.TabIndex = 5; + this.menuStrip.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuItemOpenFile, + this.menuItemSaveFile, + this.loadDefaultToolStripMenuItem}); + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 29); + this.fileToolStripMenuItem.Text = "File"; + // + // menuItemOpenFile + // + this.menuItemOpenFile.Name = "menuItemOpenFile"; + this.menuItemOpenFile.Size = new System.Drawing.Size(215, 34); + this.menuItemOpenFile.Text = "Open File"; + this.menuItemOpenFile.Click += new System.EventHandler(this.MenuItemOpenFile); + // + // menuItemSaveFile + // + this.menuItemSaveFile.Name = "menuItemSaveFile"; + this.menuItemSaveFile.Size = new System.Drawing.Size(215, 34); + this.menuItemSaveFile.Text = "Save File"; + this.menuItemSaveFile.Click += new System.EventHandler(this.MenuItemSaveFile); + // + // loadDefaultToolStripMenuItem + // + this.loadDefaultToolStripMenuItem.Name = "loadDefaultToolStripMenuItem"; + this.loadDefaultToolStripMenuItem.Size = new System.Drawing.Size(215, 34); + this.loadDefaultToolStripMenuItem.Text = "Load Default"; + this.loadDefaultToolStripMenuItem.Click += new System.EventHandler(this.MenuItemLoadDefaultConfig); + // + // buttonCancel + // + this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonCancel.Location = new System.Drawing.Point(512, 8); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(162, 40); + this.buttonCancel.TabIndex = 4; + this.buttonCancel.Text = "Cancel"; + this.buttonCancel.UseVisualStyleBackColor = true; + // + // buttonOK + // + this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonOK.Location = new System.Drawing.Point(329, 8); + this.buttonOK.Name = "buttonOK"; + this.buttonOK.Size = new System.Drawing.Size(162, 40); + this.buttonOK.TabIndex = 3; + this.buttonOK.Text = "OK"; + this.buttonOK.UseVisualStyleBackColor = true; + this.buttonOK.Click += new System.EventHandler(this.ButtonOK); + // + // ChannelConfigurationDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(686, 717); + this.Controls.Add(this.splitContainer1); + this.MainMenuStrip = this.menuStrip; + this.Name = "ChannelConfigurationDialog"; + this.Text = "ChannelConfigurationDialog"; + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel1.PerformLayout(); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.menuStrip.ResumeLayout(false); + this.menuStrip.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer1; + internal ZedGraph.ZedGraphControl zedGraphChannels; + private System.Windows.Forms.Button buttonCancel; + private System.Windows.Forms.Button buttonOK; + private System.Windows.Forms.MenuStrip menuStrip; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem menuItemOpenFile; + private System.Windows.Forms.ToolStripMenuItem menuItemSaveFile; + private System.Windows.Forms.ToolStripMenuItem loadDefaultToolStripMenuItem; + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs new file mode 100644 index 00000000..c45719d0 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs @@ -0,0 +1,892 @@ +using System.Drawing; +using System.IO; +using System.Linq; +using System.Windows.Forms; +using ZedGraph; +using System; +using OpenEphys.ProbeInterface; +using System.Collections.Generic; + +namespace OpenEphys.Onix.Design +{ + /// + /// Simple dialog window that serves as the base class for all Channel Configuration windows. + /// Within, there are a number of useful methods for initializing, resizing, and drawing channels. + /// Each device must implement their own ChannelConfigurationDialog. + /// + public abstract partial class ChannelConfigurationDialog : Form + { + /// + /// Local variable that holds the channel configuration in memory until the user presses Okay + /// + internal ProbeGroup ChannelConfiguration; + + internal List ReferenceContacts = new(); + + internal readonly bool[] SelectedContacts = null; + + /// + /// Constructs the dialog window using the given probe group, and plots all contacts after loading. + /// + /// Channel configuration given as a + public ChannelConfigurationDialog(ProbeGroup probeGroup) + { + InitializeComponent(); + Shown += FormShown; + + if (probeGroup == null) + { + LoadDefaultChannelLayout(); + } + else + { + ChannelConfiguration = probeGroup; + } + + SelectedContacts = new bool[ChannelConfiguration.NumberOfContacts]; + + zedGraphChannels.MouseDownEvent += MouseDownEvent; + zedGraphChannels.MouseMoveEvent += MouseMoveEvent; + zedGraphChannels.MouseUpEvent += MouseUpEvent; + + InitializeZedGraphChannels(); + DrawProbeGroup(); + RefreshZedGraph(); + } + + /// + /// Return the default channel layout of the current device, which fully instatiates the probe group object + /// + /// + /// Using a class that inherits from ProbeGroup, the general usage would + /// be the default constructor which should fully initialize a object. + /// For example, if there was SampleDeviceProbeGroup : ProbeGroup, the body of this + /// function could be: + /// + /// return new SampleDeviceProbeGroup(); + /// + /// + /// Returns an object that inherits from + internal abstract ProbeGroup DefaultChannelLayout(); + + internal virtual void LoadDefaultChannelLayout() + { + ChannelConfiguration = DefaultChannelLayout(); + } + + /// + /// After every zoom event, check that the axis liimits are equal to maintain the equal + /// aspect ratio of the graph, ensuring that all contacts do not look smashed or stretched. + /// + /// Incoming object + /// null + /// New state, of type + internal virtual void ZoomEvent(ZedGraphControl sender, ZoomState oldState, ZoomState newState) + { + if (newState.Type == ZoomState.StateType.Zoom || newState.Type == ZoomState.StateType.WheelZoom) + { + SetEqualAxisLimits(sender); + } + } + + private void SetEqualAxisLimits(ZedGraphControl zedGraphControl) + { + var rangeX = zedGraphControl.GraphPane.XAxis.Scale.Max - zedGraphControl.GraphPane.XAxis.Scale.Min; + var rangeY = zedGraphControl.GraphPane.YAxis.Scale.Max - zedGraphControl.GraphPane.YAxis.Scale.Min; + + if (rangeX > rangeY) + { + var diff = rangeX - rangeY; + + zedGraphControl.GraphPane.YAxis.Scale.Max += diff / 2; + zedGraphControl.GraphPane.YAxis.Scale.Min -= diff / 2; + } + else if (rangeX < rangeY) + { + var diff = rangeY - rangeX; + + zedGraphControl.GraphPane.XAxis.Scale.Max += diff / 2; + zedGraphControl.GraphPane.XAxis.Scale.Min -= diff / 2; + } + } + + private void FormShown(object sender, EventArgs e) + { + if (!TopLevel) + { + DisconnectResizeEventHandler(); + + splitContainer1.Panel2Collapsed = true; + splitContainer1.Panel2.Hide(); + + menuStrip.Visible = false; + + ConnectResizeEventHandler(); + ZedGraphChannels_Resize(null, null); + } + else + { + UpdateFontSize(); + zedGraphChannels.Refresh(); + } + } + + internal virtual void OpenFile() where T : ProbeGroup + { + var newConfiguration = OpenAndParseConfigurationFile(); + + if (newConfiguration == null) + { + return; + } + + if (ChannelConfiguration.NumberOfContacts == newConfiguration.NumberOfContacts) + { + newConfiguration.Validate(); + + ChannelConfiguration = newConfiguration; + DrawProbeGroup(); + RefreshZedGraph(); + } + else + { + throw new InvalidOperationException($"Number of contacts does not match; expected {ChannelConfiguration.NumberOfContacts} contacts" + + $", but found {newConfiguration.NumberOfContacts} contacts"); + } + } + + internal T OpenAndParseConfigurationFile() where T : ProbeGroup + { + using OpenFileDialog ofd = new(); + + ofd.Filter = "Probe Interface Files (*.json)|*.json"; + ofd.FilterIndex = 1; + ofd.Multiselect = false; + ofd.Title = "Choose probe interface file"; + + if (ofd.ShowDialog() == DialogResult.OK && File.Exists(ofd.FileName)) + { + var newConfiguration = DesignHelper.DeserializeString(File.ReadAllText(ofd.FileName)); + + return newConfiguration ?? throw new InvalidOperationException($"Unable to open {ofd.FileName}"); + } + + return null; + } + + internal void DrawProbeGroup() + { + zedGraphChannels.GraphPane.GraphObjList.Clear(); + + DrawProbeContour(); + SetEqualAspectRatio(); + DrawContacts(); + HighlightEnabledContacts(); + HighlightSelectedContacts(); + DrawContactLabels(); + DrawScale(); + } + + internal void DrawProbeContour() + { + if (ChannelConfiguration == null) + return; + + foreach (var probe in ChannelConfiguration.Probes) + { + PointD[] planarContours = ConvertFloatArrayToPointD(probe.ProbePlanarContour); + PolyObj contour = new(planarContours, Color.Black, Color.White) + { + ZOrder = ZOrder.C_BehindChartBorder + }; + + zedGraphChannels.GraphPane.GraphObjList.Add(contour); + } + } + + internal void SetEqualAspectRatio() + { + if (zedGraphChannels.GraphPane.GraphObjList.Count == 0) + return; + + var minX = MinX(zedGraphChannels.GraphPane.GraphObjList); + var minY = MinY(zedGraphChannels.GraphPane.GraphObjList); + var maxX = MaxX(zedGraphChannels.GraphPane.GraphObjList); + var maxY = MaxY(zedGraphChannels.GraphPane.GraphObjList); + + var rangeX = maxX - minX; + var rangeY = maxY - minY; + + if (rangeX == rangeY) return; + + if (rangeY < rangeX) + { + var diff = (rangeX - rangeY) / 2; + minY -= diff; + maxY += diff; + } + else + { + var diff = (rangeY - rangeX) / 2; + minX -= diff; + maxX += diff; + } + + var margin = Math.Max(rangeX, rangeY) * 0.05; + + zedGraphChannels.GraphPane.XAxis.Scale.Min = minX - margin; + zedGraphChannels.GraphPane.XAxis.Scale.Max = maxX + margin; + + zedGraphChannels.GraphPane.YAxis.Scale.Min = minY - margin; + zedGraphChannels.GraphPane.YAxis.Scale.Max = maxY + margin; + } + + internal void DrawContacts() + { + if (ChannelConfiguration == null) + return; + + for (int probeNumber = 0; probeNumber < ChannelConfiguration.Probes.Count(); probeNumber++) + { + var probe = ChannelConfiguration.Probes.ElementAt(probeNumber); + + const int borderWidth = 4; + + for (int j = 0; j < probe.ContactPositions.Length; j++) + { + Contact contact = probe.GetContact(j); + + if (contact.Shape.Equals(ContactShape.Circle)) + { + var size = contact.ShapeParams.Radius.Value * 2; + + EllipseObj contactObj = new(contact.PosX - size / 2, contact.PosY + size / 2, size, size) + { + ZOrder = ZOrder.B_BehindLegend, + Tag = ContactTag.GetContactString(probeNumber, contact.Index) + }; + + contactObj.Border.Width = borderWidth; + + zedGraphChannels.GraphPane.GraphObjList.Add(contactObj); + } + else if (contact.Shape.Equals(ContactShape.Square)) + { + var size = contact.ShapeParams.Width.Value; + + BoxObj contactObj = new(contact.PosX - size / 2, contact.PosY + size / 2, size, size) + { + ZOrder = ZOrder.B_BehindLegend, + Tag = ContactTag.GetContactString(probeNumber, contact.Index) + }; + + contactObj.Border.Width = borderWidth; + + zedGraphChannels.GraphPane.GraphObjList.Add(contactObj); + } + else + { + MessageBox.Show("Contact shapes other than 'circle' and 'square' not implemented yet."); + return; + } + } + } + } + + internal readonly Color DisabledContactFill = Color.DarkGray; + internal readonly Color EnabledContactFill = Color.LightYellow; + internal readonly Color ReferenceContactFill = Color.Black; + + internal virtual void HighlightEnabledContacts() + { + if (ChannelConfiguration == null) + return; + + for (int probeNumber = 0; probeNumber < ChannelConfiguration.Probes.Count(); probeNumber++) + { + var probe = ChannelConfiguration.Probes.ElementAt(probeNumber); + + for (int j = 0; j < probe.ContactPositions.Length; j++) + { + Contact contact = probe.GetContact(j); + + var tag = ContactTag.GetContactString(probeNumber, contact.Index); + + if (zedGraphChannels.GraphPane.GraphObjList[tag] is BoxObj graphObj) + { + graphObj.Fill.Color = contact.DeviceId == -1 ? + DisabledContactFill : + (ReferenceContacts.Any(x => x == contact.Index) ? ReferenceContactFill : EnabledContactFill); + } + else + { + throw new NullReferenceException($"Tag {tag} is not found in the graph object list"); + } + } + } + } + + internal readonly Color DeselectedContactBorder = Color.LightGray; + internal readonly Color SelectedContactBorder = Color.YellowGreen; + + internal virtual void HighlightSelectedContacts() + { + if (ChannelConfiguration == null) + return; + + for (int probeNumber = 0; probeNumber < ChannelConfiguration.Probes.Count(); probeNumber++) + { + var probe = ChannelConfiguration.Probes.ElementAt(probeNumber); + var probeOffset = GetProbeIndexOffset(probeNumber); + + for (int j = 0; j < probe.ContactPositions.Length; j++) + { + var tag = ContactTag.GetContactString(probeNumber, probe.GetContact(j).Index); + + if (zedGraphChannels.GraphPane.GraphObjList[tag] is BoxObj graphObj) + { + graphObj.Border.Color = SelectedContacts[probeOffset + j] ? + SelectedContactBorder : + DeselectedContactBorder; ; + } + else + { + throw new NullReferenceException($"Tag {tag} is not found in the graph object list"); + } + } + } + } + + internal virtual void DrawContactLabels() + { + if (ChannelConfiguration == null) + return; + + var fontSize = CalculateFontSize(); + + for (int probeNumber = 0; probeNumber < ChannelConfiguration.Probes.Count(); probeNumber++) + { + var probe = ChannelConfiguration.Probes.ElementAt(probeNumber); + + for (int j = 0; j < probe.ContactPositions.Length; j++) + { + Contact contact = probe.GetContact(j); + bool inactiveContact = contact.DeviceId == -1; + + string id = inactiveContact ? "Off" : ContactString(contact); + + TextObj textObj = new(id, contact.PosX, contact.PosY) + { + ZOrder = ZOrder.A_InFront, + Tag = ContactTag.GetTextString(probeNumber, contact.Index) + }; + + SetTextObj(textObj, fontSize); + + zedGraphChannels.GraphPane.GraphObjList.Add(textObj); + } + } + } + + internal void SetTextObj(TextObj textObj, float fontSize) + { + textObj.FontSpec.IsBold = true; + textObj.FontSpec.Border.IsVisible = false; + textObj.FontSpec.Fill.IsVisible = false; + textObj.FontSpec.Size = fontSize; + } + + internal virtual string ContactString(Contact contact) + { + return contact.Index.ToString(); + } + + internal virtual void DrawScale() + { + } + + internal void UpdateFontSize() + { + var fontSize = CalculateFontSize(); + + foreach (var obj in zedGraphChannels.GraphPane.GraphObjList) + { + if (obj == null) continue; + + if (obj is TextObj textObj) + { + textObj.FontSpec.Size = fontSize; + } + } + } + + internal virtual float CalculateFontSize() + { + float rangeY = (float)(zedGraphChannels.GraphPane.YAxis.Scale.Max - zedGraphChannels.GraphPane.YAxis.Scale.Min); + + float contactSize = ContactSize(); + + var fontSize = 300f * contactSize / rangeY; + + fontSize = fontSize < 1f ? 1f : fontSize; + fontSize = fontSize > 100f ? 100f : fontSize; + + return fontSize; + } + + internal float ContactSize() + { + var obj = zedGraphChannels.GraphPane.GraphObjList + .OfType() + .Where(obj => obj is not PolyObj) + .FirstOrDefault(); + + if (obj != null && obj != default(BoxObj)) + { + return (float)obj.Location.Width; + } + + return 1f; + } + + internal static double MinX(GraphObjList graphObjs) + { + return graphObjs.OfType() + .Min(obj => { return obj.Points.Min(p => p.X); }); + } + + internal static double MinY(GraphObjList graphObjs) + { + return graphObjs.OfType() + .Min(obj => { return obj.Points.Min(p => p.Y); }); + } + + internal static double MaxX(GraphObjList graphObjs) + { + return graphObjs.OfType() + .Max(obj => { return obj.Points.Max(p => p.X); }); + } + + internal static double MaxY(GraphObjList graphObjs) + { + return graphObjs.OfType() + .Max(obj => { return obj.Points.Max(p => p.Y); }); + } + + /// + /// Converts a two-dimensional array into an array of + /// objects. Assumes that the float array is ordered so that the first index of each pair is + /// the X position, and the second index is the Y position. + /// + /// Two-dimensional array of values + /// + public static PointD[] ConvertFloatArrayToPointD(float[][] floats) + { + PointD[] pointD = new PointD[floats.Length]; + + for (int i = 0; i < floats.Length; i++) + { + pointD[i] = new PointD(floats[i][0], floats[i][1]); + } + + return pointD; + } + + /// + /// Initialize the given so that almost everything other than the + /// axis itself is hidden, reducing visual clutter before plotting contacts + /// + public void InitializeZedGraphChannels() + { + zedGraphChannels.GraphPane.Title.IsVisible = false; + zedGraphChannels.GraphPane.TitleGap = 0; + zedGraphChannels.GraphPane.Border.IsVisible = false; + zedGraphChannels.GraphPane.Border.Width = 0; + zedGraphChannels.GraphPane.Chart.Border.IsVisible = false; + zedGraphChannels.GraphPane.Margin.All = -1; + zedGraphChannels.GraphPane.IsFontsScaled = true; + zedGraphChannels.BorderStyle = BorderStyle.None; + + zedGraphChannels.GraphPane.XAxis.IsVisible = false; + zedGraphChannels.GraphPane.XAxis.IsAxisSegmentVisible = false; + zedGraphChannels.GraphPane.XAxis.Scale.MaxAuto = true; + zedGraphChannels.GraphPane.XAxis.Scale.MinAuto = true; + + zedGraphChannels.GraphPane.YAxis.IsVisible = false; + zedGraphChannels.GraphPane.YAxis.IsAxisSegmentVisible = false; + zedGraphChannels.GraphPane.YAxis.Scale.MaxAuto = true; + zedGraphChannels.GraphPane.YAxis.Scale.MinAuto = true; + } + + private void MenuItemSaveFile(object sender, EventArgs e) + { + using SaveFileDialog sfd = new(); + sfd.Filter = "Probe Interface Files (*.json)|*.json"; + sfd.FilterIndex = 1; + sfd.Title = "Choose where to save the probe interface file"; + sfd.OverwritePrompt = true; + sfd.ValidateNames = true; + + if (sfd.ShowDialog() == DialogResult.OK) + { + DesignHelper.SerializeObject(ChannelConfiguration, sfd.FileName); + } + } + + internal void ConnectResizeEventHandler() + { + DisconnectResizeEventHandler(); + zedGraphChannels.Resize += ZedGraphChannels_Resize; + } + + internal void DisconnectResizeEventHandler() + { + zedGraphChannels.Resize -= ZedGraphChannels_Resize; + } + + private void ZedGraphChannels_Resize(object sender, EventArgs e) + { + if (zedGraphChannels.Size.Width == zedGraphChannels.Size.Height && + zedGraphChannels.Size.Height == zedGraphChannels.GraphPane.Rect.Height && + zedGraphChannels.Location.X == zedGraphChannels.GraphPane.Rect.X) + { + if (zedGraphChannels.GraphPane.Chart.Rect != zedGraphChannels.GraphPane.Rect) + { + zedGraphChannels.GraphPane.Chart.Rect = zedGraphChannels.GraphPane.Rect; + } + + return; + } + + ResizeAxes(); + UpdateControlSizeBasedOnAxisSize(); + UpdateFontSize(); + zedGraphChannels.AxisChange(); + zedGraphChannels.Refresh(); + } + + /// + /// After a resize event (such as changing the window size), readjust the size of the control to + /// ensure an equal aspect ratio for axes. + /// + public void ResizeAxes() + { + SetEqualAspectRatio(); + + RectangleF axisRect = zedGraphChannels.GraphPane.Rect; + + if (axisRect.Width > axisRect.Height) + { + axisRect.X += (axisRect.Width - axisRect.Height) / 2; + axisRect.Width = axisRect.Height; + } + else if (axisRect.Height > axisRect.Width) + { + axisRect.Y += (axisRect.Height - axisRect.Width) / 2; + axisRect.Height = axisRect.Width; + } + else + { + zedGraphChannels.GraphPane.Chart.Rect = axisRect; + return; + } + + zedGraphChannels.GraphPane.Rect = axisRect; + zedGraphChannels.GraphPane.Chart.Rect = axisRect; + } + + private void UpdateControlSizeBasedOnAxisSize() + { + RectangleF axisRect = zedGraphChannels.GraphPane.Rect; + + zedGraphChannels.Size = new Size((int)axisRect.Width, (int)axisRect.Height); + zedGraphChannels.Location = new Point((int)axisRect.X, (int)axisRect.Y); + } + + private void MenuItemOpenFile(object sender, EventArgs e) + { + OpenFile(); + DrawProbeGroup(); + RefreshZedGraph(); + } + + private void MenuItemLoadDefaultConfig(object sender, EventArgs e) + { + LoadDefaultChannelLayout(); + DrawProbeGroup(); + RefreshZedGraph(); + } + + private void ButtonOK(object sender, EventArgs e) + { + if (TopLevel) + { + DialogResult = DialogResult.OK; + Close(); + } + } + + internal void ManualZoom(double zoomFactor) + { + var center = new PointF(zedGraphChannels.GraphPane.Rect.Left + zedGraphChannels.GraphPane.Rect.Width / 2, + zedGraphChannels.GraphPane.Rect.Top + zedGraphChannels.GraphPane.Rect.Height / 2); + + zedGraphChannels.ZoomPane(zedGraphChannels.GraphPane, 1 / zoomFactor, center, true); + + UpdateFontSize(); + } + + internal void ResetZoom() + { + SetEqualAspectRatio(); + UpdateFontSize(); + } + + /// + /// Shifts the whole ZedGraph to the given relative position, where 0.0 is the very bottom of the horizontal + /// space, and 1.0 is the very top. Note that this accounts for a buffer on the top and bottom, so giving a + /// value of 0.0 would have the minimum value of Y axis equal to the bottom of the graph, and keep the range + /// the same. Similarly, a value of 1.0 would set the maximum value of the Y axis to the top of the graph, + /// and keep the range the same. + /// + /// A float value defining the percentage of the graph to move to vertically + public void MoveToVerticalPosition(float relativePosition) + { + if (relativePosition < 0.0 || relativePosition > 1.0) + { + throw new ArgumentOutOfRangeException(nameof(relativePosition)); + } + + var currentRange = zedGraphChannels.GraphPane.YAxis.Scale.Max - zedGraphChannels.GraphPane.YAxis.Scale.Min; + + var minY = MinY(zedGraphChannels.GraphPane.GraphObjList); + var maxY = MaxY(zedGraphChannels.GraphPane.GraphObjList); + + var newMinY = (maxY - minY - currentRange) * relativePosition; + + zedGraphChannels.GraphPane.YAxis.Scale.Min = newMinY; + zedGraphChannels.GraphPane.YAxis.Scale.Max = newMinY + currentRange; + } + + internal float GetRelativeVerticalPosition() + { + var minY = MinY(zedGraphChannels.GraphPane.GraphObjList); + var maxY = MaxY(zedGraphChannels.GraphPane.GraphObjList); + + var currentRange = zedGraphChannels.GraphPane.YAxis.Scale.Max - zedGraphChannels.GraphPane.YAxis.Scale.Min; + + if (zedGraphChannels.GraphPane.YAxis.Scale.Min <= minY) + return 0.0f; + else if (zedGraphChannels.GraphPane.YAxis.Scale.Min >= maxY - currentRange) + return 1.0f; + else + { + return (float)((zedGraphChannels.GraphPane.YAxis.Scale.Min - minY) / (maxY - minY - currentRange)); + } + } + + internal void RefreshZedGraph() + { + zedGraphChannels.AxisChange(); + zedGraphChannels.Refresh(); + } + + PointD clickStart = new(0.0, 0.0); + + private bool MouseDownEvent(ZedGraphControl sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + clickStart = TransformPixelsToCoordinates(e.Location, sender.GraphPane); + } + + return false; + } + + const string SelectionAreaTag = "Selection"; + + private bool MouseMoveEvent(ZedGraphControl sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + sender.Cursor = Cursors.Cross; + + if (clickStart.X == default && clickStart.Y == default) + return false; + + BoxObj oldArea = (BoxObj)sender.GraphPane.GraphObjList[SelectionAreaTag]; + if (oldArea != null) + { + sender.GraphPane.GraphObjList.Remove(oldArea); + } + + var mouseLocation = TransformPixelsToCoordinates(e.Location, sender.GraphPane); + + BoxObj selectionArea = new( + mouseLocation.X < clickStart.X ? mouseLocation.X : clickStart.X, + mouseLocation.Y > clickStart.Y ? mouseLocation.Y : clickStart.Y, + Math.Abs(mouseLocation.X - clickStart.X), + Math.Abs(mouseLocation.Y - clickStart.Y)); + selectionArea.Border.Color = Color.DarkSlateGray; + selectionArea.Fill.IsVisible = false; + selectionArea.ZOrder = ZOrder.A_InFront; + selectionArea.Tag = SelectionAreaTag; + + sender.GraphPane.GraphObjList.Add(selectionArea); + sender.Refresh(); + + return true; + } + else if (e.Button == MouseButtons.None) + { + sender.Cursor = Cursors.Arrow; + + return true; + } + + return false; + } + + private bool MouseUpEvent(ZedGraphControl sender, MouseEventArgs e) + { + sender.Cursor = Cursors.Arrow; + + if (e.Button == MouseButtons.Left) + { + if (sender.GraphPane.GraphObjList[SelectionAreaTag] is BoxObj selectionArea && selectionArea != null && ChannelConfiguration != null) + { + RectangleF rect = selectionArea.Location.Rect; + + if (!rect.IsEmpty) + { + for (int i = 0; i < ChannelConfiguration.Probes.Count(); i++) + { + var probe = ChannelConfiguration.Probes.ElementAt(i); + + for (int j = 0; j < probe.NumberOfContacts; j++) + { + if (sender.GraphPane.GraphObjList[ContactTag.GetContactString(i, j)] is BoxObj contact && contact != null) + { + if (Contains(rect, contact.Location)) + { + SetSelectedContact(contact.Tag as string, true); + } + } + } + } + } + + sender.GraphPane.GraphObjList.Remove(selectionArea); + clickStart.X = default; + clickStart.Y = default; + } + else + { + PointF mouseClick = new(e.X, e.Y); + + if (zedGraphChannels.GraphPane.FindNearestObject(mouseClick, CreateGraphics(), out object nearestObject, out int _)) + { + if (nearestObject is TextObj textObj) + { + ToggleSelectedContact(textObj.Tag as string); + } + else if (nearestObject is BoxObj boxObj) + { + ToggleSelectedContact(boxObj.Tag as string); + } + } + else + { + SetAllSelections(false); + } + } + + HighlightSelectedContacts(); + SelectedContactChanged(); + RefreshZedGraph(); + + return true; + } + + return false; + } + + private void ToggleSelectedContact(string tag) + { + SetSelectedContact(tag, !GetContactStatus(tag)); + } + + private void SetSelectedContact(string tag, bool status) + { + ContactTag parsedTag = new(tag); + + var index = GetProbeIndexOffset(parsedTag.ProbeNumber) + parsedTag.ContactNumber; + + SetSelectedContact(index, status); + } + + private void SetSelectedContact(int index, bool status) + { + SelectedContacts[index] = status; + } + + internal virtual void SelectedContactChanged() + { + } + + internal void SetAllSelections(bool newStatus) + { + for (int i = 0; i < SelectedContacts.Length; i++) + { + SetSelectedContact(i, newStatus); + } + } + + private bool GetContactStatus(string tag) + { + ContactTag parsedTag = new(tag); + + var index = GetProbeIndexOffset(parsedTag.ProbeNumber) + parsedTag.ContactNumber; + + return SelectedContacts[index]; + } + + private static PointD TransformPixelsToCoordinates(Point pixels, GraphPane graphPane) + { + graphPane.ReverseTransform(pixels, out double x, out double y); + + return new PointD(x, y); + } + + private bool Contains(RectangleF rect, Location location) + { + if (!rect.IsEmpty) + { + if (location != null) + { + var x = location.X + location.Width / 2; + var y = location.Y - location.Height / 2; + + if (x >= rect.X && x <= rect.X + rect.Width && y <= rect.Y && y >= rect.Y - rect.Height) + { + return true; + } + } + } + + return false; + } + + internal int GetProbeIndexOffset(int currentProbeIndex) + { + int offset = 0; + + for (int i = currentProbeIndex - 1; i >= 0; i--) + { + offset += ChannelConfiguration.Probes.ElementAt(i).NumberOfContacts; + } + + return offset; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.resx b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.resx new file mode 100644 index 00000000..7dd004c7 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 11, 11 + + \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ContactTag.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/ContactTag.cs new file mode 100644 index 00000000..2a7603a2 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/ContactTag.cs @@ -0,0 +1,72 @@ +using System; + +namespace OpenEphys.Onix.Design +{ + public class ContactTag + { + public const string ContactStringFormat = "Probe_{0}-Contact_{1}"; + public const string TextStringFormat = "TextProbe_{0}-Contact_{1}"; + + public int ProbeNumber; + public int ContactNumber; + + public string ContactString => GetContactString(ProbeNumber, ContactNumber); + + public string TextString => GetTextString(ProbeNumber, ContactNumber); + + public ContactTag(int probeNumber, int contactNumber) + { + ProbeNumber = probeNumber; + ContactNumber = contactNumber; + } + + public ContactTag(string tag) + { + ProbeNumber = ParseProbeNumber(tag); + ContactNumber = ParseContactNumber(tag); + } + + public static int ParseProbeNumber(string tag) + { + if (string.IsNullOrEmpty(tag)) + throw new NullReferenceException(nameof(tag)); + + string[] words = tag.Split('-'); + string[] probeStrings = words[0].Split('_'); + + if (!int.TryParse(probeStrings[1], out int probeNumber)) + { + throw new ArgumentException($"Invalid channel tag \"{tag}\" found"); + } + + return probeNumber; + } + + public static int ParseContactNumber(string tag) + { + if (string.IsNullOrEmpty(tag)) + throw new NullReferenceException(nameof(tag)); + + string[] words = tag.Split('-'); + string[] contactStrings = words[1].Split('_'); + + if (!int.TryParse(contactStrings[1], out int contactNumber)) + { + throw new ArgumentException($"Invalid channel tag \"{tag}\" found"); + } + + return contactNumber; + } + + public static string GetContactString(int probeNumber, int contactNumber) + { + return string.Format(ContactStringFormat, probeNumber, contactNumber); + } + + public static string GetTextString(int probeNumber, int contactNumber) + { + return string.Format(TextStringFormat, probeNumber, contactNumber); + } + } + +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs new file mode 100644 index 00000000..c1e4eb58 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Windows.Forms; +using Newtonsoft.Json; + +namespace OpenEphys.Onix.Design +{ + public static class DesignHelper + { + public static T DeserializeString(string channelLayout) + { + return JsonConvert.DeserializeObject(channelLayout); + } + + public static void SerializeObject(object _object, string filepath) + { + var stringJson = JsonConvert.SerializeObject(_object); + + File.WriteAllText(filepath, stringJson); + } + + public static IEnumerable GetAllChildren(this Control root) + { + var stack = new Stack(); + stack.Push(root); + + while (stack.Any()) + { + var next = stack.Pop(); + foreach (Control child in next.Controls) + stack.Push(child); + yield return next; + } + } + + /// + /// Given two forms, take all menu items that are in the "File" MenuItem of the child form, and copy them directly to the + /// "File" MenuItem for the parent form + /// + /// + /// + public static void AddMenuItemsFromDialogToFileOption(this Form thisForm, Form form) + { + const string FileString = "File"; + + if (form != null) + { + var menuStrips = form.GetAllChildren() + .OfType() + .ToList(); + + var thisMenuStrip = thisForm.GetAllChildren() + .OfType() + .FirstOrDefault(); + + ToolStripMenuItem existingMenuItem = null; + + foreach (ToolStripMenuItem menuItem in thisMenuStrip.Items) + { + if (menuItem.Text == FileString) + { + existingMenuItem = menuItem; + } + } + + if (menuStrips != null && menuStrips.Count > 0) + { + foreach (var menuStrip in menuStrips) + { + foreach (ToolStripMenuItem menuItem in menuStrip.Items) + { + if (menuItem.Text == FileString) + { + while (menuItem.DropDownItems.Count > 0) + { + existingMenuItem.DropDownItems.Add(menuItem.DropDownItems[0]); + } + } + } + } + } + } + } + + /// + /// Given two forms, take all menu items that are in the "File" MenuItem of the child form, and copy them to the + /// sub-menu name given, nested under the "File" MenuItem for the parent form + /// + /// + /// + /// + public static void AddMenuItemsFromDialogToFileOption(this Form thisForm, Form form, string subMenuName) + { + const string FileString = "File"; + + if (form != null) + { + var menuStrips = form.GetAllChildren() + .OfType() + .ToList(); + + var thisMenuStrip = thisForm.GetAllChildren() + .OfType() + .FirstOrDefault(); + + ToolStripMenuItem existingMenuItem = null; + + foreach (ToolStripMenuItem menuItem in thisMenuStrip.Items) + { + if (menuItem.Text == FileString) + { + existingMenuItem = menuItem; + } + } + + ToolStripMenuItem newItems = new() + { + Text = subMenuName + }; + + if (menuStrips != null && menuStrips.Count > 0) + { + foreach (var menuStrip in menuStrips) + { + foreach (ToolStripMenuItem menuItem in menuStrip.Items) + { + if (menuItem.Text == FileString) + { + while (menuItem.DropDownItems.Count > 0) + { + newItems.DropDownItems.Add(menuItem.DropDownItems[0]); + } + } + } + } + + existingMenuItem.DropDownItems.Add(newItems); + } + } + } + + /// + /// Convert a ProbeInterface object to a list of electrodes, which includes all possible electrodes + /// + /// A object + /// List of electrodes + public static List ToElectrodes(NeuropixelsV1eProbeGroup channelConfiguration) + { + List electrodes = new(); + + foreach (var c in channelConfiguration.GetContacts()) + { + electrodes.Add(new NeuropixelsV1eElectrode(c)); + } + + return electrodes; + } + + public static void UpdateElectrodes(List electrodes, NeuropixelsV1eProbeGroup channelConfiguration) + { + if (electrodes.Count != channelConfiguration.NumberOfContacts) + { + throw new InvalidOperationException($"Different number of electrodes found in {nameof(electrodes)} versus {nameof(channelConfiguration)}"); + } + + int index = 0; + + foreach (var c in channelConfiguration.GetContacts()) + { + electrodes[index++] = new NeuropixelsV1eElectrode(c); + } + } + + /// + /// Convert a ProbeInterface object to a list of electrodes, which only includes currently enabled electrodes + /// + /// A object + /// List of electrodes that are enabled + public static List ToChannelMap(NeuropixelsV1eProbeGroup channelConfiguration) + { + List channelMap = new(); + + foreach (var c in channelConfiguration.GetContacts().Where(c => c.DeviceId != -1)) + { + channelMap.Add(new NeuropixelsV1eElectrode(c)); + } + + return channelMap.OrderBy(e => e.Channel).ToList(); + } + + public static void UpdateChannelMap(List channelMap, NeuropixelsV1eProbeGroup channelConfiguration) + { + var enabledElectrodes = channelConfiguration.GetContacts() + .Where(c => c.DeviceId != -1); + + if (channelMap.Count != enabledElectrodes.Count()) + { + throw new InvalidOperationException($"Different number of enabled electrodes found in {nameof(channelMap)} versus {nameof(channelConfiguration)}"); + } + + int index = 0; + + foreach (var c in enabledElectrodes) + { + channelMap[index++] = new NeuropixelsV1eElectrode(c); + } + } + + /// + /// Update the currently enabled contacts in the probe group, based on the currently selected contacts in + /// the given channel map. The only operation that occurs is an update of the DeviceChannelIndices field, + /// where -1 indicates the contact is no longer enabled + /// + /// List of objects, which contain the index of the selected contact + /// + public static void UpdateProbeGroup(List channelMap, NeuropixelsV1eProbeGroup probeGroup) + { + int[] deviceChannelIndices = new int[probeGroup.NumberOfContacts]; + + deviceChannelIndices = deviceChannelIndices.Select(i => i = -1).ToArray(); + + foreach (var e in channelMap) + { + deviceChannelIndices[e.ElectrodeNumber] = e.Channel; + } + + probeGroup.UpdateDeviceChannelIndices(0, deviceChannelIndices); + } + + public static List SelectElectrodes(this List channelMap, List electrodes) + { + foreach (var e in electrodes) + { + channelMap[e.Channel] = e; + } + + return channelMap; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.Designer.cs new file mode 100644 index 00000000..e6dbc7e1 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.Designer.cs @@ -0,0 +1,117 @@ +namespace OpenEphys.Onix.Design +{ + partial class GenericDeviceDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.propertyGrid = new System.Windows.Forms.PropertyGrid(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.buttonCancel = new System.Windows.Forms.Button(); + this.buttonOK = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.SuspendLayout(); + // + // propertyGrid + // + this.propertyGrid.Dock = System.Windows.Forms.DockStyle.Fill; + this.propertyGrid.Location = new System.Drawing.Point(0, 0); + this.propertyGrid.Name = "propertyGrid"; + this.propertyGrid.Size = new System.Drawing.Size(378, 532); + this.propertyGrid.TabIndex = 0; + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Right; + this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.propertyGrid); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); + this.splitContainer1.Panel2.Controls.Add(this.buttonOK); + this.splitContainer1.Size = new System.Drawing.Size(378, 594); + this.splitContainer1.SplitterDistance = 532; + this.splitContainer1.TabIndex = 1; + // + // buttonCancel + // + this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonCancel.Location = new System.Drawing.Point(202, 8); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(162, 38); + this.buttonCancel.TabIndex = 6; + this.buttonCancel.Text = "Cancel"; + this.buttonCancel.UseVisualStyleBackColor = true; + // + // buttonOK + // + this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonOK.Location = new System.Drawing.Point(19, 8); + this.buttonOK.Name = "buttonOK"; + this.buttonOK.Size = new System.Drawing.Size(162, 38); + this.buttonOK.TabIndex = 5; + this.buttonOK.Text = "OK"; + this.buttonOK.UseVisualStyleBackColor = true; + this.buttonOK.Click += new System.EventHandler(this.ButtonClick); + // + // GenericDeviceDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(378, 594); + this.Controls.Add(this.splitContainer1); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "GenericDeviceDialog"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "GenericDeviceDialog"; + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + internal System.Windows.Forms.PropertyGrid propertyGrid; + internal System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.Button buttonCancel; + private System.Windows.Forms.Button buttonOK; + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.cs new file mode 100644 index 00000000..77838366 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.cs @@ -0,0 +1,27 @@ +using System.Windows.Forms; + +namespace OpenEphys.Onix.Design +{ + public abstract partial class GenericDeviceDialog : Form + { + public GenericDeviceDialog() + { + InitializeComponent(); + } + + private void ButtonClick(object sender, System.EventArgs e) + { + if (sender is Button button) + { + if (button.Name == nameof(buttonOK)) + { + DialogResult = DialogResult.OK; + } + else if (button.Name == nameof(buttonCancel)) + { + DialogResult = DialogResult.Cancel; + } + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.resx b/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj b/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj index 2f8e34b8..09d53a7c 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj @@ -11,10 +11,13 @@ + + + diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.Designer.cs new file mode 100644 index 00000000..e3d85ae3 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.Designer.cs @@ -0,0 +1,123 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace OpenEphys.Onix.Design.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenEphys.Onix.Design.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap StatusBlockedImage { + get { + object obj = ResourceManager.GetObject("StatusBlockedImage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap StatusCriticalImage { + get { + object obj = ResourceManager.GetObject("StatusCriticalImage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap StatusReadyImage { + get { + object obj = ResourceManager.GetObject("StatusReadyImage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap StatusRefreshImage { + get { + object obj = ResourceManager.GetObject("StatusRefreshImage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap StatusWarningImage { + get { + object obj = ResourceManager.GetObject("StatusWarningImage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap UploadImage { + get { + object obj = ResourceManager.GetObject("UploadImage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.resx b/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.resx new file mode 100644 index 00000000..59d3809d --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.resx @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\StatusBlockedImage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\StatusCriticalImage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\StatusReadyImage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\StatusRefreshImage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\StatusWarningImage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\UploadImage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusBlockedImage.png b/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusBlockedImage.png new file mode 100644 index 0000000000000000000000000000000000000000..3e35e47ec39de3a1a290571722b841580dc83564 GIT binary patch literal 268 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6;>1s;*b z3=DkxK$!8B)5Zfp!F8T4jv*HQ$qwxI_tkncJo@YKN1nI&h|q#l28;ngj2-9eZ*Bkl ze|5(F|NgEL0R|pCD(pebLEKl_7z56}k550Akl}JX|5UL<+a7mAmJ^ozlMXCnlx~wM zl3QlU_{1cK>to`Iav=w{H^-l)J4q~IUm~8u5XO2WVM3e;a|i1d} L`njxgN@xNA*vMMQ literal 0 HcmV?d00001 diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusCriticalImage.png b/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusCriticalImage.png new file mode 100644 index 0000000000000000000000000000000000000000..4762e0feb7e5d09d680c8afff79f6a21df83ea8e GIT binary patch literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6;>1s;*b z3=DkxL735kHCP2Gc*WDjF~q`uYw$tdLk2u&1b7Z`^c-OFVd$@!_`_IQ-pXm&gWw5G z6;Ilm^lwa^9N`gAcxh#CZFz3g@vR=3m5&_G@CJU?dF^;s)zIeLa+^H!pPLFq1D*M$ zGH!ljoBgvFzY=Za$3-0hDA4NlB+ z<>sP*Y_@-+)TPV)Q%^dEz#tDnm{r-UW|63%p| literal 0 HcmV?d00001 diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusReadyImage.png b/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusReadyImage.png new file mode 100644 index 0000000000000000000000000000000000000000..cbe7c2eb5064543f9eb9f2ee746779d4923a85ee GIT binary patch literal 309 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6;>1s;*b z3=DkxK$!8B)5Zfp!Rwwbjv*HQbAxyC9yZ{)qH*&ecP0z}gF{QhKPata;^24?rM#m! z`#@_{>+}h2a<>YL4u~pMy4z0C6m;6);@-n5xH%@Qd6M~}`Oh@BeK#{po-AniB%nEJ zTJ3>HTdPZ^Zg}!RCSg;W+LjL<8BD!7k6d#$nywAFvD=R6^tLyKc73glm(R@+a$;~3 zYG#X)NI9yy^_^43eyNPRt*achEMD`iVq5uQjkN~bRn;y5i&yjFlD_tA;(oB=_rD$+ z?tiC5xHda7Wqg!-s(pDypV0xe|Fz3Rru=zmE&Z9vd20l(X~DE!pcffDUHx3vIVCg! E0LrO&?EnA( literal 0 HcmV?d00001 diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusRefreshImage.png b/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusRefreshImage.png new file mode 100644 index 0000000000000000000000000000000000000000..6729e542dba81fe3a757ac54eea5006cd3f24364 GIT binary patch literal 13423 zcmeHtbzGF)*7ndycT1OpbPSAir*tbYz|cKIcbAArcPQQ6At4|LNGOPelF}j4@D4uD zd5-6M&v)MM{C?m2-abt_AU=cX4yJvUPyc!@XUg^iVHbD*(Xjb7!XUOC~XA*(G~Y6Y0ap zT#=tJ_j^W8l{6Nn_;a|g9_<{!;VMkY2Fyszd`Me!w}<;1=b)Pz&?Q@u7Hs5v&D5dx zd@S)|F>v9g4fJ{GGWX1T{QBnN_&Q>vu*ah1w(=Zy{b_s8H|TQ0J0$=|XJaGo@RE8L zhGKe4u?c&XN9mnYF)q2wuW@i1S55h|y#my16u<@9xjZ|+T0jq=yQVa)E*c*QypkaE zaAw&f?pS&gp4u&b_9!^d^fYXY*gCNDQ~f&_dENE35s><*+21E{bnAQyxHSGnqT?n~ zQ@X2KqT}GwYJGpOyK8T933OY_dE>gl)4hLnx!t!bk$QaMJ61L>a#>Y1IGVCPUeJ|d zKHlO|v-UFTj(jxd)RMdD9X(L@$m550B$f zvYApn_bQis2Rb^kUf2$~U5w~b^k;KRC^x+^l?c2^e2PM*+Vnmd?WgA#26;ESrI&PQ z{P5-8{*^dE{g2jwrYK)F))izjFMhoJI^WpS0g<1z=DA~%Gm)DZtS%4E2Oj$4Fj!=5 zxjTz%XFc6c?B)(Nq+leJN1JMxsMSTCJ;){E5TDjoVjXD=h3R1Mg&AePjRjIF8+xsj>E*w)s5_{YPK>6CaDOO;0_(ZOZVi zGQ1rb?{*SAg$g>SOop$`+xaylRWoEQGY?xCj4DJ9#6Ap?@txbLi)to_ z)~oW3xz$mSJ)1v=e`($gFx&&Ri}Bu6@2qz}JRO4HlBf7uwguLF32dAa)HZ#P6~= zch>xt2F^ch*FP=WM-Sjf$!_ME+%qy~!rf`kGJGmLSg+aBzJ#lh;op;n|tj3^-c_LSo~Ke|=5!OHVhGQ|_Gr~czn^^ro`(X*e{z1r4?if55L z(`G+#Z5DBU5#ehWJ}y$0oxrP-ua9`a*1@})0+8#H2T&2{!L zm-DxZY%IoRQ0$^C`mW=#+bg3I^+P47HzuV_ZARA5GDy=mpB|JfXW>PY2OXI|6snAT znUBNCP1)&8dDM9#`$-Sfy3!Rgea#>02oG>I^kd=B9`$At_%QA|p*@ z1gU{v^1UJE;OtdXC* z)5-GhX356sAlXsWS$Z9!N5kPITOvh0dO{V;OoskDM>n{hVK^8so`Wps8tXqzSc$bC zIw1@AGOQ0+8M#Ps&0@9T{xG+uGp@Ga%HKWWEQTrK_%EZM4|}(Wb6Qu>s<=YNiDAEqDG5i-(I?=kbdp83EK@ZpW4Pj9jEFbmX2-!;r7c9fc+6{V~NRQqQxN z6<6)El96t?9`XoWnPkeH3f6Ftd+>8%gPpIQXmgu#G@rf=%M=|5fwPEk?V*9^NH92J zC^cxe0krlkAwKKhtmX;r+tVQK6wwAX5IP*EJv90Zi~%(02NS*C3K=)OgA}xlA*qp| zoeJ-Xa5LRG&i809pC9wcIc@<(YNQ^h7i8y?zB?nMrZNhBMX$o3Bdh2{z-&cV9QsDm z)oXY~179<^DV;jl{em$JC4o7242rT~PZW4I9I_RREgzyGXqDwJOBjU8FwQ^qo+PKX zIGVw@pguN2z|)QQ`lo@O>PDsVbg~P69Ko!58?O^ZO%FgA-H#CGz$K0Whi85hOO8NO zk)3UqALoFDA)atGB>pIk5uoa_ACbz*Qy|^uXHl&s`gvN)E0{*?AiWoSP;@`()x&%N z{ZqctMT_AFLE&98RV4|TAiU`xZFZE)++KnYw}~2(k%l?2vK#|Wca9IC_-`8B>+q{*Y7qXlbtB`1#5sZy>F-ucaq5dS2sSy@btUY13qAE zX5!4$TofXS+)%@L(CU$#TS#4wKO0PL{tBh;mWNmL%x_39{4=#;9WA5`6&j-eFDiX-YsJ-w~C6IaeI_z z6}jSpjFKso!nvJbgjwMY*1KNzs(ZdviUT`GSFQu0uoBv$sH;Q8E$jIXH286uvLSn7 z#h9CA7BT%G^CPd$xMwnY7EFetZ`iT8Xb2On+= zfjDv{|6uM}xv3`^Wl@xbj6a6z>A}Y;NOb+D+N0y)5-kDKoxb+4@l@JV$@?y(mfpP# zalImkSZ1LV36uh%Nbg^S*-&qlh$N{bP%YCR)dnxI6c3I%peLb4QZ)iOS{gg!wd7YH z_tI(hnN3hxGK0`F#6vlwRL3`PQ^(ivb4TdJ*~%=Q72@K|c>u>!^bThG%nMrMLov}p z9(0URE&-K=$?r|Xs(_u|NU=R2q>~%6A*SP7N!DeXU14o!|I{M^Ijl^|!sJSr>iX=* z?}H~%dtVYO_T3;a#kXKg6Gmk-j~(PI_otHy=y`=C1N-{)&0bEUmxD}Kn=;jfWZZAa zDsKvAwHDbmF)_X=qf(}7d0Y*@DQ>X$A(^Xrg>K)s@Uk=zzt_ZnA0;0RP9lJpJBn)4 z%bUgr!8W$C=;Xk~y$vI0_-4lYm@9>O=&|jp zs{LRGBqsrUwDR?Jj)P8Lzh&&`_A}mrDa?p4!(8H0H${L3q6&(fQ<}@sN+{h%8{f6zUay-_X4p~H&VP5Q#Zwohi(6lYQY&rp<9S;IxgfKW=wS_a3 zaaVvZ3-M5VPce@&4y6jIt}51k7GR^>*}vk%%KWq>o(Gl{(+Gl|MX}=C+im18kUfS` zvRuLF(I1`9eof1OOkliuyY+B4exgKW z?GU*JLiCE`rH|ovNkM+o8fO)ddBINIlkiRaUZ#|%;(8I3Li4S8NQcA&64Mk<9yDfC z=+i&vtqh7`(P0EK+3BQdo?VB|cs)0t?HtX->oDH!I}#uy`#2!@Ry*iv7FQoj3tC-^ zcLj}p_MC4G?lmSp|63e1!i=}^B>cIzb%N{a^A<{E&Z@mV_hVQZg3*eu$39>}C%Mw6 zLP4)Me@vs^d&qU|mV&*kpsiG(G{OD>*VFqaUrwjf-ea*#2fxcqAFbYhGolf5nuetEUu7B%C|qTugKOQdCtfo75geWZc*YdGK&1640u%9m?_f zkXs|VO%yZ&=}ZwlNdF`(JzmE_HppAR`hxa*lZrKjV+c3{EDz9~3suaQ&@p?soy-_5 zyv3$eqjIoO1&=aZd3Et@>3yfBRO~UE6}ia=d@nZ5N6a@>XXcsRkPw|XOdD^)BV}xL zct9CXP{mJSympQ7d<`YE#>qI}MqL?0U?OjoWu<&`zeJuj?b}1<=H3);013SzAsk~y zH{~ZPVS`g#(=Zc@9(^TUo_H@Vx<=E*b z|De^&h}x{b5LdCS^a8}4doWCiDYGa6;!)6vQxt&euThk@BGHtWD%Zw3f_x}@pL@#k zsSmL6kn#e$E^?A2WRlkF=F%UkO5)zLkmEzP@k|=%ykbBl_1E=|G9<-HqBL>kQpq6o ztG(~VJs2=h+Hp^!1e-T(Hb~-?x2QJu<_+$M9@XPChqMgkN$Vp=HZP1Y;GmC%`>mbV#cvU6r|jU6P1QJ`;x z+mUu}$#}50Abmy|W!m^@Re|w4Rsvy73yhdVnYeQ%=X}?#bi8To0EGxeETerEOS}f< zMbEY-oD?05x|@=!XsHg}AA>&qY;zebWa)#E!xtKMqf?2qMq{CuG@!_C+hEll;>pG6 z>il%swuuIw+;7}ksZPLKc1cHw*L|wu2S0+)+#1uYTg2abMKxArcpb4{4XO#IOH%h9 zKg}<{>ZNP3FBdj{EH1z;Ej=npNq~|qpwgpo!m@ZU*Lr&7aCBJmPFQAcDIv;*m4vg4 z;A^2#pVY&lBdEF~ByuA=%=Cd0FfX@~F=#L_sS1-eSd zwy*B@$futj_jHxM@)A6e$u93A2gprG^F z+uEsPs(>9lr9P0UkV)D)YTpM+;uNqk;QgW3Mj}G`qXlXDXEB3ShVq*N+7TbDONkw# z8OX;iAW;S8423ccYOgG?m+oBmqD=;p-gClUKK#S9zC2`;qLumK8}{ zRQ&nq{b)u84x{woAMHwVmou3{36UDji~tof$R3kxT7Q!l2M;R}vNhSiET=`p4&9+rL+D5azHrLIvx zua>U&J6}_NCs#m12pd6?;aaPed5C5uZ(voO@O+FHr87i zp572zlb958Hh$_qH;C2Df=AGnMslILK1e*1S9||E|E7+@J&L8LTHuFMdXRk%t+p%j z2fE)q1Bc?aSQVZkDLS5llfhfrY>BJN) z*{6lqQ+>=q1KE|)cq~zi;cfF@%I3^JEX)UR4-Rl0f8=(0FA$7&Fu&99oQwN3I4)tx z#{}4iY9PIBQY?~|TR*AKf`XN=6WtkZhB11P>H88&D(Z{eh8cqbk?g}4{J^iprPt3V zjdX%#uq1PziAnXIe#2xg?~TEW6>bNDm!bB$N(uH$y1ZwlR+ zWs${R=^IUqAt7#UpEjhsfBQpWLt^G4{$7lrj>mmxjq#3!`OynaNBbWeLbx1SDl9AX zmL#`4NWJvSu^bG|CdF<+^RFc$zu?G4QkaF@Yx`V~Eb!Vdm{8D2)@u4yU75wr2RR{m znKAS^r%v-oy7y+wcxFtpU|^6bxznYP$=5uq2vt$1(%vJbnsxddp<+R)X2na-1i9Xl zXiN1a^4Ec6C0@DMQ|4#+eJ{cyJnlKrx^_0#!0ZoAJ4cF*#$7>A6n9L8r#0*_ihFjp zY*z}Z{0^k~IFedj2BlS6s(-OXa-5mklgK~1*Bz(7J|F9Bn z*fD4nrTbz#V#X-C-9tsYDr1IzuR=zFN_OK#xYV}w6M>kqy!)iZiX~e@TUU0Yhch2m zSi1O@X{(c_Ls3%VQ36ss)y9bM!1O=v;}Hc2->1X4Pmg4um*)asA_9gI*y6rFGt!eq zR?;*p*Z*+dH2*Q7yy6qk`^|keO~=N<>Ne7Zg-O`FQdkp5?!--{9qG(FaNxc+170yc z*grJaACxb?R>F?dE+}AM&1$OBC0I`P$pA&~{<48i$ms`f3Ti3+bn2W#tY^*%j8-SE z)np{N{G79{BXp6EvF?>_v8S{J;(rLwYm8y12!_0(k|{$eF;*^9U71p8wtsh&TTf_N ze&J5QQYs{URKL}_IZ$`G{Kb$vG9vDHmS)`tvS#mvqf-eY@xUFKiL zP}v8T6Vq-?2x++#!{fWImnHu>^wCSC8DBT`B>&1WxG#qLa)^x4EF}ZKypcE2fa-Dl zaj8ME+s7ALnDUkIE22Fhug-emz3gQQjyDmMMmrb|7zbIFX06H@i4x`nhnTgF zE4VBz=v|b}ld(5k!Gbxeuhg$w6qs8!;e^0vu6~#;L&oLLt+yyxxRAq5!J58%b42YY zZonz^DThhTU55Bfm-$^U{sys=#Mj)czJUL~aXi|&_Fp<-B1IE36jR3*OtX|Syw z@#!XKo=#_S(yCmftl!7wDN6T(X1%>pLK zZ*JJES1O9G@7}`hsmjNTN2t0i9rHkStVAY!z~_TkVVZDJR@ktti%~hPbJTPHPK>jz zvh~cgFRumR>_FyXB7gGhEq8i3Qv?5V0rsTT79Gy!CrF`$tQBOd(k7_IR1?E?qcX0~ z-QVG_K2H`t!1N{ZODzo`+u-w_O^T`~4wIE}B42c({F0zDX!W%E6RK~{xqrc!-g1P) z7CUTkT`_)&=g)p@pgNU}^$1@(m0jigWJO~8 z!gsZl35v>A3cpRT4?lgYXA6NlRa9wNWzcCmzh~wqm>GR>^V(Zhy#?w+H zSeQBN%ZPa4LTV;MkDr5XM0bO%agtlD)xjROL~b*7$=_iCd~&+h9cBExN*+|@2D!%H zEf=5j_8%0~3f9SONlgTO3^I{!)!im_XSpr)3w`OQ7>8M3ONt&S=3q41=Xk*I#x9f^ zTZ7U#I=Wom7w;?~^JCa#RDP1Ng0QK37lnqfoU{0sV}i3&pkrLN)=ahm@Hpk1PGd20GXuX`P0j<9ij}u*d@KT)-4Lu7t(T6`x(YKirMCD zRYjI3Wi7LuvSnX3%41Ajs;l-e_D^*T$9f7S*eYZ3T!Qdr8yzLQ9rTSYXTkjylBuM7 zO75g5^x7@-r7r|>3+jnhF00GdRf@* zu^E*ZNF85+HWYMaHIMbqHs$=mY0LHHg{>Ex&z=EPc)ngN#YUOi&OL7ErhN1wb_RPd z##~T!$oE<3T~UZ8L`cO#iq z%lgUMpvxM67CqXcIaoD0>eOWQBm#;X4`FU+rMDc2mRM@$Z;?*nJg5R+pZL8~20sY> zUQL$4%b?V3TG*x{n`t-VbDMa$KZn9wyOtSuApC8c+u9;4%t}reb0A=vrG% zH4`*=-qd?;w7WKI{)m^qzq-ndK`)~&jp7(2;p9&(kZP#JrT(5fl+7q?NMq#~ONl z{3PbCy)fXF;<=Pds)fah;>UPMGld)L73yU*H7T-B9BF8%6*V3`h|@-Y$n*)Cx+I)R zL*`{QJ5;n0-Pdnk8oS121(jjRVSElOlRuXpgN7fISvMM4@A@8*qho(oj@WL9Na!JY zsVEip3=Aykm44HVq%*?1;TJ~&?XkA)SWOLxdFKC9< zRoKabPx&v3M0vche^j@^>~mLsAPr4cq&NqK%9u6C&TZ4~?``MIcZ=CK_mwoYP<`|aI~ z^tP5_jQWCVJZdg7P#arCA2+DB&tn}6AA1WCOGXKCEKx5og1`|9htPXDIyk|=USf>D zc)^I{yKZhq`d<*Zy%?i`ng+d$vm2CNfJ=ajhf~hW)`O2x9E)Dm&C&|2B`g0Y1)?R! zXak44fVsInJw3TR`MI3kthsqbL`1lG__+D_I1va=n70!g;>GC%W4fdGgF_Yyvv9L@ zf!jJe(cf`G%$?ogVvLN4ar!^W5u+AzUWm>=9o)5l(Zk@D+)9Xs0AfA}05=~Gk1!_> zA19v(_wV+IQ8l%{tes$gs)*2&+Y92t&CA8Z?dbRq7BIM+$KU<^Qwx|5;@%T(Ehx;{ z-OU0j=K*zsGyU$=#lao+yH9r*^sehy+zysj+=!rldH&r-K}k*HFPl3Vt!*7$ep%e1 ze@9wc{DpIIcXRlKv9#cZIzSx}fxr;Vy#IiQ+gkm7f&O7WcO(BT5QMwG`2PX@H@$wv z@=I5+th0stol_-QF~+-i!IsVzwwB;uhfrZ2bABF60ZxdO2!vCBM?i>E1PX$1Lai*U z%s~PWh>)P=Z&XT7FgV1?0(wVN6I;^Y(JK~V8SK!~^mggFIy1rR}6 zBAoF45zXB?fTc8)#2ERwcz%)p>d|n3z^$C!91-PZ>tyNd3H$qyj;$k98xFaXjaP_A zL`Z;#7X%UpAsq7m9i#_!gCWxVj+K{(i;wTuh@}Ns9>EDgq?@fH#2U)&;$;16;BH~S zi0L4th1^vKg85fJVm4qIHz)+|?55-F>>$Q?7aRQ@<*%Zm7yYwbz^cv`zYKqYp_X?Q z_h-#XL#(-fHAT7q75Kj~Y1=q^I{m-#{2lriiKZY!9AWqg$#D4o{vHh*z_MarO091faP}q_OQ32+BoB|L*#1X`tpHslxiqBlc zQUGym@oRzqVuv|f!95{vP$_Ff4j|SUQCz>)nV$X6{Cn_^cuyPXU2^g8@pAGYUVJ*d z!eCw@FfWMlkJY9Z<-Xft|2A9EyFExv4g9+vqIY`~Sm`biwcTA@9BiR(|ESa7^5*}7 z`_2B(9Q~ine~0~HE#vIsji_Q9xVop)zjXgkfPXNk+FC%JV9x(4^xq+W$nx8X2odui zeTWkr;(W>d*9r5_EV(PB|Ha3j+4sNb0YUvAC;yhd|B>rIa{XHh{9EAvWY>S>`nMGL zx4{3&uK#axVg3D*8|sAk-RFt8pmwqxu|ZsnqnoQJ$O3Nfo_U?ciHM$iE{cXQ006K5 z?v0epfk%z##DFWQ$ziM^p&%0wMg&MA0{|$dO0rTq!XxjK##Q*dX$F@}iz(|HH0?F9 z)i#xEa`5CoJ#&kr!U3ZzDHwV^$?232?3sje_U*KOFgZCnm(t5BJUl#Aetv#+oIVXU zHnz~_=4KnAOaP#KCMG{W|L5Dc5mY$n&JIHJzM*ekWM*aQTn3%{EuNg6c{R4TKYGGW zBB@DYX&BM>wdde~2aq>oCq*L1gA?i^T;c5KC>9qVUv2y_<;f>_9(EFxa@T{p?%~S% zIyzn?${RBRn!y>Hj4otJn~a~y$}9>(nR24EY#BxA1s^ss} z_rnNgcRgAb_V|mZ&R4d!w%ki~V(x|1IPp-d^QVt`4qLudRafu+>Yan(f(nS>IygU1 z-)s}_aiw8Qz$7ChbMf?y^ywVaCON?ah|-P^HZ}?dK%r~nlJr3y?Qv66Q&Mu8cw>&t<+eb%7VFm0|BX!QyaS>*G>9pt=7`I*`O3zos?}hMexV6$0uz%axsR#@d zP~(p&IMc45d$!dkesplqv)v|s(da)vKR?37#kHlat$i^z_I%MgZP+qEQ}+ZT0#ap| z!UBWAy07<%h$l}^3nO1L58&bBV?2+K_Yar;x?x^RaYyPMUc&EQT5`4#plbHcNquj{j|fq# zblQ(EUA0S~QU_QI2WP7mY8}rxkm2;vlVA-Cs5y6gJb3kz8T#||bm{Gi>$ejhLQK@< zLoX*?-S;SfKAtYO)}0}%`j)P)x9jUgv~lvce3|c6#B=vy_%$Zg_zgytl8r{!a5&sj znVIPI)c2k>w>Rds@XKe2a3UZ*#~rlMvN^1*tUiyctyKriT_vX)Bwu|vdogUeh(PDK zm>H5iE+)>`P&6?z0?s-#orb;0`n0`^ADLQodU{$>R@PC#E_OKJN^=z;VUw{~#16D= ip3l6Et9I_bLF?`gCqBdmbRen^pd|NLwo2M8EX>4Tx04R}tkv&MmKpe$iQ%glEf@TnL$WWauh>AE$6^me@v=v%)FuC*#ni!H4 z7e~Rh;NZt%)xpJCR|i)?5c~jfb8}L3krMxx6k5c1aNLh~_a1le0HIN3n$4LokXvz#@`JL4txBDyYInj8>f#3u)Sq`}l`ke~MfRxhi1f zn8yY*$gUs!4}N!R6(=XWq(~eHy*SRt2oT-{nsvwdK6aeu2@re+uJpElqXEo(l3s6X z(IcR58@RY`YswyQxdRM78L}z6Qjn%lECTOm^i6qS;1=jv^?GaVD00(qQO+^Rg1p*W&A!Rqt{Qv*~@JU2LR5;7slsjuwK@@<$nLBgu zK5p3Tl1(rOlE%h=U?;J#&}s#LKmQMpN>xSsUG=Yvt*RG9(s*%74 zh$$XJ0DrKBZ4AFN)G&K-N^CrXnhMcV5vE)td$}kR0kh3}V)J#@x5}tuk1QrfP{x1$MqEDAmcH9hbiXW^32QC=Y1nDjpF*i>F=@z+jUW`Np!0 z4OAa*;YG zmXSc+S_NGI6y8Irs<0+uVO9VK(wnxRi2i(g_#CFsPl$1cpe!g55rP1ix8Mc;CI7`i zz-74d0^ylAYQQl4v@9ms24#+((F(8uCoe{vUMiyFfSzL4MTh8ilYHmN%>l#gVNrr` znr`t_4=5xZP4QBdFz}?0_wfgv{=}q%_a1w`rH%~qw@VUe+XsVg0}7F6`_6ZE@9smA zgZ%dMltH^6WFKw_fB}&9JLv3zPX~(ZQ8R1_>AHc+rI zf)p!?(o~Q^5EQJ4LsLP;0py;5qxjwV=6>@$-~A^%3FoZ+u6M2d?zQ&W=ONwQ&0ZO@ z3<3gylpP&xJb}Mzk`GuO_}-bWtqcM|@Uh;0LQjea%HwkxEH)h~jN#FtbTNwo0*SjH z`vvk}5LBjK8GZoEI;g&goXFV!)6iQ}t5b&~F7`Z6@9HWmcQ$ggO~^}^H87o-?&QzZ ztx>yYvc2Ei^PRYUv7+w~EiO1JE@#)|*yi+3Vk~)NL|-#iy7KAfJFja#M;{!KIp|t{ z*i3oPWK_}R!v>wogsaNJ#|C~j8HXa3z0V{_7c9959?Z*KuQxcR)@{}^_)$TAYVG8J zVAt^Lnx4sz#g<&KihREoPqSe8gskdNK~Qm5_H<)a*1BuvCUs%pSX*gJ^SVY0+n2rm z)uV0txBDCxyk0IMyn!%kxE5w^?@$vQ>nY)@)=|+`;+1ZhzOby}xU%fW0b?y^Zpk{d z<4StGKj@gS;nkb5P=PJCsA?kZG;+@NpGwoVO>b1pY&W%sD(hurUH4PCEM@;mM{#&EcT26CmZ?tinmnYYRx`(Idf_Dt zZ6X<88!Hr_7bBvUWcl+1Y0> z9PU4{JS8jvyui?)*zic=X)rZuE0)15cFHn(t$tZwtc--gFh0qt)NcA5%8GRi_10xsM3dy zew{74zVYbhf{jhndr-O{B3}FUgMIhb+-Ofo+uNE!t!R9fGF0GTx-4mp*S%sHj3~m= zA|D z>7BEg`gE*^n4%wepe{+4qmS^4e3(5*lDw>A} zzUPG*pNbr^y1PK!KI2_KnvbealiYVyY7|$njWTw}-&(x5|4J4(jV&8>|0mg< zJ4!cOLYIV}ZSlh0%4^eBU$HSst#TFT)Z0zZEo?5C+wl)k^Sujm~%h101wj63mST%la4J5ZH z^fdX?(Xz~lKlYuJt{*N@O7&BTZK$|D^0ZS666C{nXsLd7pnF=;~Ct5 zvz+-WXYTuBr>xJj@Y6jnBC=x;c-GNveCmhC4?08QD9uY?zxbE-bwl#~qooGvSln~N zG;p`E(&R0v0nPWiciZqw46?J{KH{eucdqL4+O@XZspi-04l^wcnhV@EqfaJncRG4+ z?(qP1EG?m}2bG+_KpoxLgemGd%^Z$YHVL_78CPe~BRsGzS!|SZ^Td(!*B7s`OFE+n zAq6iWW*on6(_H0r$Eo=BR#?AY>)M3UXIVpQ{rcB5w0#g6--cRW$mnfm%&-ppVL837 zjj8(}b9p$tCM8;BM`c(U zB^zt6?zvv}(Q~QYZ^TxG=$o8g6uN5#D`_%BzGUpC@)T2x=$^N43q^-%#75+$>2eCA z{FaTgVW&ZFPE=MNiJv)v$$EKT!)OtSx>e47fiL?z1MRaptQ&7XM7 z2(AkDRSuWPMT7c656Sd_s;?zs#COdg9%hrnF~u5BGRg z@<#~V{f|A|Dp$jTn?9M}Z@cz6`|t{Wv_<=v_1%%M*sTo@r(NzXB-R$Z{&YTSLm_)w z%}MF!mX$7qEfpPdg-R|YNc1iX%z*vG_PvV&+BHSwp-VLbb^KcAVq3u}K{wwVKNalk zz$)a~k0-69c2o^_<*f*p;#nB)8SybZIn}%SPEJqBjIXkNoM0@h>emAg3Nv;`)~21k zx2FCNv#Q^t*(Z~G$g5AK_`SW+r2cZFXiP@#5~SO^LT_>AuHbX3cVpxala0KNJ~i8Z zAQ&2`Xvth_4_S2Cbb(Q}lA)sW$^18yD6iw0bn%JI3f-mKsu0}98jFz!SGqoHDBRC{ zAAF;HsI*1uLRm|r_TX~nqFT4{oX)Z2p7!KH|3qTlG^;N4BCjtmx$BwW;su}94J*_L zn>^ou`^^fwo?1FAz3cYM&X+s&sqB%6C*CIIJUKliuP9%t9mKh#(_6Z&0@LHD{(E*C zD=+}x0Qtq!hN@=f6CUGwnOu8bNV*qjocHMNiRm|XXN_xo8ZHLCYu3Rv>~;D?-h1Jo zH{aJJ3YqNEO-vq%?eJ0+naEA>E+4wV4{<3aYT3EX6|Z-l zdvs$AF*JInlhsaoe%>4ttE7nT}0r7vH= z3ehnJm;2pnG`-X`y<}wKDmE*IozU=EM*m39`>Y913J4^X#Im+_ceJ+t^8^W;8xL;D zCOh1)TwcGy%h}c_k)ayd;C{$BZ9Bn9eMiWV3+e$;$m(&$e9as+4g2dAPoCTk?YLg= z@2lWzr8)(Q6bL+p*^6iPsRi5=^B(OIebf<+f{u95^E+@V%$kT3OCk;TZOE=W3$}_+ z+bT#quah+X;&mlEMR`;&SiPWID?Lm-gqmUQ-Z_8sF&a9YP zxK$LcuCIUTn8R}LWy0OtVAWo^{HWWX%%czXFHqeqe`dTg?xad$CgKPlaNq}l%q+w_3N@TA zgoe_?SR68JwDu|t%A%2BzBpH;E60YsJ-qhG|Dv<`WFo&3lNdQ3vT}Xk7 zBiI}PNlb>#@{)jSi5UTd&O(IYWSF0;JJg!Xr$ezOSQ8}NR?Lb7VN9WrM?xTgS(u10CR~0P0!1Ve5lA!wjfMjVxFCijq=?}hfv$vNj>Cp7pz>Kf zA&bj_N;oN@+$bR#1_Q>SU&w(`s;w9>ehDDCpQRTFX$VK)1`Esw03gsvBms^@!_h>< ze1Bln)%B}4NAN{OKu?62!b6};kcfzg?>q!T+vsoc{?bF>4ZK$&Jm~^%6rW1Bjiz&i zy7Pnb*inM{F{1=@iD@=(HjRM*lA4V?-^bq3)%~lFM8+^y1aHqz%WJQ={On^JCDkdBM?$JRJw!; zAU9zFJTwZ8fu-V*a3UImgk!OIJe)|yQ{Yqr8i&N0h7wFslz9~E`7EFlDD3%JNvLQ5 z6^4N{#nVG^aFi)fCs-m11*gz}D-4DJq(UGd(YU#6Bz>Jn<8Xz z`4K>Su{boYNbqgQn-xL#5>h0xq3}S}6YxYl28BVQh?s96A39$El(~czg)~8eYbUX%wrDCZxJmY(I0hb{ZQTTMLFrWs2bq1PicAcRHU+T~Bd%TEAmlPKg4aC3! z`=B=pLqelTSPX1#wV`GR$qxJHY|SKlkgF?cz8+?hJ&NQgDMYU*9*@nU^S{^W&${_v zaP$1X)aZY5pAVb!w&wC;fG%bV*NZrR8~!hVa||vlDxD+X{$1+xA#<|Kdk_IR&)I+n z9PqqEe0^YksS-&e{TF|}RNsFw0zmyE$UoBehg?78`bP@DUY=Ib+o_JNFg3S}9LE znjKD12=6gZJsP?mJV;q7rB1#1Qgii2{nSOkKQL>t=IiT}a};0H + + + + diff --git a/OpenEphys.Onix/OpenEphys.ProbeInterface b/OpenEphys.Onix/OpenEphys.ProbeInterface new file mode 160000 index 00000000..e6c7163e --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.ProbeInterface @@ -0,0 +1 @@ +Subproject commit e6c7163e79348f8fd7578c379ff9bd84bd9233ce From df5eb6fa040c7f8c9d2246268f0cbcb76c8da427 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Wed, 17 Jul 2024 09:20:24 -0400 Subject: [PATCH 2/8] Add NeuropixelsV2e GUI --- .../ChannelConfigurationDialog.cs | 218 +++-- .../OpenEphys.Onix.Design/DesignHelper.cs | 90 +- ...sV2eChannelConfigurationDialog.Designer.cs | 39 + ...europixelsV2eChannelConfigurationDialog.cs | 207 +++++ .../NeuropixelsV2eDialog.Designer.cs | 737 ++++++++++++++++ .../NeuropixelsV2eDialog.cs | 784 ++++++++++++++++++ .../NeuropixelsV2eDialog.resx | 171 ++++ .../NeuropixelsV2eEditor.cs | 35 + .../OpenEphys.Onix.Design.csproj | 17 +- .../OpenEphys.Onix/ConfigureNeuropixelsV2e.cs | 17 +- OpenEphys.Onix/OpenEphys.Onix/Electrode.cs | 38 + .../OpenEphys.Onix/NeuropixelsV2.cs | 1 + .../OpenEphys.Onix/NeuropixelsV2Helper.cs | 28 + .../NeuropixelsV2QuadShankElectrode.cs | 100 +++ ...europixelsV2QuadShankProbeConfiguration.cs | 150 ++-- .../NeuropixelsV2RegisterContext.cs | 29 +- .../NeuropixelsV2eProbeGroup.cs | 272 ++++++ 17 files changed, 2612 insertions(+), 321 deletions(-) create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eEditor.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/Electrode.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Helper.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs index c45719d0..96004848 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs @@ -250,7 +250,7 @@ internal void DrawContacts() { var probe = ChannelConfiguration.Probes.ElementAt(probeNumber); - const int borderWidth = 4; + const int borderWidth = 3; for (int j = 0; j < probe.ContactPositions.Length; j++) { @@ -260,13 +260,14 @@ internal void DrawContacts() { var size = contact.ShapeParams.Radius.Value * 2; - EllipseObj contactObj = new(contact.PosX - size / 2, contact.PosY + size / 2, size, size) + EllipseObj contactObj = new(contact.PosX - size / 2, contact.PosY + size / 2, size, size, SelectedContactBorder, DisabledContactFill) { ZOrder = ZOrder.B_BehindLegend, - Tag = ContactTag.GetContactString(probeNumber, contact.Index) + Tag = new ContactTag(probeNumber, contact.Index) }; contactObj.Border.Width = borderWidth; + contactObj.Border.IsVisible = false; zedGraphChannels.GraphPane.GraphObjList.Add(contactObj); } @@ -274,13 +275,14 @@ internal void DrawContacts() { var size = contact.ShapeParams.Width.Value; - BoxObj contactObj = new(contact.PosX - size / 2, contact.PosY + size / 2, size, size) + BoxObj contactObj = new(contact.PosX - size / 2, contact.PosY + size / 2, size, size, SelectedContactBorder, DisabledContactFill) { ZOrder = ZOrder.B_BehindLegend, - Tag = ContactTag.GetContactString(probeNumber, contact.Index) + Tag = new ContactTag(probeNumber, contact.Index) }; contactObj.Border.Width = borderWidth; + contactObj.Border.IsVisible = false; zedGraphChannels.GraphPane.GraphObjList.Add(contactObj); } @@ -294,7 +296,7 @@ internal void DrawContacts() } internal readonly Color DisabledContactFill = Color.DarkGray; - internal readonly Color EnabledContactFill = Color.LightYellow; + internal readonly Color EnabledContactFill = Color.SteelBlue; // Color.LightYellow internal readonly Color ReferenceContactFill = Color.Black; internal virtual void HighlightEnabledContacts() @@ -302,26 +304,27 @@ internal virtual void HighlightEnabledContacts() if (ChannelConfiguration == null) return; - for (int probeNumber = 0; probeNumber < ChannelConfiguration.Probes.Count(); probeNumber++) + var contactObjects = zedGraphChannels.GraphPane.GraphObjList.OfType() + .Where(c => c is not PolyObj); + + var enabledContacts = contactObjects.Where(c => c.Fill.Color == EnabledContactFill); + + foreach (var contact in enabledContacts) { - var probe = ChannelConfiguration.Probes.ElementAt(probeNumber); + contact.Fill.Color = DisabledContactFill; + } - for (int j = 0; j < probe.ContactPositions.Length; j++) - { - Contact contact = probe.GetContact(j); + foreach (var probe in ChannelConfiguration.Probes) + { + var indices = probe.DeviceChannelIndices; - var tag = ContactTag.GetContactString(probeNumber, contact.Index); + var contactsToEnable = contactObjects.Where((c, ind) => indices[ind] != -1); - if (zedGraphChannels.GraphPane.GraphObjList[tag] is BoxObj graphObj) - { - graphObj.Fill.Color = contact.DeviceId == -1 ? - DisabledContactFill : - (ReferenceContacts.Any(x => x == contact.Index) ? ReferenceContactFill : EnabledContactFill); - } - else - { - throw new NullReferenceException($"Tag {tag} is not found in the graph object list"); - } + foreach (var contact in contactsToEnable) + { + var tag = (ContactTag)contact.Tag; + + contact.Fill.Color = ReferenceContacts.Any(x => x == tag.ContactNumber) ? ReferenceContactFill : EnabledContactFill; } } } @@ -334,51 +337,78 @@ internal virtual void HighlightSelectedContacts() if (ChannelConfiguration == null) return; - for (int probeNumber = 0; probeNumber < ChannelConfiguration.Probes.Count(); probeNumber++) + var contactObjects = zedGraphChannels.GraphPane.GraphObjList.OfType() + .Where(c => c is not PolyObj); + + var selectedContacts = contactObjects.Where(c => c.Border.IsVisible); + + foreach (var contact in selectedContacts) { - var probe = ChannelConfiguration.Probes.ElementAt(probeNumber); - var probeOffset = GetProbeIndexOffset(probeNumber); + contact.Border.IsVisible = false; + } - for (int j = 0; j < probe.ContactPositions.Length; j++) - { - var tag = ContactTag.GetContactString(probeNumber, probe.GetContact(j).Index); + var contactsToSelect = contactObjects.Where((c, ind) => SelectedContacts[ind]); - if (zedGraphChannels.GraphPane.GraphObjList[tag] is BoxObj graphObj) - { - graphObj.Border.Color = SelectedContacts[probeOffset + j] ? - SelectedContactBorder : - DeselectedContactBorder; ; - } - else - { - throw new NullReferenceException($"Tag {tag} is not found in the graph object list"); - } - } + if (!contactsToSelect.Any()) + { + return; + } + + foreach (var contact in contactsToSelect) + { + contact.Border.IsVisible = true; } } + internal virtual void UpdateContactLabels() + { + if (ChannelConfiguration == null) + return; + + var indices = ChannelConfiguration.GetDeviceChannelIndices() + .Select(ind => ind == -1).ToArray(); + + var textObjs = zedGraphChannels.GraphPane.GraphObjList.OfType(); + + textObjs.Where(t => t.Text != "-1") + .Select(t => t.Text = "-1"); + + if (indices.Count() != textObjs.Count()) + { + throw new InvalidOperationException($"Incorrect number of text objects found. Expected {indices.Count()}, but found {textObjs.Count()}"); + } + + textObjs.Where((t, ind) => indices[ind]) + .Select(t => + { + var tag = t.Tag as ContactTag; + t.Text = tag.ContactNumber.ToString(); + return false; + }); + } + internal virtual void DrawContactLabels() { if (ChannelConfiguration == null) return; + zedGraphChannels.GraphPane.GraphObjList.RemoveAll(obj => obj is TextObj); + var fontSize = CalculateFontSize(); - for (int probeNumber = 0; probeNumber < ChannelConfiguration.Probes.Count(); probeNumber++) + int probeNumber = 0; + + foreach (var probe in ChannelConfiguration.Probes) { - var probe = ChannelConfiguration.Probes.ElementAt(probeNumber); + var indices = probe.DeviceChannelIndices; + var positions = probe.ContactPositions; - for (int j = 0; j < probe.ContactPositions.Length; j++) + for (int i = 0; i < indices.Length; i++) { - Contact contact = probe.GetContact(j); - bool inactiveContact = contact.DeviceId == -1; - - string id = inactiveContact ? "Off" : ContactString(contact); - - TextObj textObj = new(id, contact.PosX, contact.PosY) + TextObj textObj = new(ContactString(indices[i], i), positions[i][0], positions[i][1]) { ZOrder = ZOrder.A_InFront, - Tag = ContactTag.GetTextString(probeNumber, contact.Index) + Tag = new ContactTag(probeNumber++, i) }; SetTextObj(textObj, fontSize); @@ -396,9 +426,9 @@ internal void SetTextObj(TextObj textObj, float fontSize) textObj.FontSpec.Size = fontSize; } - internal virtual string ContactString(Contact contact) + internal virtual string ContactString(int deviceChannelIndex, int index) { - return contact.Index.ToString(); + return deviceChannelIndex == -1 ? "Off" : index.ToString(); } internal virtual void DrawScale() @@ -428,7 +458,7 @@ internal virtual float CalculateFontSize() var fontSize = 300f * contactSize / rangeY; - fontSize = fontSize < 1f ? 1f : fontSize; + fontSize = fontSize < 5f ? 0.001f : fontSize; fontSize = fontSize > 100f ? 100f : fontSize; return fontSize; @@ -757,26 +787,29 @@ private bool MouseUpEvent(ZedGraphControl sender, MouseEventArgs e) { RectangleF rect = selectionArea.Location.Rect; + sender.GraphPane.GraphObjList.Remove(selectionArea); + if (!rect.IsEmpty) { - for (int i = 0; i < ChannelConfiguration.Probes.Count(); i++) + var selectedContacts = sender.GraphPane.GraphObjList.OfType() + .Where(c => + { + var x = c.Location.X + c.Location.Width / 2; + var y = c.Location.Y - c.Location.Height / 2; + + return c is not PolyObj && + x >= rect.X && + x <= rect.X + rect.Width && + y <= rect.Y && + y >= rect.Y - rect.Height; + }); + + foreach (var contact in selectedContacts) { - var probe = ChannelConfiguration.Probes.ElementAt(i); - - for (int j = 0; j < probe.NumberOfContacts; j++) - { - if (sender.GraphPane.GraphObjList[ContactTag.GetContactString(i, j)] is BoxObj contact && contact != null) - { - if (Contains(rect, contact.Location)) - { - SetSelectedContact(contact.Tag as string, true); - } - } - } + SetSelectedContact((ContactTag)contact.Tag, true); } } - sender.GraphPane.GraphObjList.Remove(selectionArea); clickStart.X = default; clickStart.Y = default; } @@ -788,11 +821,11 @@ private bool MouseUpEvent(ZedGraphControl sender, MouseEventArgs e) { if (nearestObject is TextObj textObj) { - ToggleSelectedContact(textObj.Tag as string); + ToggleSelectedContact(textObj.Tag as ContactTag); } else if (nearestObject is BoxObj boxObj) { - ToggleSelectedContact(boxObj.Tag as string); + ToggleSelectedContact(boxObj.Tag as ContactTag); } } else @@ -811,18 +844,14 @@ private bool MouseUpEvent(ZedGraphControl sender, MouseEventArgs e) return false; } - private void ToggleSelectedContact(string tag) + private void ToggleSelectedContact(ContactTag tag) { SetSelectedContact(tag, !GetContactStatus(tag)); } - private void SetSelectedContact(string tag, bool status) + private void SetSelectedContact(ContactTag contactTag, bool status) { - ContactTag parsedTag = new(tag); - - var index = GetProbeIndexOffset(parsedTag.ProbeNumber) + parsedTag.ContactNumber; - - SetSelectedContact(index, status); + SetSelectedContact(contactTag.ContactNumber, status); } private void SetSelectedContact(int index, bool status) @@ -842,13 +871,9 @@ internal void SetAllSelections(bool newStatus) } } - private bool GetContactStatus(string tag) + private bool GetContactStatus(ContactTag tag) { - ContactTag parsedTag = new(tag); - - var index = GetProbeIndexOffset(parsedTag.ProbeNumber) + parsedTag.ContactNumber; - - return SelectedContacts[index]; + return SelectedContacts[tag.ContactNumber]; } private static PointD TransformPixelsToCoordinates(Point pixels, GraphPane graphPane) @@ -857,36 +882,5 @@ private static PointD TransformPixelsToCoordinates(Point pixels, GraphPane graph return new PointD(x, y); } - - private bool Contains(RectangleF rect, Location location) - { - if (!rect.IsEmpty) - { - if (location != null) - { - var x = location.X + location.Width / 2; - var y = location.Y - location.Height / 2; - - if (x >= rect.X && x <= rect.X + rect.Width && y <= rect.Y && y >= rect.Y - rect.Height) - { - return true; - } - } - } - - return false; - } - - internal int GetProbeIndexOffset(int currentProbeIndex) - { - int offset = 0; - - for (int i = currentProbeIndex - 1; i >= 0; i--) - { - offset += ChannelConfiguration.Probes.ElementAt(i).NumberOfContacts; - } - - return offset; - } } } diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs index c1e4eb58..59bda56b 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs @@ -141,95 +141,7 @@ public static void AddMenuItemsFromDialogToFileOption(this Form thisForm, Form f } } - /// - /// Convert a ProbeInterface object to a list of electrodes, which includes all possible electrodes - /// - /// A object - /// List of electrodes - public static List ToElectrodes(NeuropixelsV1eProbeGroup channelConfiguration) - { - List electrodes = new(); - - foreach (var c in channelConfiguration.GetContacts()) - { - electrodes.Add(new NeuropixelsV1eElectrode(c)); - } - - return electrodes; - } - - public static void UpdateElectrodes(List electrodes, NeuropixelsV1eProbeGroup channelConfiguration) - { - if (electrodes.Count != channelConfiguration.NumberOfContacts) - { - throw new InvalidOperationException($"Different number of electrodes found in {nameof(electrodes)} versus {nameof(channelConfiguration)}"); - } - - int index = 0; - - foreach (var c in channelConfiguration.GetContacts()) - { - electrodes[index++] = new NeuropixelsV1eElectrode(c); - } - } - - /// - /// Convert a ProbeInterface object to a list of electrodes, which only includes currently enabled electrodes - /// - /// A object - /// List of electrodes that are enabled - public static List ToChannelMap(NeuropixelsV1eProbeGroup channelConfiguration) - { - List channelMap = new(); - - foreach (var c in channelConfiguration.GetContacts().Where(c => c.DeviceId != -1)) - { - channelMap.Add(new NeuropixelsV1eElectrode(c)); - } - - return channelMap.OrderBy(e => e.Channel).ToList(); - } - - public static void UpdateChannelMap(List channelMap, NeuropixelsV1eProbeGroup channelConfiguration) - { - var enabledElectrodes = channelConfiguration.GetContacts() - .Where(c => c.DeviceId != -1); - - if (channelMap.Count != enabledElectrodes.Count()) - { - throw new InvalidOperationException($"Different number of enabled electrodes found in {nameof(channelMap)} versus {nameof(channelConfiguration)}"); - } - - int index = 0; - - foreach (var c in enabledElectrodes) - { - channelMap[index++] = new NeuropixelsV1eElectrode(c); - } - } - - /// - /// Update the currently enabled contacts in the probe group, based on the currently selected contacts in - /// the given channel map. The only operation that occurs is an update of the DeviceChannelIndices field, - /// where -1 indicates the contact is no longer enabled - /// - /// List of objects, which contain the index of the selected contact - /// - public static void UpdateProbeGroup(List channelMap, NeuropixelsV1eProbeGroup probeGroup) - { - int[] deviceChannelIndices = new int[probeGroup.NumberOfContacts]; - - deviceChannelIndices = deviceChannelIndices.Select(i => i = -1).ToArray(); - - foreach (var e in channelMap) - { - deviceChannelIndices[e.ElectrodeNumber] = e.Channel; - } - - probeGroup.UpdateDeviceChannelIndices(0, deviceChannelIndices); - } - - public static List SelectElectrodes(this List channelMap, List electrodes) + public static List SelectElectrodes(this List channelMap, List electrodes) where TElectrode : Electrode { foreach (var e in electrodes) { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs new file mode 100644 index 00000000..908c0453 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs @@ -0,0 +1,39 @@ +namespace OpenEphys.Onix.Design +{ + partial class NeuropixelsV2eChannelConfigurationDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "NeuropixelsV2ChannelConfigurationDialog"; + } + + #endregion + } +} \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs new file mode 100644 index 00000000..a52a8315 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using OpenEphys.ProbeInterface; +using ZedGraph; + +namespace OpenEphys.Onix.Design +{ + public partial class NeuropixelsV2eChannelConfigurationDialog : ChannelConfigurationDialog + { + internal event EventHandler OnZoom; + internal event EventHandler OnFileLoad; + + internal readonly List Electrodes; + internal readonly List ChannelMap; + + public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2eProbeGroup probeGroup) + : base(probeGroup) + { + zedGraphChannels.ZoomButtons = MouseButtons.None; + zedGraphChannels.ZoomButtons2 = MouseButtons.None; + + zedGraphChannels.ZoomStepFraction = 0.5; + + ChannelMap = NeuropixelsV2eProbeGroup.ToChannelMap((NeuropixelsV2eProbeGroup)ChannelConfiguration); + Electrodes = NeuropixelsV2eProbeGroup.ToElectrodes((NeuropixelsV2eProbeGroup)ChannelConfiguration); + } + + internal override ProbeGroup DefaultChannelLayout() + { + return new NeuropixelsV2eProbeGroup(); + } + + internal override void LoadDefaultChannelLayout() + { + base.LoadDefaultChannelLayout(); + + NeuropixelsV2eProbeGroup.UpdateElectrodes(Electrodes, (NeuropixelsV2eProbeGroup)ChannelConfiguration); + NeuropixelsV2eProbeGroup.UpdateChannelMap(ChannelMap, (NeuropixelsV2eProbeGroup)ChannelConfiguration); + + OnFileOpenHandler(); + } + + internal override void OpenFile() + { + base.OpenFile(); + + NeuropixelsV2eProbeGroup.UpdateChannelMap(ChannelMap, (NeuropixelsV2eProbeGroup)ChannelConfiguration); + + OnFileOpenHandler(); + } + + private void OnFileOpenHandler() + { + OnFileLoad?.Invoke(this, EventArgs.Empty); + } + + internal override void ZoomEvent(ZedGraphControl sender, ZoomState oldState, ZoomState newState) + { + base.ZoomEvent(sender, oldState, newState); + + UpdateFontSize(); + RefreshZedGraph(); + + OnZoomHandler(); + } + + private void OnZoomHandler() + { + OnZoom?.Invoke(this, EventArgs.Empty); + } + + internal override void DrawScale() + { + const int MajorTickIncrement = 100; + const int MajorTickLength = 10; + const int MinorTickIncrement = 10; + const int MinorTickLength = 5; + + if (ChannelConfiguration.Probes.ElementAt(0).SiUnits != ProbeSiUnits.Um) + { + MessageBox.Show("Warning: Expected ProbeGroup units to be in microns, but it is in millimeters. Scale might not be accurate."); + } + + var fontSize = CalculateFontSize(); + + var x = MaxX(zedGraphChannels.GraphPane.GraphObjList) + 10; + var minY = MinY(zedGraphChannels.GraphPane.GraphObjList); + var maxY = MaxY(zedGraphChannels.GraphPane.GraphObjList); + + zedGraphChannels.GraphPane.CurveList.Clear(); + + PointPairList pointList = new(); + + var countMajorTicks = 0; + + for (int i = (int)minY; i < maxY; i += MajorTickIncrement) + { + pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks)); + PointPair majorTickLocation = new(x + MajorTickLength, minY + MajorTickIncrement * countMajorTicks); + pointList.Add(majorTickLocation); + pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks)); + + TextObj textObj = new($"{i} µm", majorTickLocation.X + 10, majorTickLocation.Y) + { + Tag = "scale" + }; + textObj.FontSpec.Border.IsVisible = false; + textObj.FontSpec.Size = fontSize; + zedGraphChannels.GraphPane.GraphObjList.Add(textObj); + + var countMinorTicks = 1; + + for (int j = i + MinorTickIncrement; j < i + MajorTickIncrement && i + MinorTickIncrement * countMinorTicks < maxY; j += MinorTickIncrement) + { + pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks + MinorTickIncrement * countMinorTicks)); + pointList.Add(new PointPair(x + MinorTickLength, minY + MajorTickIncrement * countMajorTicks + MinorTickIncrement * countMinorTicks)); + pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks + MinorTickIncrement * countMinorTicks)); + + countMinorTicks++; + } + + countMajorTicks++; + } + + var curve = zedGraphChannels.GraphPane.AddCurve("", pointList, Color.Black, SymbolType.None); + + curve.Line.Width = 4; + curve.Label.IsVisible = false; + curve.Symbol.IsVisible = false; + } + + internal override void HighlightEnabledContacts() + { + if (ChannelConfiguration == null || ChannelMap == null) + return; + + var contactObjects = zedGraphChannels.GraphPane.GraphObjList.OfType() + .Where(c => c is not PolyObj); + + var enabledContacts = contactObjects.Where(c => c.Fill.Color == EnabledContactFill); + + foreach (var contact in enabledContacts) + { + contact.Fill.Color = DisabledContactFill; + } + + var contactsToEnable = contactObjects.Where(c => + { + var tag = c.Tag as ContactTag; + var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactNumber); + return ChannelMap[channel].ElectrodeNumber == tag.ContactNumber; + }); + + foreach (var contact in contactsToEnable) + { + var tag = (ContactTag)contact.Tag; + + contact.Fill.Color = ReferenceContacts.Any(x => x == tag.ContactNumber) ? ReferenceContactFill : EnabledContactFill; + } + } + + internal override void UpdateContactLabels() + { + if (ChannelConfiguration == null) + return; + + var indices = ChannelConfiguration.GetDeviceChannelIndices() + .Select(ind => ind == -1).ToArray(); + + var textObjs = zedGraphChannels.GraphPane.GraphObjList.OfType() + .Where(t => t.Tag is not string); + + textObjs.Where(t => t.Text != "Off"); + + foreach (var textObj in textObjs) + { + textObj.Text = "Off"; + } + + if (indices.Count() != textObjs.Count()) + { + throw new InvalidOperationException($"Incorrect number of text objects found. Expected {indices.Count()}, but found {textObjs.Count()}"); + } + + var textObjsToUpdate = textObjs.Where(c => + { + var tag = c.Tag as ContactTag; + var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactNumber); + return ChannelMap[channel].ElectrodeNumber == tag.ContactNumber; + }); + + foreach (var textObj in textObjsToUpdate) + { + var tag = textObj.Tag as ContactTag; + textObj.Text = tag.ContactNumber.ToString(); + } + } + + internal void EnableElectrodes(List electrodes) + { + ChannelMap.SelectElectrodes(electrodes); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs new file mode 100644 index 00000000..bb31ea53 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs @@ -0,0 +1,737 @@ +namespace OpenEphys.Onix.Design +{ + partial class NeuropixelsV2eDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.Label label1; + System.Windows.Forms.Label label2; + System.Windows.Forms.Label labelSelection; + System.Windows.Forms.Label labelPresets; + System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1; + System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelGainA; + System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel2; + System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel3; + System.Windows.Forms.Label label4; + System.Windows.Forms.Label label5; + System.Windows.Forms.Label Reference; + System.Windows.Forms.Label probeCalibrationFileA; + System.Windows.Forms.Label probeCalibrationFileB; + System.Windows.Forms.Label label3; + this.toolStripStatusLabelProbeA = new System.Windows.Forms.ToolStripStatusLabel(); + this.toolStripStatusLabelProbeB = new System.Windows.Forms.ToolStripStatusLabel(); + this.menuStrip = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.statusStrip = new System.Windows.Forms.StatusStrip(); + this.probeSnA = new System.Windows.Forms.ToolStripStatusLabel(); + this.gainA = new System.Windows.Forms.ToolStripStatusLabel(); + this.probeSnB = new System.Windows.Forms.ToolStripStatusLabel(); + this.gainB = new System.Windows.Forms.ToolStripStatusLabel(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.splitContainer2 = new System.Windows.Forms.SplitContainer(); + this.tabControlProbe = new System.Windows.Forms.TabControl(); + this.tabPageProbeA = new System.Windows.Forms.TabPage(); + this.panelProbeA = new System.Windows.Forms.Panel(); + this.tabPageProbeB = new System.Windows.Forms.TabPage(); + this.panelProbeB = new System.Windows.Forms.Panel(); + this.tabControlOptions = new System.Windows.Forms.TabControl(); + this.tabPageOptions = new System.Windows.Forms.TabPage(); + this.panelOptions = new System.Windows.Forms.Panel(); + this.comboBoxReferenceB = new System.Windows.Forms.ComboBox(); + this.buttonGainCalibrationFileB = new System.Windows.Forms.Button(); + this.textBoxProbeCalibrationFileB = new System.Windows.Forms.TextBox(); + this.buttonGainCalibrationFileA = new System.Windows.Forms.Button(); + this.textBoxProbeCalibrationFileA = new System.Windows.Forms.TextBox(); + this.comboBoxReferenceA = new System.Windows.Forms.ComboBox(); + this.tabPageContactsOptions = new System.Windows.Forms.TabPage(); + this.panelChannelOptions = new System.Windows.Forms.Panel(); + this.comboBoxChannelPresetsB = new System.Windows.Forms.ComboBox(); + this.comboBoxChannelPresetsA = new System.Windows.Forms.ComboBox(); + this.trackBarProbePosition = new System.Windows.Forms.TrackBar(); + this.buttonEnableContacts = new System.Windows.Forms.Button(); + this.buttonClearSelections = new System.Windows.Forms.Button(); + this.buttonResetZoom = new System.Windows.Forms.Button(); + this.buttonZoomOut = new System.Windows.Forms.Button(); + this.buttonZoomIn = new System.Windows.Forms.Button(); + this.panel1 = new System.Windows.Forms.Panel(); + this.buttonCancel = new System.Windows.Forms.Button(); + this.buttonOkay = new System.Windows.Forms.Button(); + this.linkLabelDocumentation = new System.Windows.Forms.LinkLabel(); + label1 = new System.Windows.Forms.Label(); + label2 = new System.Windows.Forms.Label(); + labelSelection = new System.Windows.Forms.Label(); + labelPresets = new System.Windows.Forms.Label(); + toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + toolStripStatusLabelGainA = new System.Windows.Forms.ToolStripStatusLabel(); + toolStripStatusLabel2 = new System.Windows.Forms.ToolStripStatusLabel(); + toolStripStatusLabel3 = new System.Windows.Forms.ToolStripStatusLabel(); + label4 = new System.Windows.Forms.Label(); + label5 = new System.Windows.Forms.Label(); + Reference = new System.Windows.Forms.Label(); + probeCalibrationFileA = new System.Windows.Forms.Label(); + probeCalibrationFileB = new System.Windows.Forms.Label(); + label3 = new System.Windows.Forms.Label(); + this.menuStrip.SuspendLayout(); + this.statusStrip.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); + this.splitContainer2.Panel1.SuspendLayout(); + this.splitContainer2.Panel2.SuspendLayout(); + this.splitContainer2.SuspendLayout(); + this.tabControlProbe.SuspendLayout(); + this.tabPageProbeA.SuspendLayout(); + this.tabPageProbeB.SuspendLayout(); + this.tabControlOptions.SuspendLayout(); + this.tabPageOptions.SuspendLayout(); + this.panelOptions.SuspendLayout(); + this.tabPageContactsOptions.SuspendLayout(); + this.panelChannelOptions.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).BeginInit(); + this.panel1.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(20, 16); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(66, 20); + label1.TabIndex = 5; + label1.Text = "Jump to"; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new System.Drawing.Point(151, 18); + label2.Name = "label2"; + label2.Size = new System.Drawing.Size(50, 20); + label2.TabIndex = 6; + label2.Text = "Zoom"; + // + // labelSelection + // + labelSelection.AutoSize = true; + labelSelection.Location = new System.Drawing.Point(139, 193); + labelSelection.Name = "labelSelection"; + labelSelection.Size = new System.Drawing.Size(75, 20); + labelSelection.TabIndex = 18; + labelSelection.Text = "Selection"; + // + // labelPresets + // + labelPresets.AutoSize = true; + labelPresets.Location = new System.Drawing.Point(139, 386); + labelPresets.Name = "labelPresets"; + labelPresets.Size = new System.Drawing.Size(63, 20); + labelPresets.TabIndex = 23; + labelPresets.Text = "Presets"; + // + // toolStripStatusLabel1 + // + toolStripStatusLabel1.Name = "toolStripStatusLabel1"; + toolStripStatusLabel1.Size = new System.Drawing.Size(74, 25); + toolStripStatusLabel1.Text = "Probe B"; + // + // toolStripStatusLabelGainA + // + toolStripStatusLabelGainA.Name = "toolStripStatusLabelGainA"; + toolStripStatusLabelGainA.Size = new System.Drawing.Size(47, 25); + toolStripStatusLabelGainA.Text = "Gain"; + // + // toolStripStatusLabel2 + // + toolStripStatusLabel2.Name = "toolStripStatusLabel2"; + toolStripStatusLabel2.Size = new System.Drawing.Size(76, 25); + toolStripStatusLabel2.Text = "Probe A"; + // + // toolStripStatusLabel3 + // + toolStripStatusLabel3.Name = "toolStripStatusLabel3"; + toolStripStatusLabel3.Size = new System.Drawing.Size(47, 25); + toolStripStatusLabel3.Text = "Gain"; + // + // label4 + // + label4.AutoSize = true; + label4.Location = new System.Drawing.Point(138, 421); + label4.Name = "label4"; + label4.Size = new System.Drawing.Size(66, 20); + label4.TabIndex = 25; + label4.Text = "Probe A"; + // + // label5 + // + label5.AutoSize = true; + label5.Location = new System.Drawing.Point(138, 498); + label5.Name = "label5"; + label5.Size = new System.Drawing.Size(66, 20); + label5.TabIndex = 27; + label5.Text = "Probe B"; + // + // toolStripStatusLabelProbeA + // + this.toolStripStatusLabelProbeA.Name = "toolStripStatusLabelProbeA"; + this.toolStripStatusLabelProbeA.Size = new System.Drawing.Size(44, 25); + this.toolStripStatusLabelProbeA.Text = "SN: "; + // + // toolStripStatusLabelProbeB + // + this.toolStripStatusLabelProbeB.Name = "toolStripStatusLabelProbeB"; + this.toolStripStatusLabelProbeB.Size = new System.Drawing.Size(44, 25); + this.toolStripStatusLabelProbeB.Text = "SN: "; + // + // Reference + // + Reference.AutoSize = true; + Reference.Location = new System.Drawing.Point(14, 79); + Reference.Name = "Reference"; + Reference.Size = new System.Drawing.Size(95, 20); + Reference.TabIndex = 4; + Reference.Text = "ReferenceA"; + // + // probeCalibrationFileA + // + probeCalibrationFileA.AutoSize = true; + probeCalibrationFileA.Location = new System.Drawing.Point(13, 275); + probeCalibrationFileA.MaximumSize = new System.Drawing.Size(200, 45); + probeCalibrationFileA.Name = "probeCalibrationFileA"; + probeCalibrationFileA.Size = new System.Drawing.Size(174, 20); + probeCalibrationFileA.TabIndex = 8; + probeCalibrationFileA.Text = "Probe A Calibration File"; + // + // probeCalibrationFileB + // + probeCalibrationFileB.AutoSize = true; + probeCalibrationFileB.Location = new System.Drawing.Point(14, 460); + probeCalibrationFileB.MaximumSize = new System.Drawing.Size(200, 45); + probeCalibrationFileB.Name = "probeCalibrationFileB"; + probeCalibrationFileB.Size = new System.Drawing.Size(174, 20); + probeCalibrationFileB.TabIndex = 11; + probeCalibrationFileB.Text = "Probe B Calibration File"; + // + // menuStrip + // + this.menuStrip.GripMargin = new System.Windows.Forms.Padding(2, 2, 0, 2); + this.menuStrip.ImageScalingSize = new System.Drawing.Size(24, 24); + this.menuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem}); + this.menuStrip.Location = new System.Drawing.Point(0, 0); + this.menuStrip.Name = "menuStrip"; + this.menuStrip.Size = new System.Drawing.Size(1265, 33); + this.menuStrip.TabIndex = 0; + this.menuStrip.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 29); + this.fileToolStripMenuItem.Text = "File"; + // + // statusStrip + // + this.statusStrip.ImageScalingSize = new System.Drawing.Size(24, 24); + this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + toolStripStatusLabel2, + this.toolStripStatusLabelProbeA, + this.probeSnA, + toolStripStatusLabelGainA, + this.gainA, + toolStripStatusLabel1, + this.toolStripStatusLabelProbeB, + this.probeSnB, + toolStripStatusLabel3, + this.gainB}); + this.statusStrip.Location = new System.Drawing.Point(0, 784); + this.statusStrip.Name = "statusStrip"; + this.statusStrip.Size = new System.Drawing.Size(1265, 32); + this.statusStrip.TabIndex = 1; + this.statusStrip.Text = "statusStrip1"; + // + // probeSnA + // + this.probeSnA.AutoSize = false; + this.probeSnA.Name = "probeSnA"; + this.probeSnA.Size = new System.Drawing.Size(135, 25); + this.probeSnA.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // gainA + // + this.gainA.AutoSize = false; + this.gainA.Name = "gainA"; + this.gainA.Size = new System.Drawing.Size(120, 25); + // + // probeSnB + // + this.probeSnB.AutoSize = false; + this.probeSnB.Name = "probeSnB"; + this.probeSnB.Size = new System.Drawing.Size(135, 25); + this.probeSnB.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // gainB + // + this.gainB.AutoSize = false; + this.gainB.Name = "gainB"; + this.gainB.Size = new System.Drawing.Size(80, 25); + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.Location = new System.Drawing.Point(0, 33); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.splitContainer2); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.panel1); + this.splitContainer1.Size = new System.Drawing.Size(1265, 751); + this.splitContainer1.SplitterDistance = 699; + this.splitContainer1.TabIndex = 2; + // + // splitContainer2 + // + this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer2.Location = new System.Drawing.Point(0, 0); + this.splitContainer2.Name = "splitContainer2"; + // + // splitContainer2.Panel1 + // + this.splitContainer2.Panel1.Controls.Add(this.tabControlProbe); + // + // splitContainer2.Panel2 + // + this.splitContainer2.Panel2.Controls.Add(this.tabControlOptions); + this.splitContainer2.Size = new System.Drawing.Size(1265, 699); + this.splitContainer2.SplitterDistance = 991; + this.splitContainer2.TabIndex = 1; + // + // tabControlProbe + // + this.tabControlProbe.Controls.Add(this.tabPageProbeA); + this.tabControlProbe.Controls.Add(this.tabPageProbeB); + this.tabControlProbe.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControlProbe.Location = new System.Drawing.Point(0, 0); + this.tabControlProbe.Name = "tabControlProbe"; + this.tabControlProbe.SelectedIndex = 0; + this.tabControlProbe.Size = new System.Drawing.Size(991, 699); + this.tabControlProbe.TabIndex = 0; + // + // tabPageProbeA + // + this.tabPageProbeA.Controls.Add(this.panelProbeA); + this.tabPageProbeA.Location = new System.Drawing.Point(4, 29); + this.tabPageProbeA.Name = "tabPageProbeA"; + this.tabPageProbeA.Size = new System.Drawing.Size(983, 666); + this.tabPageProbeA.TabIndex = 0; + this.tabPageProbeA.Text = "Probe A"; + this.tabPageProbeA.UseVisualStyleBackColor = true; + // + // panelProbeA + // + this.panelProbeA.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelProbeA.Location = new System.Drawing.Point(0, 0); + this.panelProbeA.Name = "panelProbeA"; + this.panelProbeA.Size = new System.Drawing.Size(983, 666); + this.panelProbeA.TabIndex = 0; + // + // tabPageProbeB + // + this.tabPageProbeB.Controls.Add(this.panelProbeB); + this.tabPageProbeB.Location = new System.Drawing.Point(4, 29); + this.tabPageProbeB.Name = "tabPageProbeB"; + this.tabPageProbeB.Size = new System.Drawing.Size(983, 666); + this.tabPageProbeB.TabIndex = 2; + this.tabPageProbeB.Text = "Probe B"; + this.tabPageProbeB.UseVisualStyleBackColor = true; + // + // panelProbeB + // + this.panelProbeB.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelProbeB.Location = new System.Drawing.Point(0, 0); + this.panelProbeB.Name = "panelProbeB"; + this.panelProbeB.Size = new System.Drawing.Size(983, 666); + this.panelProbeB.TabIndex = 1; + // + // tabControlOptions + // + this.tabControlOptions.Controls.Add(this.tabPageOptions); + this.tabControlOptions.Controls.Add(this.tabPageContactsOptions); + this.tabControlOptions.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControlOptions.Location = new System.Drawing.Point(0, 0); + this.tabControlOptions.Name = "tabControlOptions"; + this.tabControlOptions.SelectedIndex = 0; + this.tabControlOptions.Size = new System.Drawing.Size(270, 699); + this.tabControlOptions.TabIndex = 0; + // + // tabPageOptions + // + this.tabPageOptions.Controls.Add(this.panelOptions); + this.tabPageOptions.Location = new System.Drawing.Point(4, 29); + this.tabPageOptions.Name = "tabPageOptions"; + this.tabPageOptions.Padding = new System.Windows.Forms.Padding(3); + this.tabPageOptions.Size = new System.Drawing.Size(262, 666); + this.tabPageOptions.TabIndex = 0; + this.tabPageOptions.Text = "Options"; + this.tabPageOptions.UseVisualStyleBackColor = true; + // + // panelOptions + // + this.panelOptions.Controls.Add(this.comboBoxReferenceB); + this.panelOptions.Controls.Add(label3); + this.panelOptions.Controls.Add(this.buttonGainCalibrationFileB); + this.panelOptions.Controls.Add(this.textBoxProbeCalibrationFileB); + this.panelOptions.Controls.Add(probeCalibrationFileB); + this.panelOptions.Controls.Add(this.buttonGainCalibrationFileA); + this.panelOptions.Controls.Add(this.textBoxProbeCalibrationFileA); + this.panelOptions.Controls.Add(probeCalibrationFileA); + this.panelOptions.Controls.Add(this.comboBoxReferenceA); + this.panelOptions.Controls.Add(Reference); + this.panelOptions.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelOptions.Location = new System.Drawing.Point(3, 3); + this.panelOptions.Name = "panelOptions"; + this.panelOptions.Size = new System.Drawing.Size(256, 660); + this.panelOptions.TabIndex = 0; + // + // comboBoxReferenceB + // + this.comboBoxReferenceB.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxReferenceB.FormattingEnabled = true; + this.comboBoxReferenceB.Location = new System.Drawing.Point(115, 144); + this.comboBoxReferenceB.Name = "comboBoxReferenceB"; + this.comboBoxReferenceB.Size = new System.Drawing.Size(121, 28); + this.comboBoxReferenceB.TabIndex = 15; + this.comboBoxReferenceB.SelectedIndexChanged += new System.EventHandler(this.SelectedIndexChanged); + // + // label3 + // + label3.AutoSize = true; + label3.Location = new System.Drawing.Point(14, 147); + label3.Name = "label3"; + label3.Size = new System.Drawing.Size(95, 20); + label3.TabIndex = 14; + label3.Text = "ReferenceB"; + // + // buttonGainCalibrationFileB + // + this.buttonGainCalibrationFileB.Location = new System.Drawing.Point(44, 515); + this.buttonGainCalibrationFileB.Name = "buttonGainCalibrationFileB"; + this.buttonGainCalibrationFileB.Size = new System.Drawing.Size(141, 32); + this.buttonGainCalibrationFileB.TabIndex = 13; + this.buttonGainCalibrationFileB.Text = "Choose"; + this.buttonGainCalibrationFileB.UseVisualStyleBackColor = true; + this.buttonGainCalibrationFileB.Click += new System.EventHandler(this.ButtonClick); + // + // textBoxProbeCalibrationFileB + // + this.textBoxProbeCalibrationFileB.Location = new System.Drawing.Point(18, 483); + this.textBoxProbeCalibrationFileB.Name = "textBoxProbeCalibrationFileB"; + this.textBoxProbeCalibrationFileB.ReadOnly = true; + this.textBoxProbeCalibrationFileB.Size = new System.Drawing.Size(207, 26); + this.textBoxProbeCalibrationFileB.TabIndex = 12; + this.textBoxProbeCalibrationFileB.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.textBoxProbeCalibrationFileB.TextChanged += new System.EventHandler(this.FileTextChanged); + // + // buttonGainCalibrationFileA + // + this.buttonGainCalibrationFileA.Location = new System.Drawing.Point(43, 330); + this.buttonGainCalibrationFileA.Name = "buttonGainCalibrationFileA"; + this.buttonGainCalibrationFileA.Size = new System.Drawing.Size(141, 32); + this.buttonGainCalibrationFileA.TabIndex = 10; + this.buttonGainCalibrationFileA.Text = "Choose"; + this.buttonGainCalibrationFileA.UseVisualStyleBackColor = true; + this.buttonGainCalibrationFileA.Click += new System.EventHandler(this.ButtonClick); + // + // textBoxProbeCalibrationFileA + // + this.textBoxProbeCalibrationFileA.Location = new System.Drawing.Point(17, 298); + this.textBoxProbeCalibrationFileA.Name = "textBoxProbeCalibrationFileA"; + this.textBoxProbeCalibrationFileA.ReadOnly = true; + this.textBoxProbeCalibrationFileA.Size = new System.Drawing.Size(207, 26); + this.textBoxProbeCalibrationFileA.TabIndex = 9; + this.textBoxProbeCalibrationFileA.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.textBoxProbeCalibrationFileA.TextChanged += new System.EventHandler(this.FileTextChanged); + // + // comboBoxReferenceA + // + this.comboBoxReferenceA.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxReferenceA.FormattingEnabled = true; + this.comboBoxReferenceA.Location = new System.Drawing.Point(115, 76); + this.comboBoxReferenceA.Name = "comboBoxReferenceA"; + this.comboBoxReferenceA.Size = new System.Drawing.Size(121, 28); + this.comboBoxReferenceA.TabIndex = 5; + this.comboBoxReferenceA.SelectedIndexChanged += new System.EventHandler(this.SelectedIndexChanged); + // + // tabPageContactsOptions + // + this.tabPageContactsOptions.Controls.Add(this.panelChannelOptions); + this.tabPageContactsOptions.Location = new System.Drawing.Point(4, 29); + this.tabPageContactsOptions.Name = "tabPageContactsOptions"; + this.tabPageContactsOptions.Size = new System.Drawing.Size(262, 666); + this.tabPageContactsOptions.TabIndex = 2; + this.tabPageContactsOptions.Text = "Contacts"; + this.tabPageContactsOptions.UseVisualStyleBackColor = true; + // + // panelChannelOptions + // + this.panelChannelOptions.Controls.Add(label5); + this.panelChannelOptions.Controls.Add(this.comboBoxChannelPresetsB); + this.panelChannelOptions.Controls.Add(label4); + this.panelChannelOptions.Controls.Add(this.comboBoxChannelPresetsA); + this.panelChannelOptions.Controls.Add(labelPresets); + this.panelChannelOptions.Controls.Add(this.trackBarProbePosition); + this.panelChannelOptions.Controls.Add(this.buttonEnableContacts); + this.panelChannelOptions.Controls.Add(this.buttonClearSelections); + this.panelChannelOptions.Controls.Add(labelSelection); + this.panelChannelOptions.Controls.Add(label2); + this.panelChannelOptions.Controls.Add(label1); + this.panelChannelOptions.Controls.Add(this.buttonResetZoom); + this.panelChannelOptions.Controls.Add(this.buttonZoomOut); + this.panelChannelOptions.Controls.Add(this.buttonZoomIn); + this.panelChannelOptions.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelChannelOptions.Location = new System.Drawing.Point(0, 0); + this.panelChannelOptions.Name = "panelChannelOptions"; + this.panelChannelOptions.Size = new System.Drawing.Size(262, 666); + this.panelChannelOptions.TabIndex = 0; + // + // comboBoxChannelPresetsB + // + this.comboBoxChannelPresetsB.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxChannelPresetsB.FormattingEnabled = true; + this.comboBoxChannelPresetsB.Location = new System.Drawing.Point(92, 519); + this.comboBoxChannelPresetsB.Name = "comboBoxChannelPresetsB"; + this.comboBoxChannelPresetsB.Size = new System.Drawing.Size(162, 28); + this.comboBoxChannelPresetsB.TabIndex = 26; + // + // comboBoxChannelPresetsA + // + this.comboBoxChannelPresetsA.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxChannelPresetsA.FormattingEnabled = true; + this.comboBoxChannelPresetsA.Location = new System.Drawing.Point(92, 442); + this.comboBoxChannelPresetsA.Name = "comboBoxChannelPresetsA"; + this.comboBoxChannelPresetsA.Size = new System.Drawing.Size(162, 28); + this.comboBoxChannelPresetsA.TabIndex = 24; + // + // trackBarProbePosition + // + this.trackBarProbePosition.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left))); + this.trackBarProbePosition.Location = new System.Drawing.Point(17, 39); + this.trackBarProbePosition.Maximum = 100; + this.trackBarProbePosition.Name = "trackBarProbePosition"; + this.trackBarProbePosition.Orientation = System.Windows.Forms.Orientation.Vertical; + this.trackBarProbePosition.Size = new System.Drawing.Size(69, 603); + this.trackBarProbePosition.TabIndex = 22; + this.trackBarProbePosition.Scroll += new System.EventHandler(this.TrackBarScroll); + // + // buttonEnableContacts + // + this.buttonEnableContacts.Location = new System.Drawing.Point(130, 231); + this.buttonEnableContacts.Name = "buttonEnableContacts"; + this.buttonEnableContacts.Size = new System.Drawing.Size(96, 56); + this.buttonEnableContacts.TabIndex = 20; + this.buttonEnableContacts.Text = "Enable Contacts"; + this.buttonEnableContacts.UseVisualStyleBackColor = true; + this.buttonEnableContacts.Click += new System.EventHandler(this.ButtonClick); + // + // buttonClearSelections + // + this.buttonClearSelections.Location = new System.Drawing.Point(130, 293); + this.buttonClearSelections.Name = "buttonClearSelections"; + this.buttonClearSelections.Size = new System.Drawing.Size(96, 59); + this.buttonClearSelections.TabIndex = 19; + this.buttonClearSelections.Text = "Clear Selection"; + this.buttonClearSelections.UseVisualStyleBackColor = true; + this.buttonClearSelections.Click += new System.EventHandler(this.ButtonClick); + // + // buttonResetZoom + // + this.buttonResetZoom.Location = new System.Drawing.Point(130, 131); + this.buttonResetZoom.Name = "buttonResetZoom"; + this.buttonResetZoom.Size = new System.Drawing.Size(96, 34); + this.buttonResetZoom.TabIndex = 4; + this.buttonResetZoom.Text = "Reset"; + this.buttonResetZoom.UseVisualStyleBackColor = true; + this.buttonResetZoom.Click += new System.EventHandler(this.ButtonClick); + // + // buttonZoomOut + // + this.buttonZoomOut.Location = new System.Drawing.Point(130, 91); + this.buttonZoomOut.Name = "buttonZoomOut"; + this.buttonZoomOut.Size = new System.Drawing.Size(96, 34); + this.buttonZoomOut.TabIndex = 3; + this.buttonZoomOut.Text = "Zoom Out"; + this.buttonZoomOut.UseVisualStyleBackColor = true; + this.buttonZoomOut.Click += new System.EventHandler(this.ButtonClick); + // + // buttonZoomIn + // + this.buttonZoomIn.Location = new System.Drawing.Point(130, 51); + this.buttonZoomIn.Name = "buttonZoomIn"; + this.buttonZoomIn.Size = new System.Drawing.Size(96, 34); + this.buttonZoomIn.TabIndex = 2; + this.buttonZoomIn.Text = "Zoom In"; + this.buttonZoomIn.UseVisualStyleBackColor = true; + this.buttonZoomIn.Click += new System.EventHandler(this.ButtonClick); + // + // panel1 + // + this.panel1.Controls.Add(this.buttonCancel); + this.panel1.Controls.Add(this.buttonOkay); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(1265, 48); + this.panel1.TabIndex = 0; + // + // buttonCancel + // + this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonCancel.Location = new System.Drawing.Point(1129, 7); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(124, 34); + this.buttonCancel.TabIndex = 1; + this.buttonCancel.Text = "Cancel"; + this.buttonCancel.UseVisualStyleBackColor = true; + this.buttonCancel.Click += new System.EventHandler(this.ButtonClick); + // + // buttonOkay + // + this.buttonOkay.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonOkay.Location = new System.Drawing.Point(996, 7); + this.buttonOkay.Name = "buttonOkay"; + this.buttonOkay.Size = new System.Drawing.Size(124, 34); + this.buttonOkay.TabIndex = 0; + this.buttonOkay.Text = "OK"; + this.buttonOkay.UseVisualStyleBackColor = true; + this.buttonOkay.Click += new System.EventHandler(this.ButtonClick); + // + // linkLabelDocumentation + // + this.linkLabelDocumentation.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.linkLabelDocumentation.AutoSize = true; + this.linkLabelDocumentation.BackColor = System.Drawing.Color.GhostWhite; + this.linkLabelDocumentation.Location = new System.Drawing.Point(1144, 4); + this.linkLabelDocumentation.Name = "linkLabelDocumentation"; + this.linkLabelDocumentation.Size = new System.Drawing.Size(118, 20); + this.linkLabelDocumentation.TabIndex = 3; + this.linkLabelDocumentation.TabStop = true; + this.linkLabelDocumentation.Text = "Documentation"; + // + // NeuropixelsV2eDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1265, 816); + this.Controls.Add(this.linkLabelDocumentation); + this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.statusStrip); + this.Controls.Add(this.menuStrip); + this.DoubleBuffered = true; + this.MainMenuStrip = this.menuStrip; + this.Name = "NeuropixelsV2eDialog"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "NeuropixelsV2eDialog"; + this.menuStrip.ResumeLayout(false); + this.menuStrip.PerformLayout(); + this.statusStrip.ResumeLayout(false); + this.statusStrip.PerformLayout(); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.splitContainer2.Panel1.ResumeLayout(false); + this.splitContainer2.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); + this.splitContainer2.ResumeLayout(false); + this.tabControlProbe.ResumeLayout(false); + this.tabPageProbeA.ResumeLayout(false); + this.tabPageProbeB.ResumeLayout(false); + this.tabControlOptions.ResumeLayout(false); + this.tabPageOptions.ResumeLayout(false); + this.panelOptions.ResumeLayout(false); + this.panelOptions.PerformLayout(); + this.tabPageContactsOptions.ResumeLayout(false); + this.panelChannelOptions.ResumeLayout(false); + this.panelChannelOptions.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).EndInit(); + this.panel1.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.MenuStrip menuStrip; + private System.Windows.Forms.StatusStrip statusStrip; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.TabControl tabControlProbe; + private System.Windows.Forms.TabPage tabPageProbeA; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Panel panelProbeA; + private System.Windows.Forms.Button buttonCancel; + private System.Windows.Forms.Button buttonOkay; + private System.Windows.Forms.ToolStripStatusLabel probeSnA; + private System.Windows.Forms.ToolStripStatusLabel probeSnB; + private System.Windows.Forms.TabPage tabPageProbeB; + private System.Windows.Forms.LinkLabel linkLabelDocumentation; + private System.Windows.Forms.TabControl tabControlOptions; + private System.Windows.Forms.TabPage tabPageOptions; + private System.Windows.Forms.Panel panelOptions; + private System.Windows.Forms.ComboBox comboBoxReferenceA; + private System.Windows.Forms.TextBox textBoxProbeCalibrationFileA; + private System.Windows.Forms.Button buttonGainCalibrationFileA; + private System.Windows.Forms.Button buttonGainCalibrationFileB; + private System.Windows.Forms.TextBox textBoxProbeCalibrationFileB; + private System.Windows.Forms.TabPage tabPageContactsOptions; + private System.Windows.Forms.Panel panelChannelOptions; + private System.Windows.Forms.Button buttonZoomIn; + private System.Windows.Forms.Button buttonResetZoom; + private System.Windows.Forms.Button buttonZoomOut; + private System.Windows.Forms.Button buttonClearSelections; + private System.Windows.Forms.Button buttonEnableContacts; + private System.Windows.Forms.TrackBar trackBarProbePosition; + private System.Windows.Forms.ComboBox comboBoxChannelPresetsA; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelProbeA; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelProbeB; + private System.Windows.Forms.Panel panelProbeB; + private System.Windows.Forms.SplitContainer splitContainer2; + private System.Windows.Forms.ComboBox comboBoxReferenceB; + private System.Windows.Forms.ToolStripStatusLabel gainA; + private System.Windows.Forms.ToolStripStatusLabel gainB; + private System.Windows.Forms.ComboBox comboBoxChannelPresetsB; + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs new file mode 100644 index 00000000..13d3f52d --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs @@ -0,0 +1,784 @@ +using System; +using System.IO; +using System.Linq; +using System.Windows.Forms; + +namespace OpenEphys.Onix.Design +{ + public partial class NeuropixelsV2eDialog : Form + { + readonly NeuropixelsV2eChannelConfigurationDialog ChannelConfigurationA; + readonly NeuropixelsV2eChannelConfigurationDialog ChannelConfigurationB; + + private enum ChannelPreset + { + Shank0BankA, + Shank0BankB, + Shank0BankC, + Shank0BankD, + Shank1BankA, + Shank1BankB, + Shank1BankC, + Shank1BankD, + Shank2BankA, + Shank2BankB, + Shank2BankC, + Shank2BankD, + Shank3BankA, + Shank3BankB, + Shank3BankC, + Shank3BankD, + AllShanks0_95, + AllShanks96_191, + AllShanks192_287, + AllShanks288_383, + AllShanks384_479, + AllShanks480_575, + AllShanks576_671, + AllShanks672_767, + AllShanks768_863, + AllShanks864_959, + AllShanks960_1055, + AllShanks1056_1151, + AllShanks1152_1247, + None + } + + public ConfigureNeuropixelsV2e ConfigureNode { get; set; } + + public NeuropixelsV2eDialog(ConfigureNeuropixelsV2e configureNode) + { + InitializeComponent(); + Shown += FormShown; + + ConfigureNode = new(configureNode); + + textBoxProbeCalibrationFileA.Text = ConfigureNode.GainCalibrationFileA; + textBoxProbeCalibrationFileB.Text = ConfigureNode.GainCalibrationFileB; + + ChannelConfigurationA = new(ConfigureNode.ProbeConfigurationA.ChannelConfiguration) + { + TopLevel = false, + FormBorderStyle = FormBorderStyle.None, + Dock = DockStyle.Fill, + Parent = this, + Tag = NeuropixelsV2Probe.ProbeA + }; + + panelProbeA.Controls.Add(ChannelConfigurationA); + this.AddMenuItemsFromDialogToFileOption(ChannelConfigurationA, "Probe A"); + + if (!File.Exists(textBoxProbeCalibrationFileA.Text)) + { + panelProbeA.Visible = false; + } + + ChannelConfigurationB = new(ConfigureNode.ProbeConfigurationB.ChannelConfiguration) + { + TopLevel = false, + FormBorderStyle = FormBorderStyle.None, + Dock = DockStyle.Fill, + Parent = this, + Tag = NeuropixelsV2Probe.ProbeB + }; + + panelProbeB.Controls.Add(ChannelConfigurationB); + this.AddMenuItemsFromDialogToFileOption(ChannelConfigurationB, "Probe B"); + + if (!File.Exists(textBoxProbeCalibrationFileB.Text)) + { + panelProbeB.Visible = false; + } + + ChannelConfigurationA.OnZoom += UpdateTrackBarLocation; + ChannelConfigurationA.OnFileLoad += UpdateChannelPresetIndex; + + comboBoxReferenceA.DataSource = Enum.GetValues(typeof(NeuropixelsV2QuadShankReference)); + comboBoxReferenceA.SelectedItem = ConfigureNode.ProbeConfigurationA.Reference; + + ChannelConfigurationB.OnZoom += UpdateTrackBarLocation; + ChannelConfigurationB.OnFileLoad += UpdateChannelPresetIndex; + + comboBoxReferenceB.DataSource = Enum.GetValues(typeof(NeuropixelsV2QuadShankReference)); + comboBoxReferenceB.SelectedItem = ConfigureNode.ProbeConfigurationB.Reference; + + comboBoxChannelPresetsA.DataSource = Enum.GetValues(typeof(ChannelPreset)); + comboBoxChannelPresetsA.SelectedIndexChanged += SelectedIndexChanged; + CheckForExistingChannelPreset(NeuropixelsV2Probe.ProbeA); + + comboBoxChannelPresetsB.DataSource = Enum.GetValues(typeof(ChannelPreset)); + comboBoxChannelPresetsB.SelectedIndexChanged += SelectedIndexChanged; + CheckForExistingChannelPreset(NeuropixelsV2Probe.ProbeB); + } + + private void FormShown(object sender, EventArgs e) + { + if (!TopLevel) + { + splitContainer1.Panel2Collapsed = true; + splitContainer1.Panel2.Hide(); + + menuStrip.Visible = false; + } + + ChannelConfigurationA.Show(); + ChannelConfigurationB.Show(); + + ChannelConfigurationA.ConnectResizeEventHandler(); + ChannelConfigurationB.ConnectResizeEventHandler(); + } + + private void FileTextChanged(object sender, EventArgs e) + { + if (sender is TextBox textBox && textBox != null) + { + if (textBox.Name == nameof(textBoxProbeCalibrationFileA)) + { + ConfigureNode.GainCalibrationFileA = textBox.Text; + ParseGainCalibrationFile("A"); + } + else if (textBox.Name == nameof(textBoxProbeCalibrationFileB)) + { + ConfigureNode.GainCalibrationFileB = textBox.Text; + ParseGainCalibrationFile("B"); + } + } + } + + private void ParseGainCalibrationFile(string probe) + { + if (probe == "A") + { + if (ConfigureNode.GainCalibrationFileA != null && ConfigureNode.GainCalibrationFileA != "") + { + if (File.Exists(ConfigureNode.GainCalibrationFileA)) + { + StreamReader gainCalibrationFile = new(ConfigureNode.GainCalibrationFileA); + + probeSnA.Text = ulong.Parse(gainCalibrationFile.ReadLine()).ToString(); + + gainA.Text = NeuropixelsV2Helper.ParseGainCalibrationFile(gainCalibrationFile).ToString(); + + gainCalibrationFile.Close(); + } + } + } + else if (probe == "B") + { + if (ConfigureNode.GainCalibrationFileB != null && ConfigureNode.GainCalibrationFileB != "") + { + if (File.Exists(ConfigureNode.GainCalibrationFileB)) + { + StreamReader gainCalibrationFile = new(ConfigureNode.GainCalibrationFileB); + + probeSnB.Text = ulong.Parse(gainCalibrationFile.ReadLine()).ToString(); + + gainB.Text = NeuropixelsV2Helper.ParseGainCalibrationFile(gainCalibrationFile).ToString(); + + gainCalibrationFile.Close(); + } + } + } + } + + private void SelectedIndexChanged(object sender, EventArgs e) + { + var comboBox = sender as ComboBox; + + if (comboBox.Name == nameof(comboBoxReferenceA)) + { + ConfigureNode.ProbeConfigurationA.Reference = (NeuropixelsV2QuadShankReference)comboBox.SelectedItem; + } + else if (comboBox.Name == nameof(comboBoxReferenceB)) + { + ConfigureNode.ProbeConfigurationB.Reference = (NeuropixelsV2QuadShankReference)comboBox.SelectedItem; + } + else if (comboBox.Name == nameof(comboBoxChannelPresetsA)) + { + if ((ChannelPreset)comboBox.SelectedItem != ChannelPreset.None) + { + SetChannelPreset((ChannelPreset)comboBox.SelectedItem, NeuropixelsV2Probe.ProbeA); + } + } + else if (comboBox.Name == nameof(comboBoxChannelPresetsB)) + { + if ((ChannelPreset)comboBox.SelectedItem != ChannelPreset.None) + { + SetChannelPreset((ChannelPreset)comboBox.SelectedItem, NeuropixelsV2Probe.ProbeB); + } + } + } + + private void SetChannelPreset(ChannelPreset preset, NeuropixelsV2Probe probeSelected) + { + var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; + + var channelMap = channelConfiguration.ChannelMap; + var electrodes = channelConfiguration.Electrodes; + + switch (preset) + { + case ChannelPreset.Shank0BankA: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 0).ToList()); + break; + + case ChannelPreset.Shank0BankB: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 0).ToList()); + break; + + case ChannelPreset.Shank0BankC: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 0).ToList()); + break; + + case ChannelPreset.Shank0BankD: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + e.Shank == 0).ToList()); + break; + + case ChannelPreset.Shank1BankA: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 1).ToList()); + break; + + case ChannelPreset.Shank1BankB: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 1).ToList()); + break; + + case ChannelPreset.Shank1BankC: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 1).ToList()); + break; + + case ChannelPreset.Shank1BankD: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + e.Shank == 1).ToList()); + break; + + case ChannelPreset.Shank2BankA: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 2).ToList()); + break; + + case ChannelPreset.Shank2BankB: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 2).ToList()); + break; + + case ChannelPreset.Shank2BankC: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 2).ToList()); + break; + + case ChannelPreset.Shank2BankD: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + e.Shank == 2).ToList()); + break; + + case ChannelPreset.Shank3BankA: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 3).ToList()); + break; + + case ChannelPreset.Shank3BankB: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 3).ToList()); + break; + + case ChannelPreset.Shank3BankC: + channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 3).ToList()); + break; + + case ChannelPreset.Shank3BankD: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + e.Shank == 3).ToList()); + break; + + case ChannelPreset.AllShanks0_95: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 0 && e.ShankIndex <= 95) || + (e.Shank == 1 && e.ShankIndex >= 0 && e.ShankIndex <= 95) || + (e.Shank == 2 && e.ShankIndex >= 0 && e.ShankIndex <= 95) || + (e.Shank == 3 && e.ShankIndex >= 0 && e.ShankIndex <= 95)).ToList()); + break; + + case ChannelPreset.AllShanks96_191: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 96 && e.ShankIndex <= 191) || + (e.Shank == 1 && e.ShankIndex >= 96 && e.ShankIndex <= 191) || + (e.Shank == 2 && e.ShankIndex >= 96 && e.ShankIndex <= 191) || + (e.Shank == 3 && e.ShankIndex >= 96 && e.ShankIndex <= 191)).ToList()); + break; + + case ChannelPreset.AllShanks192_287: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 192 && e.ShankIndex <= 287) || + (e.Shank == 1 && e.ShankIndex >= 192 && e.ShankIndex <= 287) || + (e.Shank == 2 && e.ShankIndex >= 192 && e.ShankIndex <= 287) || + (e.Shank == 3 && e.ShankIndex >= 192 && e.ShankIndex <= 287)).ToList()); + break; + + case ChannelPreset.AllShanks288_383: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 288 && e.ShankIndex <= 383) || + (e.Shank == 1 && e.ShankIndex >= 288 && e.ShankIndex <= 383) || + (e.Shank == 2 && e.ShankIndex >= 288 && e.ShankIndex <= 383) || + (e.Shank == 3 && e.ShankIndex >= 288 && e.ShankIndex <= 383)).ToList()); + break; + + case ChannelPreset.AllShanks384_479: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 384 && e.ShankIndex <= 479) || + (e.Shank == 1 && e.ShankIndex >= 384 && e.ShankIndex <= 479) || + (e.Shank == 2 && e.ShankIndex >= 384 && e.ShankIndex <= 479) || + (e.Shank == 3 && e.ShankIndex >= 384 && e.ShankIndex <= 479)).ToList()); + break; + + case ChannelPreset.AllShanks480_575: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 480 && e.ShankIndex <= 575) || + (e.Shank == 1 && e.ShankIndex >= 480 && e.ShankIndex <= 575) || + (e.Shank == 2 && e.ShankIndex >= 480 && e.ShankIndex <= 575) || + (e.Shank == 3 && e.ShankIndex >= 480 && e.ShankIndex <= 575)).ToList()); + break; + + case ChannelPreset.AllShanks576_671: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 576 && e.ShankIndex <= 671) || + (e.Shank == 1 && e.ShankIndex >= 576 && e.ShankIndex <= 671) || + (e.Shank == 2 && e.ShankIndex >= 576 && e.ShankIndex <= 671) || + (e.Shank == 3 && e.ShankIndex >= 576 && e.ShankIndex <= 671)).ToList()); + break; + + case ChannelPreset.AllShanks672_767: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 672 && e.ShankIndex <= 767) || + (e.Shank == 1 && e.ShankIndex >= 672 && e.ShankIndex <= 767) || + (e.Shank == 2 && e.ShankIndex >= 672 && e.ShankIndex <= 767) || + (e.Shank == 3 && e.ShankIndex >= 672 && e.ShankIndex <= 767)).ToList()); + break; + + case ChannelPreset.AllShanks768_863: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 768 && e.ShankIndex <= 863) || + (e.Shank == 1 && e.ShankIndex >= 768 && e.ShankIndex <= 863) || + (e.Shank == 2 && e.ShankIndex >= 768 && e.ShankIndex <= 863) || + (e.Shank == 3 && e.ShankIndex >= 768 && e.ShankIndex <= 863)).ToList()); + break; + + case ChannelPreset.AllShanks864_959: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 864 && e.ShankIndex <= 959) || + (e.Shank == 1 && e.ShankIndex >= 864 && e.ShankIndex <= 959) || + (e.Shank == 2 && e.ShankIndex >= 864 && e.ShankIndex <= 959) || + (e.Shank == 3 && e.ShankIndex >= 864 && e.ShankIndex <= 959)).ToList()); + break; + + case ChannelPreset.AllShanks960_1055: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 960 && e.ShankIndex <= 1055) || + (e.Shank == 1 && e.ShankIndex >= 960 && e.ShankIndex <= 1055) || + (e.Shank == 2 && e.ShankIndex >= 960 && e.ShankIndex <= 1055) || + (e.Shank == 3 && e.ShankIndex >= 960 && e.ShankIndex <= 1055)).ToList()); + break; + + case ChannelPreset.AllShanks1056_1151: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 1056 && e.ShankIndex <= 1151) || + (e.Shank == 1 && e.ShankIndex >= 1056 && e.ShankIndex <= 1151) || + (e.Shank == 2 && e.ShankIndex >= 1056 && e.ShankIndex <= 1151) || + (e.Shank == 3 && e.ShankIndex >= 1056 && e.ShankIndex <= 1151)).ToList()); + break; + + case ChannelPreset.AllShanks1152_1247: + channelMap.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.ShankIndex >= 1152 && e.ShankIndex <= 1247) || + (e.Shank == 1 && e.ShankIndex >= 1152 && e.ShankIndex <= 1247) || + (e.Shank == 2 && e.ShankIndex >= 1152 && e.ShankIndex <= 1247) || + (e.Shank == 3 && e.ShankIndex >= 1152 && e.ShankIndex <= 1247)).ToList()); + break; + } + + channelConfiguration.HighlightEnabledContacts(); + channelConfiguration.HighlightSelectedContacts(); + channelConfiguration.UpdateContactLabels(); + channelConfiguration.RefreshZedGraph(); + } + + private void CheckForExistingChannelPreset(NeuropixelsV2Probe probeSelected) + { + var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; + var comboBox = probeSelected == NeuropixelsV2Probe.ProbeA ? comboBoxChannelPresetsA : comboBoxChannelPresetsB; + + var channelMap = channelConfiguration.ChannelMap; + + if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 0)) + { + comboBox.SelectedItem = ChannelPreset.Shank0BankA; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 0)) + { + comboBox.SelectedItem = ChannelPreset.Shank0BankB; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 0)) + { + comboBox.SelectedItem = ChannelPreset.Shank0BankC; + } + else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + e.Shank == 0)) + { + comboBox.SelectedItem = ChannelPreset.Shank0BankD; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 1)) + { + comboBox.SelectedItem = ChannelPreset.Shank1BankA; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 1)) + { + comboBox.SelectedItem = ChannelPreset.Shank1BankB; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 1)) + { + comboBox.SelectedItem = ChannelPreset.Shank1BankC; + } + else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + e.Shank == 1)) + { + comboBox.SelectedItem = ChannelPreset.Shank1BankD; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 2)) + { + comboBox.SelectedItem = ChannelPreset.Shank2BankA; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 2)) + { + comboBox.SelectedItem = ChannelPreset.Shank2BankB; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 2)) + { + comboBox.SelectedItem = ChannelPreset.Shank2BankC; + } + else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + e.Shank == 2)) + { + comboBox.SelectedItem = ChannelPreset.Shank2BankD; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 3)) + { + comboBox.SelectedItem = ChannelPreset.Shank3BankA; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 3)) + { + comboBox.SelectedItem = ChannelPreset.Shank3BankB; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 3)) + { + comboBox.SelectedItem = ChannelPreset.Shank3BankC; + } + else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + e.Shank == 3)) + { + comboBox.SelectedItem = ChannelPreset.Shank3BankD; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 0 && e.ShankIndex <= 95) || + (e.Shank == 1 && e.ShankIndex >= 0 && e.ShankIndex <= 95) || + (e.Shank == 2 && e.ShankIndex >= 0 && e.ShankIndex <= 95) || + (e.Shank == 3 && e.ShankIndex >= 0 && e.ShankIndex <= 95))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks0_95; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 192 && e.ShankIndex <= 287) || + (e.Shank == 1 && e.ShankIndex >= 192 && e.ShankIndex <= 287) || + (e.Shank == 2 && e.ShankIndex >= 192 && e.ShankIndex <= 287) || + (e.Shank == 3 && e.ShankIndex >= 192 && e.ShankIndex <= 287))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks192_287; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 288 && e.ShankIndex <= 383) || + (e.Shank == 1 && e.ShankIndex >= 288 && e.ShankIndex <= 383) || + (e.Shank == 2 && e.ShankIndex >= 288 && e.ShankIndex <= 383) || + (e.Shank == 3 && e.ShankIndex >= 288 && e.ShankIndex <= 383))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks288_383; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 394 && e.ShankIndex <= 479) || + (e.Shank == 1 && e.ShankIndex >= 394 && e.ShankIndex <= 479) || + (e.Shank == 2 && e.ShankIndex >= 394 && e.ShankIndex <= 479) || + (e.Shank == 3 && e.ShankIndex >= 394 && e.ShankIndex <= 479))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks384_479; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 480 && e.ShankIndex <= 575) || + (e.Shank == 1 && e.ShankIndex >= 480 && e.ShankIndex <= 575) || + (e.Shank == 2 && e.ShankIndex >= 480 && e.ShankIndex <= 575) || + (e.Shank == 3 && e.ShankIndex >= 480 && e.ShankIndex <= 575))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks480_575; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 576 && e.ShankIndex <= 671) || + (e.Shank == 1 && e.ShankIndex >= 576 && e.ShankIndex <= 671) || + (e.Shank == 2 && e.ShankIndex >= 576 && e.ShankIndex <= 671) || + (e.Shank == 3 && e.ShankIndex >= 576 && e.ShankIndex <= 671))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks576_671; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 672 && e.ShankIndex <= 767) || + (e.Shank == 1 && e.ShankIndex >= 672 && e.ShankIndex <= 767) || + (e.Shank == 2 && e.ShankIndex >= 672 && e.ShankIndex <= 767) || + (e.Shank == 3 && e.ShankIndex >= 672 && e.ShankIndex <= 767))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks672_767; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 768 && e.ShankIndex <= 863) || + (e.Shank == 1 && e.ShankIndex >= 768 && e.ShankIndex <= 863) || + (e.Shank == 2 && e.ShankIndex >= 768 && e.ShankIndex <= 863) || + (e.Shank == 3 && e.ShankIndex >= 768 && e.ShankIndex <= 863))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks768_863; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 864 && e.ShankIndex <= 959) || + (e.Shank == 1 && e.ShankIndex >= 864 && e.ShankIndex <= 959) || + (e.Shank == 2 && e.ShankIndex >= 864 && e.ShankIndex <= 959) || + (e.Shank == 3 && e.ShankIndex >= 864 && e.ShankIndex <= 959))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks864_959; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 960 && e.ShankIndex <= 1055) || + (e.Shank == 1 && e.ShankIndex >= 960 && e.ShankIndex <= 1055) || + (e.Shank == 2 && e.ShankIndex >= 960 && e.ShankIndex <= 1055) || + (e.Shank == 3 && e.ShankIndex >= 960 && e.ShankIndex <= 1055))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks960_1055; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 1056 && e.ShankIndex <= 1151) || + (e.Shank == 1 && e.ShankIndex >= 1056 && e.ShankIndex <= 1151) || + (e.Shank == 2 && e.ShankIndex >= 1056 && e.ShankIndex <= 1151) || + (e.Shank == 3 && e.ShankIndex >= 1056 && e.ShankIndex <= 1151))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks1056_1151; + } + else if (channelMap.All(e => (e.Shank == 0 && e.ShankIndex >= 1152 && e.ShankIndex <= 1247) || + (e.Shank == 1 && e.ShankIndex >= 1152 && e.ShankIndex <= 1247) || + (e.Shank == 2 && e.ShankIndex >= 1152 && e.ShankIndex <= 1247) || + (e.Shank == 3 && e.ShankIndex >= 1152 && e.ShankIndex <= 1247))) + { + comboBox.SelectedItem = ChannelPreset.AllShanks1152_1247; + } + else + { + comboBox.SelectedItem = ChannelPreset.None; + } + } + + private void UpdateChannelPresetIndex(object sender, EventArgs e) + { + if (sender is ChannelConfigurationDialog dialog) + { + if (dialog.Tag is NeuropixelsV2Probe probe) + { + CheckForExistingChannelPreset(probe); + } + } + + } + + private void LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + try + { + System.Diagnostics.Process.Start("https://open-ephys.github.io/onix-docs/Software%20Guide/Bonsai.ONIX/Nodes/NeuropixelsV1eDevice.html"); + } + catch (Exception) + { + MessageBox.Show("Unable to open documentation link."); + } + } + + private void ButtonClick(object sender, EventArgs e) + { + const float zoomFactor = 8f; + + if (sender is Button button && button != null) + { + if (button.Name == nameof(buttonOkay)) + { + NeuropixelsV2eProbeGroup.UpdateProbeGroup(ChannelConfigurationA.ChannelMap, ConfigureNode.ProbeConfigurationA.ChannelConfiguration); + NeuropixelsV2eProbeGroup.UpdateProbeGroup(ChannelConfigurationB.ChannelMap, ConfigureNode.ProbeConfigurationB.ChannelConfiguration); + + DialogResult = DialogResult.OK; + } + else if (button.Name == nameof(buttonCancel)) + { + DialogResult = DialogResult.Cancel; + } + else if (button.Name == nameof(buttonGainCalibrationFileA)) + { + var ofd = new OpenFileDialog() + { + CheckFileExists = true, + Filter = "Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv|All Files|*.*", + FilterIndex = 0 + }; + + if (ofd.ShowDialog() == DialogResult.OK) + { + textBoxProbeCalibrationFileA.Text = ofd.FileName; + panelProbeA.Visible = true; + } + else + { + panelProbeA.Visible = File.Exists(textBoxProbeCalibrationFileA.Text); + } + } + else if (button.Name == nameof(buttonGainCalibrationFileB)) + { + var ofd = new OpenFileDialog() + { + CheckFileExists = true, + Filter = "Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv|All Files|*.*", + FilterIndex = 0 + }; + + if (ofd.ShowDialog() == DialogResult.OK) + { + textBoxProbeCalibrationFileB.Text = ofd.FileName; + panelProbeB.Visible = true; + } + else + { + panelProbeB.Visible = File.Exists(textBoxProbeCalibrationFileB.Text); + } + } + else if (button.Name == nameof(buttonZoomIn)) + { + var probeSelected = tabControlProbe.SelectedTab == tabPageProbeA ? NeuropixelsV2Probe.ProbeA : NeuropixelsV2Probe.ProbeB; + + ZoomIn(zoomFactor, probeSelected); + } + else if (button.Name == nameof(buttonZoomOut)) + { + var probeSelected = tabControlProbe.SelectedTab == tabPageProbeA ? NeuropixelsV2Probe.ProbeA : NeuropixelsV2Probe.ProbeB; + + ZoomOut(1 / zoomFactor, probeSelected); + } + else if (button.Name == nameof(buttonResetZoom)) + { + var probeSelected = tabControlProbe.SelectedTab == tabPageProbeA ? NeuropixelsV2Probe.ProbeA : NeuropixelsV2Probe.ProbeB; + + ResetZoom(probeSelected); + } + else if (button.Name == nameof(buttonClearSelections)) + { + var channelConfiguration = tabControlProbe.SelectedTab == tabPageProbeA ? ChannelConfigurationA : ChannelConfigurationB; + + channelConfiguration.SetAllSelections(false); + channelConfiguration.HighlightEnabledContacts(); + channelConfiguration.HighlightSelectedContacts(); + channelConfiguration.UpdateContactLabels(); + channelConfiguration.RefreshZedGraph(); + } + else if (button.Name == nameof(buttonEnableContacts)) + { + var channelConfiguration = tabControlProbe.SelectedTab == tabPageProbeA ? ChannelConfigurationA : ChannelConfigurationB; + + EnableSelectedContacts((NeuropixelsV2Probe)channelConfiguration.Tag); + + channelConfiguration.SetAllSelections(false); + channelConfiguration.HighlightEnabledContacts(); + channelConfiguration.HighlightSelectedContacts(); + channelConfiguration.UpdateContactLabels(); + channelConfiguration.RefreshZedGraph(); + } + } + } + + private void EnableSelectedContacts(NeuropixelsV2Probe probeSelected) + { + var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; + + if (channelConfiguration.SelectedContacts.Length != channelConfiguration.Electrodes.Count) + throw new Exception("Invalid number of contacts versus electrodes found."); + + var selectedElectrodes = channelConfiguration.Electrodes + .Where((e, ind) => channelConfiguration.SelectedContacts[ind]) + .ToList(); + + channelConfiguration.EnableElectrodes(selectedElectrodes); + + CheckForExistingChannelPreset(probeSelected); + } + + private void ZoomIn(double zoom, NeuropixelsV2Probe probeSelected) + { + if (zoom <= 1) + { + throw new ArgumentOutOfRangeException($"Argument {nameof(zoom)} must be greater than 1.0 to zoom in"); + } + + var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; + + channelConfiguration.ManualZoom(zoom); + channelConfiguration.RefreshZedGraph(); + } + + private void ZoomOut(double zoom, NeuropixelsV2Probe probeSelected) + { + if (zoom >= 1) + { + throw new ArgumentOutOfRangeException($"Argument {nameof(zoom)} must be less than 1.0 to zoom out"); + } + + var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; + + channelConfiguration.ManualZoom(zoom); + channelConfiguration.RefreshZedGraph(); + } + + private void ResetZoom(NeuropixelsV2Probe probeSelected) + { + var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; + + channelConfiguration.ResetZoom(); + channelConfiguration.RefreshZedGraph(); + } + + private void MoveToVerticalPosition(float relativePosition, NeuropixelsV2Probe probeSelected) + { + var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; + + channelConfiguration.MoveToVerticalPosition(relativePosition); + channelConfiguration.RefreshZedGraph(); + } + + private void TrackBarScroll(object sender, EventArgs e) + { + if (sender is TrackBar trackBar && trackBar != null) + { + if (trackBar.Name == nameof(trackBarProbePosition)) + { + var probeSelected = tabControlProbe.SelectedTab == tabPageProbeA ? NeuropixelsV2Probe.ProbeA : NeuropixelsV2Probe.ProbeB; + + MoveToVerticalPosition(trackBar.Value / 100.0f, probeSelected); + } + } + } + + private void UpdateTrackBarLocation(object sender, EventArgs e) + { + var channelConfiguration = tabControlProbe.SelectedTab == tabPageProbeA ? ChannelConfigurationA : ChannelConfigurationB; + + trackBarProbePosition.Value = (int)(channelConfiguration.GetRelativeVerticalPosition() * 100); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx new file mode 100644 index 00000000..447103fd --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + 17, 17 + + + 165, 17 + + + False + + + False + + \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eEditor.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eEditor.cs new file mode 100644 index 00000000..f37ff17e --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eEditor.cs @@ -0,0 +1,35 @@ +using System; +using System.ComponentModel; +using System.Windows.Forms; +using Bonsai.Design; + +namespace OpenEphys.Onix.Design +{ + public class NeuropixelsV2eEditor : WorkflowComponentEditor + { + public override bool EditComponent(ITypeDescriptorContext context, object component, IServiceProvider provider, IWin32Window owner) + { + if (provider != null) + { + var editorState = (IWorkflowEditorState)provider.GetService(typeof(IWorkflowEditorState)); + if (editorState != null && !editorState.WorkflowRunning && component is ConfigureNeuropixelsV2e configureNeuropixelsV2e) + { + using var editorDialog = new NeuropixelsV2eDialog(configureNeuropixelsV2e); + + if (editorDialog.ShowDialog() == DialogResult.OK) + { + configureNeuropixelsV2e.Enable = editorDialog.ConfigureNode.Enable; + configureNeuropixelsV2e.GainCalibrationFileA = editorDialog.ConfigureNode.GainCalibrationFileA; + configureNeuropixelsV2e.GainCalibrationFileB = editorDialog.ConfigureNode.GainCalibrationFileB; + configureNeuropixelsV2e.ProbeConfigurationA = editorDialog.ConfigureNode.ProbeConfigurationA; + configureNeuropixelsV2e.ProbeConfigurationB = editorDialog.ConfigureNode.ProbeConfigurationB; + + return true; + } + } + } + + return false; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj b/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj index 09d53a7c..c702bc4c 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj @@ -10,7 +10,7 @@ - + @@ -20,4 +20,19 @@ + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs index e2e402a5..e2fe0b94 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs @@ -5,6 +5,7 @@ namespace OpenEphys.Onix { + [Editor("OpenEphys.Onix.Design.NeuropixelsV2eEditor, OpenEphys.Onix.Design", typeof(ComponentEditor))] public class ConfigureNeuropixelsV2e : SingleDeviceFactory { public ConfigureNeuropixelsV2e() @@ -12,13 +13,25 @@ public ConfigureNeuropixelsV2e() { } + public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) + : base(typeof(NeuropixelsV2e)) + { + Enable = configureNode.Enable; + ProbeConfigurationA = configureNode.ProbeConfigurationA; + ProbeConfigurationB = configureNode.ProbeConfigurationB; + GainCalibrationFileA = configureNode.GainCalibrationFileA; + GainCalibrationFileB = configureNode.GainCalibrationFileB; + DeviceName = configureNode.DeviceName; + DeviceAddress = configureNode.DeviceAddress; + } + [Category(ConfigurationCategory)] [Description("Specifies whether the NeuropixelsV2 device is enabled.")] public bool Enable { get; set; } = true; [Category(ConfigurationCategory)] [Description("Probe A electrode configuration.")] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new(); [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe A.")] @@ -27,7 +40,7 @@ public ConfigureNeuropixelsV2e() [Category(ConfigurationCategory)] [Description("Probe B electrode configuration.")] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new(); [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe B.")] diff --git a/OpenEphys.Onix/OpenEphys.Onix/Electrode.cs b/OpenEphys.Onix/OpenEphys.Onix/Electrode.cs new file mode 100644 index 00000000..c415fdd2 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/Electrode.cs @@ -0,0 +1,38 @@ +using System.Drawing; +using System.Xml.Serialization; + +namespace OpenEphys.Onix +{ + public abstract class Electrode + { + /// + /// Index of the electrode within the context of the probe + /// + [XmlIgnore] + public int ElectrodeNumber { get; internal set; } + /// + /// The shank this electrode belongs to + /// + [XmlIgnore] + public int Shank { get; internal set; } + /// + /// Index of the electrode within this shank + /// + [XmlIgnore] + public int ShankIndex { get; internal set; } + /// + /// The bank, or logical block of channels, this electrode belongs to + /// + [XmlIgnore] + public int Channel { get; internal set; } + /// + /// Location of the electrode in two-dimensional space + /// + [XmlIgnore] + public PointF Position { get; internal set; } + + public Electrode() + { + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2.cs index 3488fdc5..27e9f9a1 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2.cs @@ -19,6 +19,7 @@ static class NeuropixelsV2 public const int ChannelCount = 384; public const int BaseBitsPerChannel = 4; public const int ElectrodePerShank = 1280; + public const int ElectrodePerBlock = 48; public const int ReferencePixelCount = 4; public const int DummyRegisterCount = 4; public const int RegistersPerShank = ElectrodePerShank + ReferencePixelCount + DummyRegisterCount; diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Helper.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Helper.cs new file mode 100644 index 00000000..018b2895 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Helper.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Linq; + +namespace OpenEphys.Onix +{ + public class NeuropixelsV2Helper + { + public static double ParseGainCalibrationFile(StreamReader file) + { + if (file != null) + { + var gainCalibration = file.ReadLine().Split(',').Skip(1).FirstOrDefault(); + + if (double.TryParse(gainCalibration, out var gain)) + { + return gain; + } + else + { + throw new FormatException($"Gain calibration file is improperly formatted: `{gainCalibration}` is an invalid line."); + } + } + + throw new ArgumentNullException("Invalid stream reader, check that the file name is correct."); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs new file mode 100644 index 00000000..acf80703 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs @@ -0,0 +1,100 @@ +using System; +using System.Drawing; +using System.Xml.Serialization; +using OpenEphys.ProbeInterface; + +namespace OpenEphys.Onix +{ + public class NeuropixelsV2QuadShankElectrode : Electrode + { + /// + /// The bank, or logical block of channels, this electrode belongs to + /// + [XmlIgnore] + public NeuropixelsV2QuadShankBank Bank { get; private set; } + + public int Block { get; private set; } + public int BlockIndex { get; private set; } + + public NeuropixelsV2QuadShankElectrode(int electrodeNumber) + { + ElectrodeNumber = electrodeNumber; + Shank = electrodeNumber / NeuropixelsV2.ElectrodePerShank; + ShankIndex = electrodeNumber % NeuropixelsV2.ElectrodePerShank; + Bank = (NeuropixelsV2QuadShankBank)(ShankIndex / NeuropixelsV2.ChannelCount); + Block = ShankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; + BlockIndex = ShankIndex % NeuropixelsV2.ElectrodePerBlock; + Channel = GetChannelNumber(Shank, Block, BlockIndex); + Position = GetPosition(electrodeNumber); + } + + public NeuropixelsV2QuadShankElectrode(Contact contact) + { + ElectrodeNumber = contact.Index; + Shank = int.TryParse(contact.ShankId, out int result) ? result : ElectrodeNumber / NeuropixelsV2.ElectrodePerShank; + ShankIndex = ElectrodeNumber % NeuropixelsV2.ElectrodePerShank; + Bank = (NeuropixelsV2QuadShankBank)(ShankIndex / NeuropixelsV2.ChannelCount); + Block = ShankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; + BlockIndex = ShankIndex % NeuropixelsV2.ElectrodePerBlock; + Channel = GetChannelNumber(Shank, Block, BlockIndex); + Position = GetPosition(ElectrodeNumber); + } + + private PointF GetPosition(int electrodeNumber) + { + var position = NeuropixelsV2eProbeGroup.DefaultContactPosition(electrodeNumber); + return new PointF(x: position[0], y: position[1]); + } + + public static int GetChannelNumber(int electrodeNumber) + { + var shank = electrodeNumber / NeuropixelsV2.ElectrodePerShank; + var shankIndex = electrodeNumber % NeuropixelsV2.ElectrodePerShank; + var block = shankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; + var blockIndex = shankIndex % NeuropixelsV2.ElectrodePerBlock; + + return GetChannelNumber(shank, block, blockIndex); + } + + internal static int GetChannelNumber(int shank, int block, int blockIndex) => (shank, block) switch + { + (0, 0) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 0, + (0, 1) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 2, + (0, 2) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 4, + (0, 3) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 6, + (0, 4) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 5, + (0, 5) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 7, + (0, 6) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 1, + (0, 7) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 3, + + (1, 0) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 1, + (1, 1) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 3, + (1, 2) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 5, + (1, 3) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 7, + (1, 4) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 4, + (1, 5) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 6, + (1, 6) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 0, + (1, 7) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 2, + + (2, 0) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 4, + (2, 1) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 6, + (2, 2) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 0, + (2, 3) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 2, + (2, 4) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 1, + (2, 5) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 3, + (2, 6) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 5, + (2, 7) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 7, + + (3, 0) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 5, + (3, 1) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 7, + (3, 2) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 1, + (3, 3) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 3, + (3, 4) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 0, + (3, 5) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 2, + (3, 6) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 4, + (3, 7) => blockIndex + NeuropixelsV2.ElectrodePerBlock* 6, + + _ => throw new ArgumentOutOfRangeException($"Invalid shank and/or electrode value: {(shank, block)}"), + }; + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankProbeConfiguration.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankProbeConfiguration.cs index e4c6c9da..dd9409e7 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankProbeConfiguration.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankProbeConfiguration.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; -using System.Drawing; -using System.Linq; +using System.ComponentModel; +using Bonsai; +using Newtonsoft.Json; +using System.Text; using System.Xml.Serialization; namespace OpenEphys.Onix @@ -24,119 +26,61 @@ public enum NeuropixelsV2QuadShankBank public class NeuropixelsV2QuadShankProbeConfiguration { - public static readonly IReadOnlyList ProbeModel = CreateProbeModel(); - - public NeuropixelsV2QuadShankProbeConfiguration() - { - ChannelMap = new List(NeuropixelsV2.ChannelCount); - for (int i = 0; i < NeuropixelsV2.ChannelCount; i++) - { - ChannelMap.Add(ProbeModel.FirstOrDefault(e => e.Channel == i)); - } - } - - private static List CreateProbeModel() - { - var electrodes = new List(NeuropixelsV2.ElectrodePerShank * 4); - for (int i = 0; i < NeuropixelsV2.ElectrodePerShank * 4; i++) - { - electrodes.Add(new NeuropixelsV2QuadShankElectrode() { ElectrodeNumber = i }); - } - return electrodes; - } + //public static readonly IReadOnlyList ProbeModel = CreateProbeModel(); public NeuropixelsV2QuadShankReference Reference { get; set; } = NeuropixelsV2QuadShankReference.External; + [XmlIgnore] public List ChannelMap { get; } - public void SelectElectrodes(List electrodes) + + [XmlIgnore] + [Category("Configuration")] + [Description("Defines the shape of the probe, and which contacts are currently selected for streaming")] + public NeuropixelsV2eProbeGroup ChannelConfiguration { get; private set; } = new(); + + [Browsable(false)] + [Externalizable(false)] + [XmlElement(nameof(ChannelConfiguration))] + public string ChannelConfigurationString { - foreach (var e in electrodes) + get { - ChannelMap[e.Channel] = e; + var jsonString = JsonConvert.SerializeObject(ChannelConfiguration); + return Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString)); } - } - } - - public class NeuropixelsV2QuadShankElectrode - { - private int electrodeNumber = 0; - - public int ElectrodeNumber - { - get => electrodeNumber; set { - electrodeNumber = value; - Shank = electrodeNumber / NeuropixelsV2.ElectrodePerShank; - IntraShankElectrodeIndex = electrodeNumber % NeuropixelsV2.ElectrodePerShank; - - Position = new PointF(x: electrodeNumber % 2 * 32.0f + 8.0f, y: (IntraShankElectrodeIndex - (IntraShankElectrodeIndex % 2)) * 7.5f); - - if (IntraShankElectrodeIndex < 384) - Bank = NeuropixelsV2QuadShankBank.A; - else if (IntraShankElectrodeIndex >= 384 && IntraShankElectrodeIndex < 768) - Bank = NeuropixelsV2QuadShankBank.B; - else if (IntraShankElectrodeIndex >= 768 && IntraShankElectrodeIndex < 1152) - Bank = NeuropixelsV2QuadShankBank.C; - else - Bank = NeuropixelsV2QuadShankBank.D; - - var block = IntraShankElectrodeIndex % 384 / 48; - var blockIndex = IntraShankElectrodeIndex % 48; - - Channel = (Shank, block) switch - { - (0, 0) => blockIndex + 48 * 0, - (0, 1) => blockIndex + 48 * 2, - (0, 2) => blockIndex + 48 * 4, - (0, 3) => blockIndex + 48 * 6, - (0, 4) => blockIndex + 48 * 5, - (0, 5) => blockIndex + 48 * 7, - (0, 6) => blockIndex + 48 * 1, - (0, 7) => blockIndex + 48 * 3, - - (1, 0) => blockIndex + 48 * 1, - (1, 1) => blockIndex + 48 * 3, - (1, 2) => blockIndex + 48 * 5, - (1, 3) => blockIndex + 48 * 7, - (1, 4) => blockIndex + 48 * 4, - (1, 5) => blockIndex + 48 * 6, - (1, 6) => blockIndex + 48 * 0, - (1, 7) => blockIndex + 48 * 2, - - (2, 0) => blockIndex + 48 * 4, - (2, 1) => blockIndex + 48 * 6, - (2, 2) => blockIndex + 48 * 0, - (2, 3) => blockIndex + 48 * 2, - (2, 4) => blockIndex + 48 * 1, - (2, 5) => blockIndex + 48 * 3, - (2, 6) => blockIndex + 48 * 5, - (2, 7) => blockIndex + 48 * 7, - - (3, 0) => blockIndex + 48 * 5, - (3, 1) => blockIndex + 48 * 7, - (3, 2) => blockIndex + 48 * 1, - (3, 3) => blockIndex + 48 * 3, - (3, 4) => blockIndex + 48 * 0, - (3, 5) => blockIndex + 48 * 2, - (3, 6) => blockIndex + 48 * 4, - (3, 7) => blockIndex + 48 * 6, - - _ => throw new ArgumentOutOfRangeException($"Invalid shank and/or electrode value: {(Shank, IntraShankElectrodeIndex)}"), - }; + var jsonString = Encoding.UTF8.GetString(Convert.FromBase64String(value)); + ChannelConfiguration = JsonConvert.DeserializeObject(jsonString); } } - [XmlIgnore] - public int Channel { get; private set; } = 0; - [XmlIgnore] - public int Shank { get; private set; } = 0; - [XmlIgnore] - public int IntraShankElectrodeIndex { get; private set; } = 0; - [XmlIgnore] - public NeuropixelsV2QuadShankBank Bank { get; private set; } = NeuropixelsV2QuadShankBank.A; - [XmlIgnore] - public PointF Position { get; private set; } = new(0f, 0f); + public NeuropixelsV2QuadShankProbeConfiguration() + { + //ChannelMap = new List(NeuropixelsV2.ChannelCount); + //for (int i = 0; i < NeuropixelsV2.ChannelCount; i++) + //{ + // ChannelMap.Add(ProbeModel.FirstOrDefault(e => e.Channel == i)); + //} + } + + //private static List CreateProbeModel() + //{ + // var electrodes = new List(NeuropixelsV2.ElectrodePerShank * 4); + // for (int i = 0; i < NeuropixelsV2.ElectrodePerShank * 4; i++) + // { + // electrodes.Add(new NeuropixelsV2QuadShankElectrode(i)); + // } + // return electrodes; + //} + + //public void SelectElectrodes(List electrodes) + //{ + // foreach (var e in electrodes) + // { + // ChannelMap[e.Channel] = e; + // } + //} } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2RegisterContext.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2RegisterContext.cs index 5c0fcfc7..30c86d36 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2RegisterContext.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2RegisterContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Linq; namespace OpenEphys.Onix { @@ -58,17 +59,17 @@ private void WriteShiftRegister(uint srAddress, BitArray data) //var count = 2; //while (count-- > 0) //{ - // This allows Base shift registers to get a good STATUS, but does not help shank registers. - //WriteByte(NeuropixelsV2.SOFT_RESET, 0xFF); - //WriteByte(NeuropixelsV2.SOFT_RESET, 0x00); + // This allows Base shift registers to get a good STATUS, but does not help shank registers. + //WriteByte(NeuropixelsV2.SOFT_RESET, 0xFF); + //WriteByte(NeuropixelsV2.SOFT_RESET, 0x00); - WriteByte(NeuropixelsV2.SR_LENGTH1, (uint)bytes.Length % 0x100); - WriteByte(NeuropixelsV2.SR_LENGTH2, (uint)bytes.Length / 0x100); + WriteByte(NeuropixelsV2.SR_LENGTH1, (uint)bytes.Length % 0x100); + WriteByte(NeuropixelsV2.SR_LENGTH2, (uint)bytes.Length / 0x100); - foreach (var b in bytes) - { - WriteByte(srAddress, b); - } + foreach (var b in bytes) + { + WriteByte(srAddress, b); + } //} //if (ReadByte(NeuropixelsV2.STATUS) != (uint)NeuropixelsV2Status.SR_OK) @@ -97,10 +98,10 @@ public static BitArray[] GenerateShankBits(NeuropixelsV2QuadShankProbeConfigurat const int PixelOffset = (NeuropixelsV2.ElectrodePerShank - 1) / 2; const int ReferencePixelOffset = 3; - foreach (var c in probe.ChannelMap) - { - var baseIndex = c.IntraShankElectrodeIndex % 2; - var pixelIndex = c.IntraShankElectrodeIndex / 2; + foreach (var c in NeuropixelsV2eProbeGroup.ToChannelMap(probe.ChannelConfiguration)) + { + var baseIndex = c.ShankIndex % 2; + var pixelIndex = c.ShankIndex / 2; pixelIndex = baseIndex == 0 ? pixelIndex + PixelOffset + 2 * ReferencePixelOffset : PixelOffset - pixelIndex + ReferencePixelOffset; @@ -134,7 +135,7 @@ public static BitArray[] GenerateBaseBits(NeuropixelsV2QuadShankProbeConfigurati var configIndex = i % 2; var bitOffset = (382 - i + configIndex) / 2 * NeuropixelsV2.BaseBitsPerChannel; baseBits[configIndex][bitOffset + 0] = false; // standby bit - baseBits[configIndex][bitOffset + referenceBit ] = true; + baseBits[configIndex][bitOffset + referenceBit] = true; } return baseBits; diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs new file mode 100644 index 00000000..5a50e059 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs @@ -0,0 +1,272 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using OpenEphys.ProbeInterface; + +namespace OpenEphys.Onix +{ + public class NeuropixelsV2eProbeGroup : ProbeGroup + { + const float shankOffsetX = 200f; + const float shankWidthX = 70f; + const float shankPitchX = 250f; + const int numberOfShanks = 4; + + public NeuropixelsV2eProbeGroup() + : base("probeinterface", "0.2.21", + new List() + { + new(ProbeNdim._2, + ProbeSiUnits.Um, + new ProbeAnnotations("Neuropixels 2.0e", "IMEC"), + new ContactAnnotations(new string[0]), + DefaultContactPositions(NeuropixelsV2.ElectrodePerShank * numberOfShanks), + Probe.DefaultContactPlaneAxes(NeuropixelsV2.ElectrodePerShank * numberOfShanks), + Probe.DefaultContactShapes(NeuropixelsV2.ElectrodePerShank * numberOfShanks, ContactShape.Square), + Probe.DefaultSquareParams(NeuropixelsV2.ElectrodePerShank * numberOfShanks, 12.0f), + DefaultProbePlanarContourQuadShank(), + DefaultDeviceChannelIndices(NeuropixelsV2.ChannelCount, NeuropixelsV2.ElectrodePerShank * numberOfShanks), + Probe.DefaultContactIds(NeuropixelsV2.ElectrodePerShank * numberOfShanks), + DefaultShankIds(NeuropixelsV2.ElectrodePerShank * numberOfShanks)) + }.ToArray()) + { + } + + [JsonConstructor] + public NeuropixelsV2eProbeGroup(string specification, string version, Probe[] probes) + : base(specification, version, probes) + { + } + + public NeuropixelsV2eProbeGroup(NeuropixelsV2eProbeGroup probeGroup) + : base(probeGroup) + { + } + + public static float[][] DefaultContactPositions(int numberOfChannels) + { + if (numberOfChannels % 2 != 0) + { + throw new ArgumentException("Invalid number of channels given; must be a multiple of two"); + } + + float[][] contactPositions = new float[numberOfChannels][]; + + for (int i = 0; i < numberOfChannels; i++) + { + contactPositions[i] = DefaultContactPosition(i); + } + + return contactPositions; + } + + public static float[] DefaultContactPosition(int index) + { + return new float[2] { ContactPositionX(index), index % NeuropixelsV2.ElectrodePerShank / 2 * 15 + 170 }; + } + + private static float ContactPositionX(int index) + { + var shank = index / NeuropixelsV2.ElectrodePerShank; + var offset = shankOffsetX + (shankWidthX + shankPitchX) * shank + 11; + + return (index % 2) switch + { + 0 => offset + 8.0f, + 1 => offset + 40.0f, + _ => throw new ArgumentException("Invalid index given.") + }; + } + + /// + /// Generates a default planar contour for the probe, based on the given probe index + /// + /// + public static float[][] DefaultProbePlanarContourQuadShank() + { + const int numberOfShanks = 4; + const float shankTipY = 0f; + const float shankBaseY = 155f; + const float shankLengthY = 10000f; + const float probeLengthY = 10155f; + + float[][] probePlanarContour = new float[25][]; + + probePlanarContour[0] = new float[2] { 0f, probeLengthY }; + probePlanarContour[1] = new float[2] { 0f, shankLengthY }; + + for (int i = 0; i < numberOfShanks; i++) + { + probePlanarContour[2 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i, shankLengthY }; + probePlanarContour[3 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i, shankBaseY }; + probePlanarContour[4 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX / 2, shankTipY }; + probePlanarContour[5 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX, shankBaseY }; + probePlanarContour[6 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX, shankLengthY }; + } + + probePlanarContour[22] = new float[2] { shankOffsetX * 2 + (shankWidthX + shankPitchX) * (numberOfShanks - 1) + shankWidthX, shankLengthY }; + probePlanarContour[23] = new float[2] { shankOffsetX * 2 + (shankWidthX + shankPitchX) * (numberOfShanks - 1) + shankWidthX, probeLengthY }; + probePlanarContour[24] = new float[2] { 0f, probeLengthY }; + + return probePlanarContour; + } + + /// + /// Generates a default planar contour for the probe, based on the given probe index + /// + /// + public static float[][] DefaultProbePlanarContourSingleShank() + { + float[][] probePlanarContour = new float[6][]; + + probePlanarContour[0] = new float[2] { -11f, 155f }; + probePlanarContour[1] = new float[2] { 24f, 0f }; + probePlanarContour[2] = new float[2] { 59f, 155f }; + probePlanarContour[3] = new float[2] { 59f, 10000f }; + probePlanarContour[4] = new float[2] { -11f, 10000f }; + probePlanarContour[5] = new float[2] { -11f, 155f }; + + return probePlanarContour; + } + + /// + /// Override of the DefaultDeviceChannelIndices function, which initializes a portion of the + /// device channel indices, and leaves the rest at -1 to indicate they are not actively recorded + /// + /// Number of contacts that are connected for recording + /// Total number of physical contacts on the probe + /// + public static int[] DefaultDeviceChannelIndices(int channelCount, int electrodeCount) + { + int[] deviceChannelIndices = new int[electrodeCount]; + + for (int i = 0; i < channelCount; i++) + { + deviceChannelIndices[i] = NeuropixelsV2QuadShankElectrode.GetChannelNumber(i / NeuropixelsV2.ElectrodePerShank, + i % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock, + i % NeuropixelsV2.ElectrodePerBlock); + } + + for (int i = channelCount; i < electrodeCount; i++) + { + deviceChannelIndices[i] = -1; + } + + return deviceChannelIndices; + } + + /// + /// Generates an array of strings with the value "0" as the default shank ID + /// + /// Number of contacts in a single probe + /// + public static string[] DefaultShankIds(int numberOfContacts) + { + string[] contactIds = new string[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + var shank = i / NeuropixelsV2.ElectrodePerShank; + contactIds[i] = shank switch + { + 0 => "0", + 1 => "1", + 2 => "2", + 3 => "3", + _ => throw new InvalidOperationException($"Too many shanks; expected four or less zero-indexed shanks, but received {shank} as an index.") + }; + } + + return contactIds; + } + + /// + /// Convert a ProbeInterface object to a list of electrodes, which includes all possible electrodes + /// + /// A object + /// List of electrodes + public static List ToElectrodes(NeuropixelsV2eProbeGroup channelConfiguration) + { + List electrodes = new(); + + foreach (var c in channelConfiguration.GetContacts()) + { + electrodes.Add(new NeuropixelsV2QuadShankElectrode(c)); + } + + return electrodes; + } + + public static void UpdateElectrodes(List electrodes, NeuropixelsV2eProbeGroup channelConfiguration) + { + if (electrodes.Count != channelConfiguration.NumberOfContacts) + { + throw new InvalidOperationException($"Different number of electrodes found in {nameof(electrodes)} versus {nameof(channelConfiguration)}"); + } + + int index = 0; + + foreach (var c in channelConfiguration.GetContacts()) + { + electrodes[index++] = new NeuropixelsV2QuadShankElectrode(c); + } + } + + /// + /// Convert a ProbeInterface object to a list of electrodes, which only includes currently enabled electrodes + /// + /// A object + /// List of electrodes that are enabled + public static List ToChannelMap(NeuropixelsV2eProbeGroup channelConfiguration) + { + List channelMap = new(); + + foreach (var c in channelConfiguration.GetContacts().Where(c => c.DeviceId != -1)) + { + channelMap.Add(new NeuropixelsV2QuadShankElectrode(c)); + } + + return channelMap.OrderBy(e => e.Channel).ToList(); + } + + public static void UpdateChannelMap(List channelMap, NeuropixelsV2eProbeGroup channelConfiguration) + { + var enabledElectrodes = channelConfiguration.GetContacts() + .Where(c => c.DeviceId != -1); + + if (channelMap.Count != enabledElectrodes.Count()) + { + throw new InvalidOperationException($"Different number of enabled electrodes found in {nameof(channelMap)} versus {nameof(channelConfiguration)}"); + } + + int index = 0; + + foreach (var c in enabledElectrodes) + { + channelMap[index++] = new NeuropixelsV2QuadShankElectrode(c); + } + } + + /// + /// Update the currently enabled contacts in the probe group, based on the currently selected contacts in + /// the given channel map. The only operation that occurs is an update of the DeviceChannelIndices field, + /// where -1 indicates the contact is no longer enabled + /// + /// List of objects, which contain the index of the selected contact + /// + public static void UpdateProbeGroup(List channelMap, NeuropixelsV2eProbeGroup probeGroup) + { + int[] deviceChannelIndices = new int[probeGroup.NumberOfContacts]; + + deviceChannelIndices = deviceChannelIndices.Select(i => i = -1).ToArray(); + + foreach (var e in channelMap) + { + deviceChannelIndices[e.ElectrodeNumber] = e.Channel; + } + + probeGroup.UpdateDeviceChannelIndices(0, deviceChannelIndices); + } + } +} From 24f7ff9ae1d3d865d530ecde40527f97fc1f503f Mon Sep 17 00:00:00 2001 From: bparks13 Date: Wed, 17 Jul 2024 18:31:24 -0400 Subject: [PATCH 3/8] Add NeuropixelsV2eHeadstage GUI --- ...europixelsV2eChannelConfigurationDialog.cs | 4 + .../NeuropixelsV2eDialog.Designer.cs | 26 +++ .../NeuropixelsV2eDialog.cs | 41 +++- .../NeuropixelsV2eDialog.resx | 3 - .../NeuropixelsV2eHeadstageDialog.Designer.cs | 198 ++++++++++++++++++ .../NeuropixelsV2eHeadstageDialog.cs | 52 +++++ .../NeuropixelsV2eHeadstageDialog.resx | 123 +++++++++++ .../NeuropixelsV2eHeadstageEditor.cs | 37 ++++ .../ConfigureNeuropixelsV2eHeadstage.cs | 1 + 9 files changed, 479 insertions(+), 6 deletions(-) create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.resx create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs index a52a8315..80a54fae 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -26,6 +26,10 @@ public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2eProbeGroup probeGr ChannelMap = NeuropixelsV2eProbeGroup.ToChannelMap((NeuropixelsV2eProbeGroup)ChannelConfiguration); Electrodes = NeuropixelsV2eProbeGroup.ToElectrodes((NeuropixelsV2eProbeGroup)ChannelConfiguration); + + HighlightEnabledContacts(); + UpdateContactLabels(); + RefreshZedGraph(); } internal override ProbeGroup DefaultChannelLayout() diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs index bb31ea53..51598b03 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs @@ -81,6 +81,8 @@ private void InitializeComponent() this.buttonCancel = new System.Windows.Forms.Button(); this.buttonOkay = new System.Windows.Forms.Button(); this.linkLabelDocumentation = new System.Windows.Forms.LinkLabel(); + this.buttonClearCalibrationFileA = new System.Windows.Forms.Button(); + this.buttonClearCalibrationFileB = new System.Windows.Forms.Button(); label1 = new System.Windows.Forms.Label(); label2 = new System.Windows.Forms.Label(); labelSelection = new System.Windows.Forms.Label(); @@ -408,6 +410,8 @@ private void InitializeComponent() // // panelOptions // + this.panelOptions.Controls.Add(this.buttonClearCalibrationFileB); + this.panelOptions.Controls.Add(this.buttonClearCalibrationFileA); this.panelOptions.Controls.Add(this.comboBoxReferenceB); this.panelOptions.Controls.Add(label3); this.panelOptions.Controls.Add(this.buttonGainCalibrationFileB); @@ -649,6 +653,26 @@ private void InitializeComponent() this.linkLabelDocumentation.TabStop = true; this.linkLabelDocumentation.Text = "Documentation"; // + // buttonClearCalibrationFileA + // + this.buttonClearCalibrationFileA.Location = new System.Drawing.Point(43, 368); + this.buttonClearCalibrationFileA.Name = "buttonClearCalibrationFileA"; + this.buttonClearCalibrationFileA.Size = new System.Drawing.Size(141, 32); + this.buttonClearCalibrationFileA.TabIndex = 16; + this.buttonClearCalibrationFileA.Text = "Clear"; + this.buttonClearCalibrationFileA.UseVisualStyleBackColor = true; + this.buttonClearCalibrationFileA.Click += new System.EventHandler(this.ButtonClick); + // + // buttonClearCalibrationFileB + // + this.buttonClearCalibrationFileB.Location = new System.Drawing.Point(43, 553); + this.buttonClearCalibrationFileB.Name = "buttonClearCalibrationFileB"; + this.buttonClearCalibrationFileB.Size = new System.Drawing.Size(141, 32); + this.buttonClearCalibrationFileB.TabIndex = 17; + this.buttonClearCalibrationFileB.Text = "Clear"; + this.buttonClearCalibrationFileB.UseVisualStyleBackColor = true; + this.buttonClearCalibrationFileB.Click += new System.EventHandler(this.ButtonClick); + // // NeuropixelsV2eDialog // this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); @@ -733,5 +757,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripStatusLabel gainA; private System.Windows.Forms.ToolStripStatusLabel gainB; private System.Windows.Forms.ComboBox comboBoxChannelPresetsB; + private System.Windows.Forms.Button buttonClearCalibrationFileB; + private System.Windows.Forms.Button buttonClearCalibrationFileA; } } diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs index 13d3f52d..41e52e39 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs @@ -161,6 +161,16 @@ private void ParseGainCalibrationFile(string probe) gainCalibrationFile.Close(); } + else + { + probeSnA.Text = ""; + gainA.Text = ""; + } + } + else + { + probeSnA.Text = ""; + gainA.Text = ""; } } else if (probe == "B") @@ -177,6 +187,16 @@ private void ParseGainCalibrationFile(string probe) gainCalibrationFile.Close(); } + else + { + probeSnB.Text = ""; + gainB.Text = ""; + } + } + else + { + probeSnB.Text = ""; + gainB.Text = ""; } } } @@ -605,7 +625,7 @@ private void LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) } } - private void ButtonClick(object sender, EventArgs e) + internal void ButtonClick(object sender, EventArgs e) { const float zoomFactor = 8f; @@ -613,8 +633,7 @@ private void ButtonClick(object sender, EventArgs e) { if (button.Name == nameof(buttonOkay)) { - NeuropixelsV2eProbeGroup.UpdateProbeGroup(ChannelConfigurationA.ChannelMap, ConfigureNode.ProbeConfigurationA.ChannelConfiguration); - NeuropixelsV2eProbeGroup.UpdateProbeGroup(ChannelConfigurationB.ChannelMap, ConfigureNode.ProbeConfigurationB.ChannelConfiguration); + UpdateProbeGroups(); DialogResult = DialogResult.OK; } @@ -700,9 +719,25 @@ private void ButtonClick(object sender, EventArgs e) channelConfiguration.UpdateContactLabels(); channelConfiguration.RefreshZedGraph(); } + else if (button.Name == nameof(buttonClearCalibrationFileA)) + { + textBoxProbeCalibrationFileA.Text = ""; + panelProbeA.Visible = false; + } + else if (button.Name == nameof(buttonClearCalibrationFileB)) + { + textBoxProbeCalibrationFileB.Text = ""; + panelProbeB.Visible = false; + } } } + internal void UpdateProbeGroups() + { + NeuropixelsV2eProbeGroup.UpdateProbeGroup(ChannelConfigurationA.ChannelMap, ConfigureNode.ProbeConfigurationA.ChannelConfiguration); + NeuropixelsV2eProbeGroup.UpdateProbeGroup(ChannelConfigurationB.ChannelMap, ConfigureNode.ProbeConfigurationB.ChannelConfiguration); + } + private void EnableSelectedContacts(NeuropixelsV2Probe probeSelected) { var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx index 447103fd..a5f74c26 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx @@ -165,7 +165,4 @@ False - - False - \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs new file mode 100644 index 00000000..e2c950ab --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs @@ -0,0 +1,198 @@ +namespace OpenEphys.Onix.Design +{ + partial class NeuropixelsV2eHeadstageDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPageNeuropixelsV2e = new System.Windows.Forms.TabPage(); + this.panelNeuropixelsV2e = new System.Windows.Forms.Panel(); + this.tabPageBno055 = new System.Windows.Forms.TabPage(); + this.panelBno055 = new System.Windows.Forms.Panel(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.buttonCancel = new System.Windows.Forms.Button(); + this.buttonOkay = new System.Windows.Forms.Button(); + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.tabControl1.SuspendLayout(); + this.tabPageNeuropixelsV2e.SuspendLayout(); + this.tabPageBno055.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.menuStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // tabControl1 + // + this.tabControl1.Controls.Add(this.tabPageNeuropixelsV2e); + this.tabControl1.Controls.Add(this.tabPageBno055); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControl1.Location = new System.Drawing.Point(0, 0); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(1295, 722); + this.tabControl1.TabIndex = 0; + // + // tabPageNeuropixelsV2e + // + this.tabPageNeuropixelsV2e.Controls.Add(this.panelNeuropixelsV2e); + this.tabPageNeuropixelsV2e.Location = new System.Drawing.Point(4, 29); + this.tabPageNeuropixelsV2e.Name = "tabPageNeuropixelsV2e"; + this.tabPageNeuropixelsV2e.Padding = new System.Windows.Forms.Padding(3); + this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(1287, 689); + this.tabPageNeuropixelsV2e.TabIndex = 0; + this.tabPageNeuropixelsV2e.Text = "NeuropixelsV2e"; + this.tabPageNeuropixelsV2e.UseVisualStyleBackColor = true; + // + // panelNeuropixelsV2e + // + this.panelNeuropixelsV2e.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelNeuropixelsV2e.Location = new System.Drawing.Point(3, 3); + this.panelNeuropixelsV2e.Name = "panelNeuropixelsV2e"; + this.panelNeuropixelsV2e.Size = new System.Drawing.Size(1281, 683); + this.panelNeuropixelsV2e.TabIndex = 0; + // + // tabPageBno055 + // + this.tabPageBno055.Controls.Add(this.panelBno055); + this.tabPageBno055.Location = new System.Drawing.Point(4, 29); + this.tabPageBno055.Name = "tabPageBno055"; + this.tabPageBno055.Padding = new System.Windows.Forms.Padding(3); + this.tabPageBno055.Size = new System.Drawing.Size(1287, 689); + this.tabPageBno055.TabIndex = 1; + this.tabPageBno055.Text = "Bno055"; + this.tabPageBno055.UseVisualStyleBackColor = true; + // + // panelBno055 + // + this.panelBno055.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelBno055.Location = new System.Drawing.Point(3, 3); + this.panelBno055.Name = "panelBno055"; + this.panelBno055.Size = new System.Drawing.Size(1281, 683); + this.panelBno055.TabIndex = 0; + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.Location = new System.Drawing.Point(0, 33); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.tabControl1); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); + this.splitContainer1.Panel2.Controls.Add(this.buttonOkay); + this.splitContainer1.Size = new System.Drawing.Size(1295, 778); + this.splitContainer1.SplitterDistance = 722; + this.splitContainer1.TabIndex = 1; + // + // buttonCancel + // + this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonCancel.Location = new System.Drawing.Point(1121, 7); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(162, 40); + this.buttonCancel.TabIndex = 6; + this.buttonCancel.Text = "Cancel"; + this.buttonCancel.UseVisualStyleBackColor = true; + // + // buttonOkay + // + this.buttonOkay.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonOkay.Location = new System.Drawing.Point(938, 7); + this.buttonOkay.Name = "buttonOkay"; + this.buttonOkay.Size = new System.Drawing.Size(162, 40); + this.buttonOkay.TabIndex = 5; + this.buttonOkay.Text = "OK"; + this.buttonOkay.UseVisualStyleBackColor = true; + this.buttonOkay.Click += new System.EventHandler(this.ButtonClick); + // + // menuStrip1 + // + this.menuStrip1.GripMargin = new System.Windows.Forms.Padding(2, 2, 0, 2); + this.menuStrip1.ImageScalingSize = new System.Drawing.Size(24, 24); + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Size = new System.Drawing.Size(1295, 33); + this.menuStrip1.TabIndex = 2; + this.menuStrip1.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 29); + this.fileToolStripMenuItem.Text = "File"; + // + // NeuropixelsV2eHeadstageDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1295, 811); + this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.menuStrip1); + this.DoubleBuffered = true; + this.MainMenuStrip = this.menuStrip1; + this.Name = "NeuropixelsV2eHeadstageDialog"; + this.Text = "NeuropixelsV2eHeadstageDialog"; + this.tabControl1.ResumeLayout(false); + this.tabPageNeuropixelsV2e.ResumeLayout(false); + this.tabPageBno055.ResumeLayout(false); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPageNeuropixelsV2e; + private System.Windows.Forms.TabPage tabPageBno055; + private System.Windows.Forms.Panel panelNeuropixelsV2e; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.Button buttonCancel; + private System.Windows.Forms.Button buttonOkay; + private System.Windows.Forms.Panel panelBno055; + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs new file mode 100644 index 00000000..62e79953 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs @@ -0,0 +1,52 @@ +using System.Windows.Forms; + +namespace OpenEphys.Onix.Design +{ + public partial class NeuropixelsV2eHeadstageDialog : Form + { + public readonly NeuropixelsV2eDialog ConfigureNeuropixelsV2e; + //public readonly NeuropixelsV2eBno055Dialog ConfigureBno055; + + public NeuropixelsV2eHeadstageDialog(ConfigureNeuropixelsV2e configureNeuropixelsV2e, ConfigureNeuropixelsV2eBno055 configureBno055) + { + InitializeComponent(); + + ConfigureNeuropixelsV2e = new(configureNeuropixelsV2e) + { + TopLevel = false, + FormBorderStyle = FormBorderStyle.None, + Dock = DockStyle.Fill, + Parent = this + }; + + panelNeuropixelsV2e.Controls.Add(ConfigureNeuropixelsV2e); + this.AddMenuItemsFromDialogToFileOption(ConfigureNeuropixelsV2e, "NeuropixelsV2e"); + ConfigureNeuropixelsV2e.Show(); + + //ConfigureBno055 = new(configureBno055) + //{ + // TopLevel = false, + // FormBorderStyle = FormBorderStyle.None, + // Dock = DockStyle.Fill, + // Parent = this + //}; + + //panelBno055.Controls.Add(ConfigureBno055); + //ConfigureBno055.Show(); + //ConfigureBno055.Invalidate(); + } + + private void ButtonClick(object sender, System.EventArgs e) + { + if (sender is Button button && button != null) + { + if (button.Name == nameof(buttonOkay)) + { + ConfigureNeuropixelsV2e.UpdateProbeGroups(); + + DialogResult = DialogResult.OK; + } + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.resx b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.resx new file mode 100644 index 00000000..d5494e30 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs new file mode 100644 index 00000000..c0df3493 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs @@ -0,0 +1,37 @@ +using Bonsai.Design; +using System.ComponentModel; +using System.Windows.Forms; +using System; + +namespace OpenEphys.Onix.Design +{ + public class NeuropixelsV2eHeadstageEditor : WorkflowComponentEditor + { + public override bool EditComponent(ITypeDescriptorContext context, object component, IServiceProvider provider, IWin32Window owner) + { + if (provider != null) + { + var editorState = (IWorkflowEditorState)provider.GetService(typeof(IWorkflowEditorState)); + if (editorState != null && !editorState.WorkflowRunning && component is ConfigureNeuropixelsV2eHeadstage configureHeadstage) + { + using var editorDialog = new NeuropixelsV2eHeadstageDialog(configureHeadstage.NeuropixelsV2, configureHeadstage.Bno055); + + if (editorDialog.ShowDialog() == DialogResult.OK) + { + //configureHeadstage.Bno055.Enable = editorDialog.ConfigureBno055.ConfigureNode.Enable; + + configureHeadstage.NeuropixelsV2.Enable = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.Enable; + configureHeadstage.NeuropixelsV2.ProbeConfigurationA = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.ProbeConfigurationA; + configureHeadstage.NeuropixelsV2.ProbeConfigurationB = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.ProbeConfigurationB; + configureHeadstage.NeuropixelsV2.GainCalibrationFileA = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.GainCalibrationFileA; + configureHeadstage.NeuropixelsV2.GainCalibrationFileB = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.GainCalibrationFileB; + + return true; + } + } + } + + return false; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs index 710715f7..2595c559 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs @@ -3,6 +3,7 @@ namespace OpenEphys.Onix { + [Editor("OpenEphys.Onix.Design.NeuropixelsV2eHeadstageEditor, OpenEphys.Onix.Design", typeof(ComponentEditor))] public class ConfigureNeuropixelsV2eHeadstage : HubDeviceFactory { PortName port; From a739bc003628ddc570279127493c21d2866f6f51 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Thu, 18 Jul 2024 13:48:39 -0400 Subject: [PATCH 4/8] Add GUI support for NeuropixelsV2eBno055 --- .../NeuropixelsV2eBno055Dialog.Designer.cs | 39 +++++++++ .../NeuropixelsV2eBno055Dialog.cs | 30 +++++++ .../NeuropixelsV2eBno055Editor.cs | 31 +++++++ .../NeuropixelsV2eDialog.Designer.cs | 87 ++++++++++--------- .../NeuropixelsV2eDialog.cs | 2 +- .../NeuropixelsV2eDialog.resx | 6 +- .../NeuropixelsV2eHeadstageDialog.cs | 24 ++--- .../ConfigureNeuropixelsV2eBno055.cs | 7 ++ 8 files changed, 167 insertions(+), 59 deletions(-) create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.Designer.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Editor.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.Designer.cs new file mode 100644 index 00000000..c5d424e2 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.Designer.cs @@ -0,0 +1,39 @@ +namespace OpenEphys.Onix.Design +{ + partial class NeuropixelsV2eBno055Dialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "NeuropixelsV2eBno055Dialog"; + } + + #endregion + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.cs new file mode 100644 index 00000000..4b287986 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.cs @@ -0,0 +1,30 @@ +using System; + +namespace OpenEphys.Onix.Design +{ + public partial class NeuropixelsV2eBno055Dialog : GenericDeviceDialog + { + public ConfigureNeuropixelsV2eBno055 ConfigureNode + { + get => (ConfigureNeuropixelsV2eBno055)propertyGrid.SelectedObject; + set => propertyGrid.SelectedObject = value; + } + + public NeuropixelsV2eBno055Dialog(ConfigureNeuropixelsV2eBno055 configureNode) + { + InitializeComponent(); + Shown += FormShown; + + ConfigureNode = new(configureNode); + } + + private void FormShown(object sender, EventArgs e) + { + if (!TopLevel) + { + splitContainer1.Panel2Collapsed = true; + splitContainer1.Panel2.Hide(); + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Editor.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Editor.cs new file mode 100644 index 00000000..83e3ec1a --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Editor.cs @@ -0,0 +1,31 @@ +using System; +using System.ComponentModel; +using Bonsai.Design; +using System.Windows.Forms; + +namespace OpenEphys.Onix.Design +{ + internal class NeuropixelsV2eBno055Editor : WorkflowComponentEditor + { + public override bool EditComponent(ITypeDescriptorContext context, object component, IServiceProvider provider, IWin32Window owner) + { + if (provider != null) + { + var editorState = (IWorkflowEditorState)provider.GetService(typeof(IWorkflowEditorState)); + if (editorState != null && !editorState.WorkflowRunning && component is ConfigureNeuropixelsV2eBno055 configureBno055) + { + using var editorDialog = new NeuropixelsV2eBno055Dialog(configureBno055); + + if (editorDialog.ShowDialog() == DialogResult.OK) + { + configureBno055.Enable = editorDialog.ConfigureNode.Enable; + + return true; + } + } + } + + return false; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs index 51598b03..eb8fc80f 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs @@ -61,6 +61,8 @@ private void InitializeComponent() this.tabControlOptions = new System.Windows.Forms.TabControl(); this.tabPageOptions = new System.Windows.Forms.TabPage(); this.panelOptions = new System.Windows.Forms.Panel(); + this.buttonClearCalibrationFileB = new System.Windows.Forms.Button(); + this.buttonClearCalibrationFileA = new System.Windows.Forms.Button(); this.comboBoxReferenceB = new System.Windows.Forms.ComboBox(); this.buttonGainCalibrationFileB = new System.Windows.Forms.Button(); this.textBoxProbeCalibrationFileB = new System.Windows.Forms.TextBox(); @@ -81,8 +83,6 @@ private void InitializeComponent() this.buttonCancel = new System.Windows.Forms.Button(); this.buttonOkay = new System.Windows.Forms.Button(); this.linkLabelDocumentation = new System.Windows.Forms.LinkLabel(); - this.buttonClearCalibrationFileA = new System.Windows.Forms.Button(); - this.buttonClearCalibrationFileB = new System.Windows.Forms.Button(); label1 = new System.Windows.Forms.Label(); label2 = new System.Windows.Forms.Label(); labelSelection = new System.Windows.Forms.Label(); @@ -197,18 +197,6 @@ private void InitializeComponent() label5.TabIndex = 27; label5.Text = "Probe B"; // - // toolStripStatusLabelProbeA - // - this.toolStripStatusLabelProbeA.Name = "toolStripStatusLabelProbeA"; - this.toolStripStatusLabelProbeA.Size = new System.Drawing.Size(44, 25); - this.toolStripStatusLabelProbeA.Text = "SN: "; - // - // toolStripStatusLabelProbeB - // - this.toolStripStatusLabelProbeB.Name = "toolStripStatusLabelProbeB"; - this.toolStripStatusLabelProbeB.Size = new System.Drawing.Size(44, 25); - this.toolStripStatusLabelProbeB.Text = "SN: "; - // // Reference // Reference.AutoSize = true; @@ -238,6 +226,27 @@ private void InitializeComponent() probeCalibrationFileB.TabIndex = 11; probeCalibrationFileB.Text = "Probe B Calibration File"; // + // label3 + // + label3.AutoSize = true; + label3.Location = new System.Drawing.Point(14, 147); + label3.Name = "label3"; + label3.Size = new System.Drawing.Size(95, 20); + label3.TabIndex = 14; + label3.Text = "ReferenceB"; + // + // toolStripStatusLabelProbeA + // + this.toolStripStatusLabelProbeA.Name = "toolStripStatusLabelProbeA"; + this.toolStripStatusLabelProbeA.Size = new System.Drawing.Size(44, 25); + this.toolStripStatusLabelProbeA.Text = "SN: "; + // + // toolStripStatusLabelProbeB + // + this.toolStripStatusLabelProbeB.Name = "toolStripStatusLabelProbeB"; + this.toolStripStatusLabelProbeB.Size = new System.Drawing.Size(44, 25); + this.toolStripStatusLabelProbeB.Text = "SN: "; + // // menuStrip // this.menuStrip.GripMargin = new System.Windows.Forms.Padding(2, 2, 0, 2); @@ -428,6 +437,26 @@ private void InitializeComponent() this.panelOptions.Size = new System.Drawing.Size(256, 660); this.panelOptions.TabIndex = 0; // + // buttonClearCalibrationFileB + // + this.buttonClearCalibrationFileB.Location = new System.Drawing.Point(43, 553); + this.buttonClearCalibrationFileB.Name = "buttonClearCalibrationFileB"; + this.buttonClearCalibrationFileB.Size = new System.Drawing.Size(141, 32); + this.buttonClearCalibrationFileB.TabIndex = 17; + this.buttonClearCalibrationFileB.Text = "Clear"; + this.buttonClearCalibrationFileB.UseVisualStyleBackColor = true; + this.buttonClearCalibrationFileB.Click += new System.EventHandler(this.ButtonClick); + // + // buttonClearCalibrationFileA + // + this.buttonClearCalibrationFileA.Location = new System.Drawing.Point(43, 368); + this.buttonClearCalibrationFileA.Name = "buttonClearCalibrationFileA"; + this.buttonClearCalibrationFileA.Size = new System.Drawing.Size(141, 32); + this.buttonClearCalibrationFileA.TabIndex = 16; + this.buttonClearCalibrationFileA.Text = "Clear"; + this.buttonClearCalibrationFileA.UseVisualStyleBackColor = true; + this.buttonClearCalibrationFileA.Click += new System.EventHandler(this.ButtonClick); + // // comboBoxReferenceB // this.comboBoxReferenceB.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; @@ -438,15 +467,6 @@ private void InitializeComponent() this.comboBoxReferenceB.TabIndex = 15; this.comboBoxReferenceB.SelectedIndexChanged += new System.EventHandler(this.SelectedIndexChanged); // - // label3 - // - label3.AutoSize = true; - label3.Location = new System.Drawing.Point(14, 147); - label3.Name = "label3"; - label3.Size = new System.Drawing.Size(95, 20); - label3.TabIndex = 14; - label3.Text = "ReferenceB"; - // // buttonGainCalibrationFileB // this.buttonGainCalibrationFileB.Location = new System.Drawing.Point(44, 515); @@ -652,26 +672,7 @@ private void InitializeComponent() this.linkLabelDocumentation.TabIndex = 3; this.linkLabelDocumentation.TabStop = true; this.linkLabelDocumentation.Text = "Documentation"; - // - // buttonClearCalibrationFileA - // - this.buttonClearCalibrationFileA.Location = new System.Drawing.Point(43, 368); - this.buttonClearCalibrationFileA.Name = "buttonClearCalibrationFileA"; - this.buttonClearCalibrationFileA.Size = new System.Drawing.Size(141, 32); - this.buttonClearCalibrationFileA.TabIndex = 16; - this.buttonClearCalibrationFileA.Text = "Clear"; - this.buttonClearCalibrationFileA.UseVisualStyleBackColor = true; - this.buttonClearCalibrationFileA.Click += new System.EventHandler(this.ButtonClick); - // - // buttonClearCalibrationFileB - // - this.buttonClearCalibrationFileB.Location = new System.Drawing.Point(43, 553); - this.buttonClearCalibrationFileB.Name = "buttonClearCalibrationFileB"; - this.buttonClearCalibrationFileB.Size = new System.Drawing.Size(141, 32); - this.buttonClearCalibrationFileB.TabIndex = 17; - this.buttonClearCalibrationFileB.Text = "Clear"; - this.buttonClearCalibrationFileB.UseVisualStyleBackColor = true; - this.buttonClearCalibrationFileB.Click += new System.EventHandler(this.ButtonClick); + this.linkLabelDocumentation.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkClicked); // // NeuropixelsV2eDialog // diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs index 41e52e39..0d54110d 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs @@ -617,7 +617,7 @@ private void LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { try { - System.Diagnostics.Process.Start("https://open-ephys.github.io/onix-docs/Software%20Guide/Bonsai.ONIX/Nodes/NeuropixelsV1eDevice.html"); + System.Diagnostics.Process.Start("https://open-ephys.github.io/onix-docs/Software%20Guide/Bonsai.ONIX/Nodes/NeuropixelsV2eDevice.html"); } catch (Exception) { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx index a5f74c26..a4d8141e 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx @@ -156,13 +156,13 @@ False + + False + 17, 17 165, 17 - - False - \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs index 62e79953..188b6886 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs @@ -5,7 +5,7 @@ namespace OpenEphys.Onix.Design public partial class NeuropixelsV2eHeadstageDialog : Form { public readonly NeuropixelsV2eDialog ConfigureNeuropixelsV2e; - //public readonly NeuropixelsV2eBno055Dialog ConfigureBno055; + public readonly NeuropixelsV2eBno055Dialog ConfigureBno055; public NeuropixelsV2eHeadstageDialog(ConfigureNeuropixelsV2e configureNeuropixelsV2e, ConfigureNeuropixelsV2eBno055 configureBno055) { @@ -23,17 +23,17 @@ public NeuropixelsV2eHeadstageDialog(ConfigureNeuropixelsV2e configureNeuropixel this.AddMenuItemsFromDialogToFileOption(ConfigureNeuropixelsV2e, "NeuropixelsV2e"); ConfigureNeuropixelsV2e.Show(); - //ConfigureBno055 = new(configureBno055) - //{ - // TopLevel = false, - // FormBorderStyle = FormBorderStyle.None, - // Dock = DockStyle.Fill, - // Parent = this - //}; - - //panelBno055.Controls.Add(ConfigureBno055); - //ConfigureBno055.Show(); - //ConfigureBno055.Invalidate(); + ConfigureBno055 = new(configureBno055) + { + TopLevel = false, + FormBorderStyle = FormBorderStyle.None, + Dock = DockStyle.Fill, + Parent = this + }; + + panelBno055.Controls.Add(ConfigureBno055); + ConfigureBno055.Show(); + ConfigureBno055.Invalidate(); } private void ButtonClick(object sender, System.EventArgs e) diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs index 1a6d10b4..68de63bf 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs @@ -3,6 +3,7 @@ namespace OpenEphys.Onix { + [Editor("OpenEphys.Onix.Design.NeuropixelsV2eBno055Editor, OpenEphys.Onix.Design", typeof(ComponentEditor))] public class ConfigureNeuropixelsV2eBno055 : SingleDeviceFactory { public ConfigureNeuropixelsV2eBno055() @@ -10,6 +11,12 @@ public ConfigureNeuropixelsV2eBno055() { } + public ConfigureNeuropixelsV2eBno055(ConfigureNeuropixelsV2eBno055 configureNeuropixelsV2eBno055) + : base(typeof(NeuropixelsV2eBno055)) + { + Enable = configureNeuropixelsV2eBno055.Enable; + } + [Category(ConfigurationCategory)] [Description("Specifies whether the BNO055 device is enabled.")] public bool Enable { get; set; } = true; From 83077de88eb41673337ba91c369401d3f7d11532 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Thu, 18 Jul 2024 15:20:38 -0400 Subject: [PATCH 5/8] Updates - Ensure GUI gain calibration parsing matches configuration parsing - Prevent reference overwriting - Ensure that Bno055 state is correctly saved - Update submodule - Enforce 384 channels in the channel map - Update contacts to be more easily seen, and that numbers fit inside the contact - Correctly copy device name/address --- .../ChannelConfigurationDialog.cs | 8 +-- ...europixelsV2eChannelConfigurationDialog.cs | 3 +- .../NeuropixelsV2eDialog.Designer.cs | 2 - .../NeuropixelsV2eDialog.cs | 64 +++++++------------ .../NeuropixelsV2eHeadstageEditor.cs | 2 +- .../OpenEphys.Onix/ConfigureNeuropixelsV2e.cs | 2 +- .../ConfigureNeuropixelsV2eBno055.cs | 2 + .../OpenEphys.Onix/NeuropixelsV2Helper.cs | 28 -------- .../NeuropixelsV2QuadShankElectrode.cs | 2 +- .../NeuropixelsV2eProbeGroup.cs | 15 ++++- OpenEphys.Onix/OpenEphys.ProbeInterface | 2 +- 11 files changed, 47 insertions(+), 83 deletions(-) delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Helper.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs index 96004848..f8cfd826 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs @@ -195,7 +195,7 @@ internal void DrawProbeContour() foreach (var probe in ChannelConfiguration.Probes) { PointD[] planarContours = ConvertFloatArrayToPointD(probe.ProbePlanarContour); - PolyObj contour = new(planarContours, Color.Black, Color.White) + PolyObj contour = new(planarContours, Color.DarkGray, Color.Black) { ZOrder = ZOrder.C_BehindChartBorder }; @@ -295,8 +295,8 @@ internal void DrawContacts() } } - internal readonly Color DisabledContactFill = Color.DarkGray; - internal readonly Color EnabledContactFill = Color.SteelBlue; // Color.LightYellow + internal readonly Color DisabledContactFill = Color.White; + internal readonly Color EnabledContactFill = Color.LightGreen; internal readonly Color ReferenceContactFill = Color.Black; internal virtual void HighlightEnabledContacts() @@ -456,7 +456,7 @@ internal virtual float CalculateFontSize() float contactSize = ContactSize(); - var fontSize = 300f * contactSize / rangeY; + var fontSize = 250f * contactSize / rangeY; fontSize = fontSize < 5f ? 0.001f : fontSize; fontSize = fontSize > 100f ? 100f : fontSize; diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 80a54fae..79ca61a3 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -51,6 +51,7 @@ internal override void OpenFile() { base.OpenFile(); + NeuropixelsV2eProbeGroup.UpdateElectrodes(Electrodes, (NeuropixelsV2eProbeGroup)ChannelConfiguration); NeuropixelsV2eProbeGroup.UpdateChannelMap(ChannelMap, (NeuropixelsV2eProbeGroup)ChannelConfiguration); OnFileOpenHandler(); @@ -83,7 +84,7 @@ internal override void DrawScale() const int MinorTickIncrement = 10; const int MinorTickLength = 5; - if (ChannelConfiguration.Probes.ElementAt(0).SiUnits != ProbeSiUnits.Um) + if (ChannelConfiguration.Probes.ElementAt(0).SiUnits != ProbeSiUnits.um) { MessageBox.Show("Warning: Expected ProbeGroup units to be in microns, but it is in millimeters. Scale might not be accurate."); } diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs index eb8fc80f..041264b5 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs @@ -465,7 +465,6 @@ private void InitializeComponent() this.comboBoxReferenceB.Name = "comboBoxReferenceB"; this.comboBoxReferenceB.Size = new System.Drawing.Size(121, 28); this.comboBoxReferenceB.TabIndex = 15; - this.comboBoxReferenceB.SelectedIndexChanged += new System.EventHandler(this.SelectedIndexChanged); // // buttonGainCalibrationFileB // @@ -515,7 +514,6 @@ private void InitializeComponent() this.comboBoxReferenceA.Name = "comboBoxReferenceA"; this.comboBoxReferenceA.Size = new System.Drawing.Size(121, 28); this.comboBoxReferenceA.TabIndex = 5; - this.comboBoxReferenceA.SelectedIndexChanged += new System.EventHandler(this.SelectedIndexChanged); // // tabPageContactsOptions // diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs index 0d54110d..6d5edd84 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs @@ -95,12 +95,14 @@ public NeuropixelsV2eDialog(ConfigureNeuropixelsV2e configureNode) comboBoxReferenceA.DataSource = Enum.GetValues(typeof(NeuropixelsV2QuadShankReference)); comboBoxReferenceA.SelectedItem = ConfigureNode.ProbeConfigurationA.Reference; + comboBoxReferenceA.SelectedIndexChanged += SelectedIndexChanged; ChannelConfigurationB.OnZoom += UpdateTrackBarLocation; ChannelConfigurationB.OnFileLoad += UpdateChannelPresetIndex; comboBoxReferenceB.DataSource = Enum.GetValues(typeof(NeuropixelsV2QuadShankReference)); comboBoxReferenceB.SelectedItem = ConfigureNode.ProbeConfigurationB.Reference; + comboBoxReferenceB.SelectedIndexChanged += SelectedIndexChanged; comboBoxChannelPresetsA.DataSource = Enum.GetValues(typeof(ChannelPreset)); comboBoxChannelPresetsA.SelectedIndexChanged += SelectedIndexChanged; @@ -149,56 +151,37 @@ private void ParseGainCalibrationFile(string probe) { if (probe == "A") { - if (ConfigureNode.GainCalibrationFileA != null && ConfigureNode.GainCalibrationFileA != "") - { - if (File.Exists(ConfigureNode.GainCalibrationFileA)) - { - StreamReader gainCalibrationFile = new(ConfigureNode.GainCalibrationFileA); - - probeSnA.Text = ulong.Parse(gainCalibrationFile.ReadLine()).ToString(); - - gainA.Text = NeuropixelsV2Helper.ParseGainCalibrationFile(gainCalibrationFile).ToString(); - - gainCalibrationFile.Close(); - } - else - { - probeSnA.Text = ""; - gainA.Text = ""; - } - } - else - { - probeSnA.Text = ""; - gainA.Text = ""; - } + ParseGainCalibrationFile(ConfigureNode.GainCalibrationFileA, probeSnA, gainA); } else if (probe == "B") { - if (ConfigureNode.GainCalibrationFileB != null && ConfigureNode.GainCalibrationFileB != "") - { - if (File.Exists(ConfigureNode.GainCalibrationFileB)) - { - StreamReader gainCalibrationFile = new(ConfigureNode.GainCalibrationFileB); - - probeSnB.Text = ulong.Parse(gainCalibrationFile.ReadLine()).ToString(); + ParseGainCalibrationFile(ConfigureNode.GainCalibrationFileB, probeSnB, gainB); + } + } - gainB.Text = NeuropixelsV2Helper.ParseGainCalibrationFile(gainCalibrationFile).ToString(); + private void ParseGainCalibrationFile(string filename, ToolStripStatusLabel probeSN, ToolStripStatusLabel gainLabel) + { + if (filename != null && filename != "") + { + if (File.Exists(filename)) + { + using StreamReader gainCalibrationFile = new(filename); - gainCalibrationFile.Close(); - } - else - { - probeSnB.Text = ""; - gainB.Text = ""; - } + probeSN.Text = ulong.Parse(gainCalibrationFile.ReadLine()).ToString(); + gainLabel.Text = double.Parse(gainCalibrationFile.ReadLine()).ToString(); } else { - probeSnB.Text = ""; - gainB.Text = ""; + probeSN.Text = ""; + gainLabel.Text = ""; } } + + else + { + probeSN.Text = ""; + gainLabel.Text = ""; + } } private void SelectedIndexChanged(object sender, EventArgs e) @@ -610,7 +593,6 @@ private void UpdateChannelPresetIndex(object sender, EventArgs e) CheckForExistingChannelPreset(probe); } } - } private void LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs index c0df3493..8c46aded 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs @@ -18,7 +18,7 @@ public override bool EditComponent(ITypeDescriptorContext context, object compon if (editorDialog.ShowDialog() == DialogResult.OK) { - //configureHeadstage.Bno055.Enable = editorDialog.ConfigureBno055.ConfigureNode.Enable; + configureHeadstage.Bno055.Enable = editorDialog.ConfigureBno055.ConfigureNode.Enable; configureHeadstage.NeuropixelsV2.Enable = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.Enable; configureHeadstage.NeuropixelsV2.ProbeConfigurationA = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.ProbeConfigurationA; diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs index e2fe0b94..3b6360e0 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs @@ -165,7 +165,7 @@ static ushort ReadGainCorrection(string gainCalibrationFile, ulong probeSerialNu { if (gainCalibrationFile == null) { - throw new ArgumentException("Calibration file must be specified."); + throw new ArgumentException($"Calibration file must be specified for probe {probeSerialNumber}."); } System.IO.StreamReader gainFile = new(gainCalibrationFile); diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs index 68de63bf..a82fd7f6 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs @@ -15,6 +15,8 @@ public ConfigureNeuropixelsV2eBno055(ConfigureNeuropixelsV2eBno055 configureNeur : base(typeof(NeuropixelsV2eBno055)) { Enable = configureNeuropixelsV2eBno055.Enable; + DeviceName = configureNeuropixelsV2eBno055.DeviceName; + DeviceAddress = configureNeuropixelsV2eBno055.DeviceAddress; } [Category(ConfigurationCategory)] diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Helper.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Helper.cs deleted file mode 100644 index 018b2895..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Helper.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.IO; -using System.Linq; - -namespace OpenEphys.Onix -{ - public class NeuropixelsV2Helper - { - public static double ParseGainCalibrationFile(StreamReader file) - { - if (file != null) - { - var gainCalibration = file.ReadLine().Split(',').Skip(1).FirstOrDefault(); - - if (double.TryParse(gainCalibration, out var gain)) - { - return gain; - } - else - { - throw new FormatException($"Gain calibration file is improperly formatted: `{gainCalibration}` is an invalid line."); - } - } - - throw new ArgumentNullException("Invalid stream reader, check that the file name is correct."); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs index acf80703..534ac095 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs @@ -31,7 +31,7 @@ public NeuropixelsV2QuadShankElectrode(int electrodeNumber) public NeuropixelsV2QuadShankElectrode(Contact contact) { ElectrodeNumber = contact.Index; - Shank = int.TryParse(contact.ShankId, out int result) ? result : ElectrodeNumber / NeuropixelsV2.ElectrodePerShank; + Shank = ElectrodeNumber / NeuropixelsV2.ElectrodePerShank; ShankIndex = ElectrodeNumber % NeuropixelsV2.ElectrodePerShank; Bank = (NeuropixelsV2QuadShankBank)(ShankIndex / NeuropixelsV2.ChannelCount); Block = ShankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs index 5a50e059..bb7fe9c1 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs @@ -17,8 +17,8 @@ public NeuropixelsV2eProbeGroup() : base("probeinterface", "0.2.21", new List() { - new(ProbeNdim._2, - ProbeSiUnits.Um, + new(ProbeNdim.Two, + ProbeSiUnits.um, new ProbeAnnotations("Neuropixels 2.0e", "IMEC"), new ContactAnnotations(new string[0]), DefaultContactPositions(NeuropixelsV2.ElectrodePerShank * numberOfShanks), @@ -222,7 +222,16 @@ public static List ToChannelMap(NeuropixelsV2eP { List channelMap = new(); - foreach (var c in channelConfiguration.GetContacts().Where(c => c.DeviceId != -1)) + var enabledContacts = channelConfiguration.GetContacts().Where(c => c.DeviceId != -1); + + if (enabledContacts.Count() != NeuropixelsV2.ChannelCount) + { + throw new InvalidOperationException($"Channel configuration must have {NeuropixelsV2.ChannelCount} contacts enabled." + + $"Instead there are {enabledContacts.Count()} contacts enabled. Enabled contacts are designated by a device channel" + + $"index >= 0."); + } + + foreach (var c in enabledContacts) { channelMap.Add(new NeuropixelsV2QuadShankElectrode(c)); } diff --git a/OpenEphys.Onix/OpenEphys.ProbeInterface b/OpenEphys.Onix/OpenEphys.ProbeInterface index e6c7163e..701d7739 160000 --- a/OpenEphys.Onix/OpenEphys.ProbeInterface +++ b/OpenEphys.Onix/OpenEphys.ProbeInterface @@ -1 +1 @@ -Subproject commit e6c7163e79348f8fd7578c379ff9bd84bd9233ce +Subproject commit 701d77392dfab615b833c13ecb36bb211312284c From fa5de3756c98a72bfd429386ba886a1958656fa6 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Mon, 22 Jul 2024 13:49:14 -0400 Subject: [PATCH 6/8] Address review comments: - Rename "Contacts" to "Electrodes" - Zoom in on the mouse pointer - Always show contact numbers, even when disabled - Add position labels to track bar - Make contacts more visible - Automatically resize scale depending on zoom level - Spelling --- .../ChannelConfigurationDialog.cs | 67 +++++------ ...europixelsV2eChannelConfigurationDialog.cs | 82 ++++++++----- .../NeuropixelsV2eDialog.Designer.cs | 109 +++++++++++------- .../NeuropixelsV2eDialog.cs | 1 + .../NeuropixelsV2eDialog.resx | 6 + .../NeuropixelsV2eHeadstageDialog.Designer.cs | 16 +-- .../NeuropixelsV2eHeadstageEditor.cs | 12 +- .../ConfigureNeuropixelsV2eBeta.cs | 8 +- .../ConfigureNeuropixelsV2eHeadstage.cs | 6 +- 9 files changed, 180 insertions(+), 127 deletions(-) diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs index f8cfd826..d8081db3 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs @@ -195,7 +195,7 @@ internal void DrawProbeContour() foreach (var probe in ChannelConfiguration.Probes) { PointD[] planarContours = ConvertFloatArrayToPointD(probe.ProbePlanarContour); - PolyObj contour = new(planarContours, Color.DarkGray, Color.Black) + PolyObj contour = new(planarContours, Color.LightGray, Color.White) { ZOrder = ZOrder.C_BehindChartBorder }; @@ -232,7 +232,7 @@ internal void SetEqualAspectRatio() maxX += diff; } - var margin = Math.Max(rangeX, rangeY) * 0.05; + var margin = Math.Max(rangeX, rangeY) * 0.02; zedGraphChannels.GraphPane.XAxis.Scale.Min = minX - margin; zedGraphChannels.GraphPane.XAxis.Scale.Max = maxX + margin; @@ -295,8 +295,8 @@ internal void DrawContacts() } } - internal readonly Color DisabledContactFill = Color.White; - internal readonly Color EnabledContactFill = Color.LightGreen; + internal readonly Color DisabledContactFill = Color.LightGray; + internal readonly Color EnabledContactFill = Color.DarkBlue; internal readonly Color ReferenceContactFill = Color.Black; internal virtual void HighlightEnabledContacts() @@ -360,31 +360,12 @@ internal virtual void HighlightSelectedContacts() } } + internal readonly Color DisabledContactTextColor = Color.Black; + internal readonly Color EnabledContactTextColor = Color.White; + internal virtual void UpdateContactLabels() { - if (ChannelConfiguration == null) - return; - - var indices = ChannelConfiguration.GetDeviceChannelIndices() - .Select(ind => ind == -1).ToArray(); - - var textObjs = zedGraphChannels.GraphPane.GraphObjList.OfType(); - - textObjs.Where(t => t.Text != "-1") - .Select(t => t.Text = "-1"); - - if (indices.Count() != textObjs.Count()) - { - throw new InvalidOperationException($"Incorrect number of text objects found. Expected {indices.Count()}, but found {textObjs.Count()}"); - } - - textObjs.Where((t, ind) => indices[ind]) - .Select(t => - { - var tag = t.Tag as ContactTag; - t.Text = tag.ContactNumber.ToString(); - return false; - }); + DrawContactLabels(); } internal virtual void DrawContactLabels() @@ -392,7 +373,7 @@ internal virtual void DrawContactLabels() if (ChannelConfiguration == null) return; - zedGraphChannels.GraphPane.GraphObjList.RemoveAll(obj => obj is TextObj); + zedGraphChannels.GraphPane.GraphObjList.RemoveAll(obj => obj is TextObj && obj.Tag is ContactTag); var fontSize = CalculateFontSize(); @@ -413,6 +394,15 @@ internal virtual void DrawContactLabels() SetTextObj(textObj, fontSize); + if (indices[i] == -1) + { + textObj.FontSpec.FontColor = DisabledContactTextColor; + } + else + { + textObj.FontSpec.FontColor = EnabledContactTextColor; + } + zedGraphChannels.GraphPane.GraphObjList.Add(textObj); } } @@ -426,9 +416,11 @@ internal void SetTextObj(TextObj textObj, float fontSize) textObj.FontSpec.Size = fontSize; } + const string DisabledContactString = "Off"; + internal virtual string ContactString(int deviceChannelIndex, int index) { - return deviceChannelIndex == -1 ? "Off" : index.ToString(); + return deviceChannelIndex == -1 ? DisabledContactString : index.ToString(); } internal virtual void DrawScale() @@ -439,14 +431,12 @@ internal void UpdateFontSize() { var fontSize = CalculateFontSize(); - foreach (var obj in zedGraphChannels.GraphPane.GraphObjList) - { - if (obj == null) continue; + var textObjsToUpdate = zedGraphChannels.GraphPane.GraphObjList.OfType() + .Where(t => t.Tag is ContactTag); - if (obj is TextObj textObj) - { - textObj.FontSpec.Size = fontSize; - } + foreach (var obj in textObjsToUpdate) + { + obj.FontSpec.Size = fontSize; } } @@ -458,7 +448,7 @@ internal virtual float CalculateFontSize() var fontSize = 250f * contactSize / rangeY; - fontSize = fontSize < 5f ? 0.001f : fontSize; + fontSize = fontSize < 1f ? 0.001f : fontSize; fontSize = fontSize > 100f ? 100f : fontSize; return fontSize; @@ -528,6 +518,8 @@ public static PointD[] ConvertFloatArrayToPointD(float[][] floats) /// public void InitializeZedGraphChannels() { + zedGraphChannels.IsZoomOnMouseCenter = true; + zedGraphChannels.GraphPane.Title.IsVisible = false; zedGraphChannels.GraphPane.TitleGap = 0; zedGraphChannels.GraphPane.Border.IsVisible = false; @@ -591,6 +583,7 @@ private void ZedGraphChannels_Resize(object sender, EventArgs e) ResizeAxes(); UpdateControlSizeBasedOnAxisSize(); UpdateFontSize(); + DrawScale(); zedGraphChannels.AxisChange(); zedGraphChannels.Refresh(); } diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 79ca61a3..d3037ebd 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -67,6 +67,7 @@ internal override void ZoomEvent(ZedGraphControl sender, ZoomState oldState, Zoo base.ZoomEvent(sender, oldState, newState); UpdateFontSize(); + DrawScale(); RefreshZedGraph(); OnZoomHandler(); @@ -79,6 +80,12 @@ private void OnZoomHandler() internal override void DrawScale() { + const string ScalePointsTag = "scale_points"; + const string ScaleTextTag = "scale_text"; + + zedGraphChannels.GraphPane.GraphObjList.RemoveAll(obj => obj is TextObj && obj.Tag is string tag && tag == ScaleTextTag); + zedGraphChannels.GraphPane.CurveList.RemoveAll(curve => curve.Tag is string tag && tag == ScalePointsTag); + const int MajorTickIncrement = 100; const int MajorTickLength = 10; const int MinorTickIncrement = 10; @@ -91,7 +98,13 @@ internal override void DrawScale() var fontSize = CalculateFontSize(); - var x = MaxX(zedGraphChannels.GraphPane.GraphObjList) + 10; + var zoomedOut = fontSize <= 2; + + fontSize = zoomedOut ? 8 : fontSize * 4; + var majorTickOffset = MajorTickLength + GetXRange(zedGraphChannels) * 0.015; + majorTickOffset = majorTickOffset > 50 ? 50 : majorTickOffset; + + var x = MaxX(zedGraphChannels.GraphPane.GraphObjList) + 100; var minY = MinY(zedGraphChannels.GraphPane.GraphObjList); var maxY = MaxY(zedGraphChannels.GraphPane.GraphObjList); @@ -103,40 +116,52 @@ internal override void DrawScale() for (int i = (int)minY; i < maxY; i += MajorTickIncrement) { + PointPair majorTickLocation = new(x + majorTickOffset, minY + MajorTickIncrement * countMajorTicks); + pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks)); - PointPair majorTickLocation = new(x + MajorTickLength, minY + MajorTickIncrement * countMajorTicks); pointList.Add(majorTickLocation); pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks)); - TextObj textObj = new($"{i} µm", majorTickLocation.X + 10, majorTickLocation.Y) + if (!zoomedOut || i % (5 * MajorTickIncrement) == 0) { - Tag = "scale" - }; - textObj.FontSpec.Border.IsVisible = false; - textObj.FontSpec.Size = fontSize; - zedGraphChannels.GraphPane.GraphObjList.Add(textObj); - - var countMinorTicks = 1; + TextObj textObj = new($"{i} µm\n", majorTickLocation.X + 5, majorTickLocation.Y, CoordType.AxisXYScale, AlignH.Left, AlignV.Center) + { + Tag = ScaleTextTag, + }; + textObj.FontSpec.Border.IsVisible = false; + textObj.FontSpec.Size = fontSize; + zedGraphChannels.GraphPane.GraphObjList.Add(textObj); + } - for (int j = i + MinorTickIncrement; j < i + MajorTickIncrement && i + MinorTickIncrement * countMinorTicks < maxY; j += MinorTickIncrement) + if (!zoomedOut) { - pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks + MinorTickIncrement * countMinorTicks)); - pointList.Add(new PointPair(x + MinorTickLength, minY + MajorTickIncrement * countMajorTicks + MinorTickIncrement * countMinorTicks)); - pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks + MinorTickIncrement * countMinorTicks)); + var countMinorTicks = 1; + + for (int j = i + MinorTickIncrement; j < i + MajorTickIncrement && i + MinorTickIncrement * countMinorTicks < maxY; j += MinorTickIncrement) + { + pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks + MinorTickIncrement * countMinorTicks)); + pointList.Add(new PointPair(x + MinorTickLength, minY + MajorTickIncrement * countMajorTicks + MinorTickIncrement * countMinorTicks)); + pointList.Add(new PointPair(x, minY + MajorTickIncrement * countMajorTicks + MinorTickIncrement * countMinorTicks)); - countMinorTicks++; + countMinorTicks++; + } } countMajorTicks++; } - var curve = zedGraphChannels.GraphPane.AddCurve("", pointList, Color.Black, SymbolType.None); + var curve = zedGraphChannels.GraphPane.AddCurve(ScalePointsTag, pointList, Color.Black, SymbolType.None); curve.Line.Width = 4; curve.Label.IsVisible = false; curve.Symbol.IsVisible = false; } + private static double GetXRange(ZedGraphControl zedGraph) + { + return zedGraph.GraphPane.XAxis.Scale.Max - zedGraph.GraphPane.XAxis.Scale.Min; + } + internal override void HighlightEnabledContacts() { if (ChannelConfiguration == null || ChannelMap == null) @@ -176,34 +201,33 @@ internal override void UpdateContactLabels() .Select(ind => ind == -1).ToArray(); var textObjs = zedGraphChannels.GraphPane.GraphObjList.OfType() - .Where(t => t.Tag is not string); + .Where(t => t.Tag is ContactTag); - textObjs.Where(t => t.Text != "Off"); + var textObjsToUpdate = textObjs.Where(t => t.FontSpec.FontColor != DisabledContactTextColor); - foreach (var textObj in textObjs) - { - textObj.Text = "Off"; - } - - if (indices.Count() != textObjs.Count()) + foreach (var textObj in textObjsToUpdate) { - throw new InvalidOperationException($"Incorrect number of text objects found. Expected {indices.Count()}, but found {textObjs.Count()}"); + textObj.FontSpec.FontColor = DisabledContactTextColor; } - var textObjsToUpdate = textObjs.Where(c => + textObjsToUpdate = textObjs.Where(c => { var tag = c.Tag as ContactTag; var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactNumber); return ChannelMap[channel].ElectrodeNumber == tag.ContactNumber; }); - + foreach (var textObj in textObjsToUpdate) { - var tag = textObj.Tag as ContactTag; - textObj.Text = tag.ContactNumber.ToString(); + textObj.FontSpec.FontColor = EnabledContactTextColor; } } + internal override string ContactString(int deviceChannelIndex, int index) + { + return index.ToString(); + } + internal void EnableElectrodes(List electrodes) { ChannelMap.SelectElectrodes(electrodes); diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs index 041264b5..dfb4642b 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs @@ -42,6 +42,8 @@ private void InitializeComponent() System.Windows.Forms.Label probeCalibrationFileA; System.Windows.Forms.Label probeCalibrationFileB; System.Windows.Forms.Label label3; + System.Windows.Forms.Label label6; + System.Windows.Forms.Label label7; this.toolStripStatusLabelProbeA = new System.Windows.Forms.ToolStripStatusLabel(); this.toolStripStatusLabelProbeB = new System.Windows.Forms.ToolStripStatusLabel(); this.menuStrip = new System.Windows.Forms.MenuStrip(); @@ -69,7 +71,7 @@ private void InitializeComponent() this.buttonGainCalibrationFileA = new System.Windows.Forms.Button(); this.textBoxProbeCalibrationFileA = new System.Windows.Forms.TextBox(); this.comboBoxReferenceA = new System.Windows.Forms.ComboBox(); - this.tabPageContactsOptions = new System.Windows.Forms.TabPage(); + this.tabPageElectrodes = new System.Windows.Forms.TabPage(); this.panelChannelOptions = new System.Windows.Forms.Panel(); this.comboBoxChannelPresetsB = new System.Windows.Forms.ComboBox(); this.comboBoxChannelPresetsA = new System.Windows.Forms.ComboBox(); @@ -97,6 +99,8 @@ private void InitializeComponent() probeCalibrationFileA = new System.Windows.Forms.Label(); probeCalibrationFileB = new System.Windows.Forms.Label(); label3 = new System.Windows.Forms.Label(); + label6 = new System.Windows.Forms.Label(); + label7 = new System.Windows.Forms.Label(); this.menuStrip.SuspendLayout(); this.statusStrip.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); @@ -113,7 +117,7 @@ private void InitializeComponent() this.tabControlOptions.SuspendLayout(); this.tabPageOptions.SuspendLayout(); this.panelOptions.SuspendLayout(); - this.tabPageContactsOptions.SuspendLayout(); + this.tabPageElectrodes.SuspendLayout(); this.panelChannelOptions.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).BeginInit(); this.panel1.SuspendLayout(); @@ -131,7 +135,7 @@ private void InitializeComponent() // label2 // label2.AutoSize = true; - label2.Location = new System.Drawing.Point(151, 18); + label2.Location = new System.Drawing.Point(202, 19); label2.Name = "label2"; label2.Size = new System.Drawing.Size(50, 20); label2.TabIndex = 6; @@ -140,7 +144,7 @@ private void InitializeComponent() // labelSelection // labelSelection.AutoSize = true; - labelSelection.Location = new System.Drawing.Point(139, 193); + labelSelection.Location = new System.Drawing.Point(190, 194); labelSelection.Name = "labelSelection"; labelSelection.Size = new System.Drawing.Size(75, 20); labelSelection.TabIndex = 18; @@ -149,7 +153,7 @@ private void InitializeComponent() // labelPresets // labelPresets.AutoSize = true; - labelPresets.Location = new System.Drawing.Point(139, 386); + labelPresets.Location = new System.Drawing.Point(190, 387); labelPresets.Name = "labelPresets"; labelPresets.Size = new System.Drawing.Size(63, 20); labelPresets.TabIndex = 23; @@ -182,7 +186,7 @@ private void InitializeComponent() // label4 // label4.AutoSize = true; - label4.Location = new System.Drawing.Point(138, 421); + label4.Location = new System.Drawing.Point(189, 422); label4.Name = "label4"; label4.Size = new System.Drawing.Size(66, 20); label4.TabIndex = 25; @@ -191,7 +195,7 @@ private void InitializeComponent() // label5 // label5.AutoSize = true; - label5.Location = new System.Drawing.Point(138, 498); + label5.Location = new System.Drawing.Point(189, 499); label5.Name = "label5"; label5.Size = new System.Drawing.Size(66, 20); label5.TabIndex = 27; @@ -255,14 +259,14 @@ private void InitializeComponent() this.fileToolStripMenuItem}); this.menuStrip.Location = new System.Drawing.Point(0, 0); this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(1265, 33); + this.menuStrip.Size = new System.Drawing.Size(1265, 36); this.menuStrip.TabIndex = 0; this.menuStrip.Text = "menuStrip1"; // // fileToolStripMenuItem // this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 29); + this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 30); this.fileToolStripMenuItem.Text = "File"; // // statusStrip @@ -315,7 +319,7 @@ private void InitializeComponent() // this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; - this.splitContainer1.Location = new System.Drawing.Point(0, 33); + this.splitContainer1.Location = new System.Drawing.Point(0, 36); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -326,8 +330,8 @@ private void InitializeComponent() // splitContainer1.Panel2 // this.splitContainer1.Panel2.Controls.Add(this.panel1); - this.splitContainer1.Size = new System.Drawing.Size(1265, 751); - this.splitContainer1.SplitterDistance = 699; + this.splitContainer1.Size = new System.Drawing.Size(1265, 748); + this.splitContainer1.SplitterDistance = 696; this.splitContainer1.TabIndex = 2; // // splitContainer2 @@ -344,8 +348,8 @@ private void InitializeComponent() // splitContainer2.Panel2 // this.splitContainer2.Panel2.Controls.Add(this.tabControlOptions); - this.splitContainer2.Size = new System.Drawing.Size(1265, 699); - this.splitContainer2.SplitterDistance = 991; + this.splitContainer2.Size = new System.Drawing.Size(1265, 696); + this.splitContainer2.SplitterDistance = 940; this.splitContainer2.TabIndex = 1; // // tabControlProbe @@ -356,7 +360,7 @@ private void InitializeComponent() this.tabControlProbe.Location = new System.Drawing.Point(0, 0); this.tabControlProbe.Name = "tabControlProbe"; this.tabControlProbe.SelectedIndex = 0; - this.tabControlProbe.Size = new System.Drawing.Size(991, 699); + this.tabControlProbe.Size = new System.Drawing.Size(940, 696); this.tabControlProbe.TabIndex = 0; // // tabPageProbeA @@ -364,7 +368,7 @@ private void InitializeComponent() this.tabPageProbeA.Controls.Add(this.panelProbeA); this.tabPageProbeA.Location = new System.Drawing.Point(4, 29); this.tabPageProbeA.Name = "tabPageProbeA"; - this.tabPageProbeA.Size = new System.Drawing.Size(983, 666); + this.tabPageProbeA.Size = new System.Drawing.Size(932, 663); this.tabPageProbeA.TabIndex = 0; this.tabPageProbeA.Text = "Probe A"; this.tabPageProbeA.UseVisualStyleBackColor = true; @@ -374,7 +378,7 @@ private void InitializeComponent() this.panelProbeA.Dock = System.Windows.Forms.DockStyle.Fill; this.panelProbeA.Location = new System.Drawing.Point(0, 0); this.panelProbeA.Name = "panelProbeA"; - this.panelProbeA.Size = new System.Drawing.Size(983, 666); + this.panelProbeA.Size = new System.Drawing.Size(932, 663); this.panelProbeA.TabIndex = 0; // // tabPageProbeB @@ -398,12 +402,12 @@ private void InitializeComponent() // tabControlOptions // this.tabControlOptions.Controls.Add(this.tabPageOptions); - this.tabControlOptions.Controls.Add(this.tabPageContactsOptions); + this.tabControlOptions.Controls.Add(this.tabPageElectrodes); this.tabControlOptions.Dock = System.Windows.Forms.DockStyle.Fill; this.tabControlOptions.Location = new System.Drawing.Point(0, 0); this.tabControlOptions.Name = "tabControlOptions"; this.tabControlOptions.SelectedIndex = 0; - this.tabControlOptions.Size = new System.Drawing.Size(270, 699); + this.tabControlOptions.Size = new System.Drawing.Size(321, 696); this.tabControlOptions.TabIndex = 0; // // tabPageOptions @@ -412,7 +416,7 @@ private void InitializeComponent() this.tabPageOptions.Location = new System.Drawing.Point(4, 29); this.tabPageOptions.Name = "tabPageOptions"; this.tabPageOptions.Padding = new System.Windows.Forms.Padding(3); - this.tabPageOptions.Size = new System.Drawing.Size(262, 666); + this.tabPageOptions.Size = new System.Drawing.Size(262, 663); this.tabPageOptions.TabIndex = 0; this.tabPageOptions.Text = "Options"; this.tabPageOptions.UseVisualStyleBackColor = true; @@ -434,7 +438,7 @@ private void InitializeComponent() this.panelOptions.Dock = System.Windows.Forms.DockStyle.Fill; this.panelOptions.Location = new System.Drawing.Point(3, 3); this.panelOptions.Name = "panelOptions"; - this.panelOptions.Size = new System.Drawing.Size(256, 660); + this.panelOptions.Size = new System.Drawing.Size(256, 657); this.panelOptions.TabIndex = 0; // // buttonClearCalibrationFileB @@ -515,18 +519,20 @@ private void InitializeComponent() this.comboBoxReferenceA.Size = new System.Drawing.Size(121, 28); this.comboBoxReferenceA.TabIndex = 5; // - // tabPageContactsOptions + // tabPageElectrodes // - this.tabPageContactsOptions.Controls.Add(this.panelChannelOptions); - this.tabPageContactsOptions.Location = new System.Drawing.Point(4, 29); - this.tabPageContactsOptions.Name = "tabPageContactsOptions"; - this.tabPageContactsOptions.Size = new System.Drawing.Size(262, 666); - this.tabPageContactsOptions.TabIndex = 2; - this.tabPageContactsOptions.Text = "Contacts"; - this.tabPageContactsOptions.UseVisualStyleBackColor = true; + this.tabPageElectrodes.Controls.Add(this.panelChannelOptions); + this.tabPageElectrodes.Location = new System.Drawing.Point(4, 29); + this.tabPageElectrodes.Name = "tabPageElectrodes"; + this.tabPageElectrodes.Size = new System.Drawing.Size(313, 663); + this.tabPageElectrodes.TabIndex = 2; + this.tabPageElectrodes.Text = "Electrodes"; + this.tabPageElectrodes.UseVisualStyleBackColor = true; // // panelChannelOptions // + this.panelChannelOptions.Controls.Add(label7); + this.panelChannelOptions.Controls.Add(label6); this.panelChannelOptions.Controls.Add(label5); this.panelChannelOptions.Controls.Add(this.comboBoxChannelPresetsB); this.panelChannelOptions.Controls.Add(label4); @@ -544,14 +550,14 @@ private void InitializeComponent() this.panelChannelOptions.Dock = System.Windows.Forms.DockStyle.Fill; this.panelChannelOptions.Location = new System.Drawing.Point(0, 0); this.panelChannelOptions.Name = "panelChannelOptions"; - this.panelChannelOptions.Size = new System.Drawing.Size(262, 666); + this.panelChannelOptions.Size = new System.Drawing.Size(313, 663); this.panelChannelOptions.TabIndex = 0; // // comboBoxChannelPresetsB // this.comboBoxChannelPresetsB.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxChannelPresetsB.FormattingEnabled = true; - this.comboBoxChannelPresetsB.Location = new System.Drawing.Point(92, 519); + this.comboBoxChannelPresetsB.Location = new System.Drawing.Point(143, 520); this.comboBoxChannelPresetsB.Name = "comboBoxChannelPresetsB"; this.comboBoxChannelPresetsB.Size = new System.Drawing.Size(162, 28); this.comboBoxChannelPresetsB.TabIndex = 26; @@ -560,7 +566,7 @@ private void InitializeComponent() // this.comboBoxChannelPresetsA.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxChannelPresetsA.FormattingEnabled = true; - this.comboBoxChannelPresetsA.Location = new System.Drawing.Point(92, 442); + this.comboBoxChannelPresetsA.Location = new System.Drawing.Point(143, 443); this.comboBoxChannelPresetsA.Name = "comboBoxChannelPresetsA"; this.comboBoxChannelPresetsA.Size = new System.Drawing.Size(162, 28); this.comboBoxChannelPresetsA.TabIndex = 24; @@ -569,17 +575,21 @@ private void InitializeComponent() // this.trackBarProbePosition.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left))); + this.trackBarProbePosition.AutoSize = false; this.trackBarProbePosition.Location = new System.Drawing.Point(17, 39); this.trackBarProbePosition.Maximum = 100; this.trackBarProbePosition.Name = "trackBarProbePosition"; this.trackBarProbePosition.Orientation = System.Windows.Forms.Orientation.Vertical; - this.trackBarProbePosition.Size = new System.Drawing.Size(69, 603); + this.trackBarProbePosition.Size = new System.Drawing.Size(56, 600); this.trackBarProbePosition.TabIndex = 22; + this.trackBarProbePosition.TickFrequency = 2; + this.trackBarProbePosition.TickStyle = System.Windows.Forms.TickStyle.Both; + this.trackBarProbePosition.Value = 50; this.trackBarProbePosition.Scroll += new System.EventHandler(this.TrackBarScroll); // // buttonEnableContacts // - this.buttonEnableContacts.Location = new System.Drawing.Point(130, 231); + this.buttonEnableContacts.Location = new System.Drawing.Point(181, 232); this.buttonEnableContacts.Name = "buttonEnableContacts"; this.buttonEnableContacts.Size = new System.Drawing.Size(96, 56); this.buttonEnableContacts.TabIndex = 20; @@ -589,7 +599,7 @@ private void InitializeComponent() // // buttonClearSelections // - this.buttonClearSelections.Location = new System.Drawing.Point(130, 293); + this.buttonClearSelections.Location = new System.Drawing.Point(181, 294); this.buttonClearSelections.Name = "buttonClearSelections"; this.buttonClearSelections.Size = new System.Drawing.Size(96, 59); this.buttonClearSelections.TabIndex = 19; @@ -599,7 +609,7 @@ private void InitializeComponent() // // buttonResetZoom // - this.buttonResetZoom.Location = new System.Drawing.Point(130, 131); + this.buttonResetZoom.Location = new System.Drawing.Point(181, 132); this.buttonResetZoom.Name = "buttonResetZoom"; this.buttonResetZoom.Size = new System.Drawing.Size(96, 34); this.buttonResetZoom.TabIndex = 4; @@ -609,7 +619,7 @@ private void InitializeComponent() // // buttonZoomOut // - this.buttonZoomOut.Location = new System.Drawing.Point(130, 91); + this.buttonZoomOut.Location = new System.Drawing.Point(181, 92); this.buttonZoomOut.Name = "buttonZoomOut"; this.buttonZoomOut.Size = new System.Drawing.Size(96, 34); this.buttonZoomOut.TabIndex = 3; @@ -619,7 +629,7 @@ private void InitializeComponent() // // buttonZoomIn // - this.buttonZoomIn.Location = new System.Drawing.Point(130, 51); + this.buttonZoomIn.Location = new System.Drawing.Point(181, 52); this.buttonZoomIn.Name = "buttonZoomIn"; this.buttonZoomIn.Size = new System.Drawing.Size(96, 34); this.buttonZoomIn.TabIndex = 2; @@ -672,6 +682,25 @@ private void InitializeComponent() this.linkLabelDocumentation.Text = "Documentation"; this.linkLabelDocumentation.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkClicked); // + // label6 + // + label6.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + label6.AutoSize = true; + label6.Location = new System.Drawing.Point(72, 610); + label6.Name = "label6"; + label6.Size = new System.Drawing.Size(48, 20); + label6.TabIndex = 28; + label6.Text = "0 mm"; + // + // label7 + // + label7.AutoSize = true; + label7.Location = new System.Drawing.Point(72, 48); + label7.Name = "label7"; + label7.Size = new System.Drawing.Size(57, 20); + label7.TabIndex = 29; + label7.Text = "10 mm"; + // // NeuropixelsV2eDialog // this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); @@ -705,7 +734,7 @@ private void InitializeComponent() this.tabPageOptions.ResumeLayout(false); this.panelOptions.ResumeLayout(false); this.panelOptions.PerformLayout(); - this.tabPageContactsOptions.ResumeLayout(false); + this.tabPageElectrodes.ResumeLayout(false); this.panelChannelOptions.ResumeLayout(false); this.panelChannelOptions.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).EndInit(); @@ -738,7 +767,7 @@ private void InitializeComponent() private System.Windows.Forms.Button buttonGainCalibrationFileA; private System.Windows.Forms.Button buttonGainCalibrationFileB; private System.Windows.Forms.TextBox textBoxProbeCalibrationFileB; - private System.Windows.Forms.TabPage tabPageContactsOptions; + private System.Windows.Forms.TabPage tabPageElectrodes; private System.Windows.Forms.Panel panelChannelOptions; private System.Windows.Forms.Button buttonZoomIn; private System.Windows.Forms.Button buttonResetZoom; diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs index 6d5edd84..302df1ee 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs @@ -767,6 +767,7 @@ private void ResetZoom(NeuropixelsV2Probe probeSelected) var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; channelConfiguration.ResetZoom(); + channelConfiguration.DrawScale(); channelConfiguration.RefreshZedGraph(); } diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx index a4d8141e..024c125b 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx @@ -165,4 +165,10 @@ 165, 17 + + False + + + False + \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs index e2c950ab..45cafc83 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs @@ -56,7 +56,7 @@ private void InitializeComponent() this.tabControl1.Location = new System.Drawing.Point(0, 0); this.tabControl1.Name = "tabControl1"; this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(1295, 722); + this.tabControl1.Size = new System.Drawing.Size(1295, 719); this.tabControl1.TabIndex = 0; // // tabPageNeuropixelsV2e @@ -65,7 +65,7 @@ private void InitializeComponent() this.tabPageNeuropixelsV2e.Location = new System.Drawing.Point(4, 29); this.tabPageNeuropixelsV2e.Name = "tabPageNeuropixelsV2e"; this.tabPageNeuropixelsV2e.Padding = new System.Windows.Forms.Padding(3); - this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(1287, 689); + this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(1287, 686); this.tabPageNeuropixelsV2e.TabIndex = 0; this.tabPageNeuropixelsV2e.Text = "NeuropixelsV2e"; this.tabPageNeuropixelsV2e.UseVisualStyleBackColor = true; @@ -75,7 +75,7 @@ private void InitializeComponent() this.panelNeuropixelsV2e.Dock = System.Windows.Forms.DockStyle.Fill; this.panelNeuropixelsV2e.Location = new System.Drawing.Point(3, 3); this.panelNeuropixelsV2e.Name = "panelNeuropixelsV2e"; - this.panelNeuropixelsV2e.Size = new System.Drawing.Size(1281, 683); + this.panelNeuropixelsV2e.Size = new System.Drawing.Size(1281, 680); this.panelNeuropixelsV2e.TabIndex = 0; // // tabPageBno055 @@ -101,7 +101,7 @@ private void InitializeComponent() // this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; - this.splitContainer1.Location = new System.Drawing.Point(0, 33); + this.splitContainer1.Location = new System.Drawing.Point(0, 36); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -113,8 +113,8 @@ private void InitializeComponent() // this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); this.splitContainer1.Panel2.Controls.Add(this.buttonOkay); - this.splitContainer1.Size = new System.Drawing.Size(1295, 778); - this.splitContainer1.SplitterDistance = 722; + this.splitContainer1.Size = new System.Drawing.Size(1295, 775); + this.splitContainer1.SplitterDistance = 719; this.splitContainer1.TabIndex = 1; // // buttonCancel @@ -147,14 +147,14 @@ private void InitializeComponent() this.fileToolStripMenuItem}); this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Name = "menuStrip1"; - this.menuStrip1.Size = new System.Drawing.Size(1295, 33); + this.menuStrip1.Size = new System.Drawing.Size(1295, 36); this.menuStrip1.TabIndex = 2; this.menuStrip1.Text = "menuStrip1"; // // fileToolStripMenuItem // this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 29); + this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 32); this.fileToolStripMenuItem.Text = "File"; // // NeuropixelsV2eHeadstageDialog diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs index 8c46aded..103c238f 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs @@ -14,17 +14,17 @@ public override bool EditComponent(ITypeDescriptorContext context, object compon var editorState = (IWorkflowEditorState)provider.GetService(typeof(IWorkflowEditorState)); if (editorState != null && !editorState.WorkflowRunning && component is ConfigureNeuropixelsV2eHeadstage configureHeadstage) { - using var editorDialog = new NeuropixelsV2eHeadstageDialog(configureHeadstage.NeuropixelsV2, configureHeadstage.Bno055); + using var editorDialog = new NeuropixelsV2eHeadstageDialog(configureHeadstage.NeuropixelsV2e, configureHeadstage.Bno055); if (editorDialog.ShowDialog() == DialogResult.OK) { configureHeadstage.Bno055.Enable = editorDialog.ConfigureBno055.ConfigureNode.Enable; - configureHeadstage.NeuropixelsV2.Enable = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.Enable; - configureHeadstage.NeuropixelsV2.ProbeConfigurationA = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.ProbeConfigurationA; - configureHeadstage.NeuropixelsV2.ProbeConfigurationB = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.ProbeConfigurationB; - configureHeadstage.NeuropixelsV2.GainCalibrationFileA = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.GainCalibrationFileA; - configureHeadstage.NeuropixelsV2.GainCalibrationFileB = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.GainCalibrationFileB; + configureHeadstage.NeuropixelsV2e.Enable = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.Enable; + configureHeadstage.NeuropixelsV2e.ProbeConfigurationA = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.ProbeConfigurationA; + configureHeadstage.NeuropixelsV2e.ProbeConfigurationB = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.ProbeConfigurationB; + configureHeadstage.NeuropixelsV2e.GainCalibrationFileA = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.GainCalibrationFileA; + configureHeadstage.NeuropixelsV2e.GainCalibrationFileB = editorDialog.ConfigureNeuropixelsV2e.ConfigureNode.GainCalibrationFileB; return true; } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs index 0da820fb..1e3e82af 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs @@ -25,7 +25,7 @@ public ConfigureNeuropixelsV2eBeta() public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(); [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] - [Description("Path to the gain calibraiton file for probe A.")] + [Description("Path to the gain calibration file for probe A.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] public string GainCalibrationFileA { get; set; } @@ -34,7 +34,7 @@ public ConfigureNeuropixelsV2eBeta() public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(); [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] - [Description("Path to the gain calibraiton file for probe B.")] + [Description("Path to the gain calibration file for probe B.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] public string GainCalibrationFileB { get; set; } @@ -169,7 +169,7 @@ static ushort ReadGainCorrection(string gainCalibrationFile, ulong probeSerialNu { if (gainCalibrationFile == null) { - throw new ArgumentException("Calibraiton file must be specified."); + throw new ArgumentException("Calibration file must be specified."); } System.IO.StreamReader gainFile = new(gainCalibrationFile); @@ -177,7 +177,7 @@ static ushort ReadGainCorrection(string gainCalibrationFile, ulong probeSerialNu if (probeSerialNumber != sn) { - throw new ArgumentException($"Probe serial number {probeSerialNumber} does not match calibraiton file serial number {sn}."); + throw new ArgumentException($"Probe serial number {probeSerialNumber} does not match calibration file serial number {sn}."); } // Q1.14 fixed point conversion diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs index 2595c559..e3283e1d 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs @@ -17,7 +17,7 @@ public ConfigureNeuropixelsV2eHeadstage() [Category(ConfigurationCategory)] [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureNeuropixelsV2e NeuropixelsV2 { get; set; } = new(); + public ConfigureNeuropixelsV2e NeuropixelsV2e { get; set; } = new(); [Category(ConfigurationCategory)] [TypeConverter(typeof(HubDeviceConverter))] @@ -31,7 +31,7 @@ public PortName Port port = value; var offset = (uint)port << 8; LinkController.DeviceAddress = (uint)port; - NeuropixelsV2.DeviceAddress = offset + 0; + NeuropixelsV2e.DeviceAddress = offset + 0; Bno055.DeviceAddress = offset + 1; } } @@ -48,7 +48,7 @@ public double? PortVoltage internal override IEnumerable GetDevices() { yield return LinkController; - yield return NeuropixelsV2; + yield return NeuropixelsV2e; yield return Bno055; } } From e58d803fe12bef1a0e377038dfa89662ed205c57 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 2 Aug 2024 13:01:22 -0400 Subject: [PATCH 7/8] Merge changes from main --- {Bonsai => .bonsai}/Bonsai.config | 0 {Bonsai => .bonsai}/NuGet.config | 0 {Bonsai => .bonsai}/Setup.cmd | 0 {Bonsai => .bonsai}/Setup.ps1 | 0 .github/workflows/build.yml | 80 +++ .gitignore | 14 +- .gitmodules | 4 +- ...ctory.Build.props => Directory.Build.props | 20 +- OpenEphys.Onix/NuGet.config => NuGet.config | 0 OpenEphys.Onix/OpenEphys.Onix/AnalogInput.cs | 87 --- .../OpenEphys.Onix/AnalogInputDataFrame.cs | 28 - OpenEphys.Onix/OpenEphys.Onix/AnalogOutput.cs | 136 ----- OpenEphys.Onix/OpenEphys.Onix/Bno055Data.cs | 27 - .../OpenEphys.Onix/ConfigureAnalogIO.cs | 268 --------- .../OpenEphys.Onix/ConfigureBreakoutBoard.cs | 28 - .../OpenEphys.Onix/ConfigureDigitalIO.cs | 76 --- .../OpenEphys.Onix/ConfigureHarpSyncInput.cs | 58 -- .../OpenEphys.Onix/ConfigureHeadstage64.cs | 111 ---- .../OpenEphys.Onix/ConfigureHeartbeat.cs | 70 --- .../OpenEphys.Onix/ConfigureMemoryMonitor.cs | 57 -- .../ConfigureNeuropixelsV2eBetaHeadstage.cs | 54 -- .../ConfigureNeuropixelsV2eHeadstage.cs | 55 -- .../OpenEphys.Onix/ConfigureTS4231.cs | 45 -- .../OpenEphys.Onix/ConfigureTest0.cs | 71 --- .../OpenEphys.Onix/ContextHelper.cs | 47 -- OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs | 430 -------------- .../OpenEphys.Onix/CreateContext.cs | 37 -- .../OpenEphys.Onix/DeviceFactory.cs | 35 -- OpenEphys.Onix/OpenEphys.Onix/DeviceID.cs | 37 -- OpenEphys.Onix/OpenEphys.Onix/DigitalInput.cs | 27 - .../OpenEphys.Onix/DigitalInputDataFrame.cs | 33 -- .../OpenEphys.Onix/DigitalOutput.cs | 25 - .../OpenEphys.Onix/HarpSyncInputData.cs | 27 - .../OpenEphys.Onix/HarpSyncInputDataFrame.cs | 28 - .../Headstage64ElectricalStimulatorTrigger.cs | 182 ------ .../OpenEphys.Onix/HeartbeatCounter.cs | 27 - .../OpenEphys.Onix/HubConfiguration.cs | 8 - .../OpenEphys.Onix/HubDeviceFactory.cs | 57 -- OpenEphys.Onix/OpenEphys.Onix/ManagedFrame.cs | 22 - OpenEphys.Onix/OpenEphys.Onix/MemoryUsage.cs | 29 - .../OpenEphys.Onix/MemoryUsageDataFrame.cs | 37 -- .../OpenEphys.Onix/NeuropixelsV1eAdc.cs | 14 - .../NeuropixelsV1eBno055Data.cs | 59 -- .../OpenEphys.Onix/NeuropixelsV1eData.cs | 73 --- ...europixelsV2QuadShankProbeConfiguration.cs | 86 --- .../OpenEphys.Onix/NeuropixelsV2eBetaData.cs | 78 --- .../NeuropixelsV2eBno055Data.cs | 59 -- .../OpenEphys.Onix/NeuropixelsV2eData.cs | 71 --- .../OpenEphys.Onix/Rhd2164Config.cs | 205 ------- OpenEphys.Onix/OpenEphys.Onix/Rhd2164Data.cs | 60 -- .../OpenEphys.Onix/Rhd2164DataFrame.cs | 32 -- .../OpenEphys.Onix/StartAcquisition.cs | 40 -- OpenEphys.Onix/OpenEphys.Onix/TS4231Data.cs | 27 - .../OpenEphys.Onix/TS4231DataFrame.cs | 47 -- OpenEphys.Onix/OpenEphys.Onix/Test0Data.cs | 69 --- .../OpenEphys.Onix/Test0DataFrame.cs | 31 -- OpenEphys.Onix/OpenEphys.ProbeInterface | 1 - .../ChannelConfigurationDialog.Designer.cs | 2 +- .../ChannelConfigurationDialog.cs | 6 +- .../ChannelConfigurationDialog.resx | 0 .../ContactTag.cs | 2 +- .../DesignHelper.cs | 2 +- .../GenericDeviceDialog.Designer.cs | 2 +- .../GenericDeviceDialog.cs | 2 +- .../GenericDeviceDialog.resx | 0 .../NeuropixelsV2eBno055Dialog.Designer.cs | 2 +- .../NeuropixelsV2eBno055Dialog.cs | 2 +- .../NeuropixelsV2eBno055Editor.cs | 2 +- ...sV2eChannelConfigurationDialog.Designer.cs | 4 +- ...europixelsV2eChannelConfigurationDialog.cs | 2 +- .../NeuropixelsV2eDialog.Designer.cs | 2 +- .../NeuropixelsV2eDialog.cs | 2 +- .../NeuropixelsV2eDialog.resx | 0 .../NeuropixelsV2eEditor.cs | 2 +- .../NeuropixelsV2eHeadstageDialog.Designer.cs | 2 +- .../NeuropixelsV2eHeadstageDialog.cs | 2 +- .../NeuropixelsV2eHeadstageDialog.resx | 0 .../NeuropixelsV2eHeadstageEditor.cs | 2 +- .../OpenEphys.Onix1.Design.csproj | 11 +- .../OpenEphys.Onix1.Design.csproj.user | 29 + .../Properties/Resources.Designer.cs | 4 +- .../Properties/Resources.resx | 0 .../Properties/launchSettings.json | 2 +- .../Resources/StatusBlockedImage.png | Bin .../Resources/StatusCriticalImage.png | Bin .../Resources/StatusReadyImage.png | Bin .../Resources/StatusRefreshImage.png | Bin .../Resources/StatusWarningImage.png | Bin .../Resources/UploadImage.png | Bin .../OpenEphys.Onix.sln => OpenEphys.Onix1.sln | 52 +- .../BitHelper.cs | 2 +- OpenEphys.Onix1/Bno055Data.cs | 40 ++ .../Bno055DataFrame.cs | 61 +- OpenEphys.Onix1/BreakoutAnalogInput.cs | 117 ++++ .../BreakoutAnalogInputDataFrame.cs | 35 ++ OpenEphys.Onix1/BreakoutAnalogOutput.cs | 161 ++++++ OpenEphys.Onix1/BreakoutDigitalInput.cs | 44 ++ .../BreakoutDigitalInputDataFrame.cs | 41 ++ OpenEphys.Onix1/BreakoutDigitalOutput.cs | 34 ++ .../BufferHelper.cs | 2 +- .../ConfigureBno055.cs | 30 +- OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs | 397 +++++++++++++ OpenEphys.Onix1/ConfigureBreakoutBoard.cs | 48 ++ OpenEphys.Onix1/ConfigureBreakoutDigitalIO.cs | 166 ++++++ .../ConfigureFmcLinkController.cs | 36 +- OpenEphys.Onix1/ConfigureHarpSyncInput.cs | 126 +++++ OpenEphys.Onix1/ConfigureHeadstage64.cs | 193 +++++++ ...onfigureHeadstage64ElectricalStimulator.cs | 31 +- .../ConfigureHeadstage64OpticalStimulator.cs | 81 +++ OpenEphys.Onix1/ConfigureHeartbeat.cs | 104 ++++ .../ConfigureLoadTester.cs | 73 ++- OpenEphys.Onix1/ConfigureMemoryMonitor.cs | 95 ++++ .../ConfigureNeuropixelsV1e.cs | 124 ++++- .../ConfigureNeuropixelsV1eBno055.cs | 30 +- .../ConfigureNeuropixelsV1eHeadstage.cs | 49 +- .../ConfigureNeuropixelsV2e.cs | 57 +- .../ConfigureNeuropixelsV2eBeta.cs | 60 +- .../ConfigureNeuropixelsV2eBetaHeadstage.cs | 85 +++ .../ConfigureNeuropixelsV2eBno055.cs | 32 +- .../ConfigureNeuropixelsV2eHeadstage.cs | 86 +++ .../ConfigureNeuropixelsV2eLinkController.cs | 2 +- .../ConfigureRhd2164.cs | 58 +- OpenEphys.Onix1/ConfigureTS4231V1.cs | 73 +++ OpenEphys.Onix1/ContextHelper.cs | 72 +++ OpenEphys.Onix1/ContextTask.cs | 524 ++++++++++++++++++ OpenEphys.Onix1/CreateContext.cs | 62 +++ .../DS90UB9x.cs | 30 +- OpenEphys.Onix1/DataFrame.cs | 68 +++ .../DeviceContext.cs | 6 +- OpenEphys.Onix1/DeviceFactory.cs | 82 +++ .../DeviceInfo.cs | 4 +- .../DeviceManager.cs | 75 ++- .../DeviceNameConverter.cs | 2 +- .../Electrode.cs | 2 +- OpenEphys.Onix1/HarpSyncInputData.cs | 38 ++ OpenEphys.Onix1/HarpSyncInputDataFrame.cs | 36 ++ .../Headstage64ElectricalStimulatorTrigger.cs | 240 ++++++++ .../Headstage64OpticalStimulatorTrigger.cs | 270 +++++++++ OpenEphys.Onix1/HeartbeatData.cs | 40 ++ OpenEphys.Onix1/HeartbeatDataFrame.cs | 27 + .../I2CRegisterContext.cs | 2 +- .../IDeviceConfiguration.cs | 2 +- .../MatHelper.cs | 2 +- OpenEphys.Onix1/MemoryMonitorData.cs | 42 ++ OpenEphys.Onix1/MemoryMonitorDataFrame.cs | 43 ++ OpenEphys.Onix1/MultiDeviceFactory.cs | 92 +++ OpenEphys.Onix1/NeuropixelsV1eAdc.cs | 41 ++ OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs | 70 +++ OpenEphys.Onix1/NeuropixelsV1eData.cs | 90 +++ .../NeuropixelsV1eDataFrame.cs | 44 +- .../NeuropixelsV1eDeviceInfo.cs | 2 +- .../NeuropixelsV1eMetadata.cs | 2 +- .../NeuropixelsV1eRegisterContext.cs | 28 +- .../NeuropixelsV2.cs | 11 +- .../NeuropixelsV2QuadShankElectrode.cs | 2 +- ...europixelsV2QuadShankProbeConfiguration.cs | 158 ++++++ .../NeuropixelsV2RegisterContext.cs | 2 +- OpenEphys.Onix1/NeuropixelsV2eBetaData.cs | 97 ++++ .../NeuropixelsV2eBetaDataFrame.cs | 38 +- .../NeuropixelsV2eBetaMetadata.cs | 2 +- OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs | 70 +++ OpenEphys.Onix1/NeuropixelsV2eData.cs | 93 ++++ .../NeuropixelsV2eDataFrame.cs | 23 +- .../NeuropixelsV2eDeviceInfo.cs | 2 +- .../NeuropixelsV2eMetadata.cs | 2 +- .../NeuropixelsV2eProbeGroup.cs | 2 +- .../ObservableExtensions.cs | 28 +- .../OpenEphys.Onix1.csproj | 9 +- .../PassthroughState.cs | 4 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/launchSettings.json | 2 +- OpenEphys.Onix1/Rhd2164Config.cs | 319 +++++++++++ OpenEphys.Onix1/Rhd2164Data.cs | 82 +++ OpenEphys.Onix1/Rhd2164DataFrame.cs | 51 ++ .../SingleDeviceFactoryConverter.cs | 4 +- .../StackDisposable.cs | 2 +- OpenEphys.Onix1/StartAcquisition.cs | 75 +++ OpenEphys.Onix1/TS4231V1Data.cs | 49 ++ OpenEphys.Onix1/TS4231V1DataFrame.cs | 97 ++++ OpenEphys.Onix1/TS4231V1PositionConverter.cs | 164 ++++++ OpenEphys.Onix1/TS4231V1PositionData.cs | 95 ++++ OpenEphys.Onix1/TS4231V1PositionDataFrame.cs | 48 ++ OpenEphys.ProbeInterface | 1 + README.md | 10 +- build/Version.props | 20 + 185 files changed, 6125 insertions(+), 3517 deletions(-) rename {Bonsai => .bonsai}/Bonsai.config (100%) rename {Bonsai => .bonsai}/NuGet.config (100%) rename {Bonsai => .bonsai}/Setup.cmd (100%) rename {Bonsai => .bonsai}/Setup.ps1 (100%) create mode 100644 .github/workflows/build.yml rename OpenEphys.Onix/Directory.Build.props => Directory.Build.props (50%) rename OpenEphys.Onix/NuGet.config => NuGet.config (100%) delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/AnalogInput.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/AnalogInputDataFrame.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/AnalogOutput.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/Bno055Data.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureAnalogIO.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureBreakoutBoard.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureDigitalIO.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureHarpSyncInput.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstage64.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureHeartbeat.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureMemoryMonitor.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBetaHeadstage.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureTS4231.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureTest0.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ContextHelper.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/CreateContext.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/DeviceFactory.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/DeviceID.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/DigitalInput.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/DigitalInputDataFrame.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/DigitalOutput.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/HarpSyncInputData.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/HarpSyncInputDataFrame.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/Headstage64ElectricalStimulatorTrigger.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/HeartbeatCounter.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/HubConfiguration.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/HubDeviceFactory.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/ManagedFrame.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/MemoryUsage.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/MemoryUsageDataFrame.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eAdc.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eBno055Data.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eData.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankProbeConfiguration.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaData.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBno055Data.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eData.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/Rhd2164Config.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/Rhd2164Data.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/Rhd2164DataFrame.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/StartAcquisition.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/TS4231Data.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/TS4231DataFrame.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/Test0Data.cs delete mode 100644 OpenEphys.Onix/OpenEphys.Onix/Test0DataFrame.cs delete mode 160000 OpenEphys.Onix/OpenEphys.ProbeInterface rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/ChannelConfigurationDialog.Designer.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/ChannelConfigurationDialog.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/ChannelConfigurationDialog.resx (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/ContactTag.cs (98%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/DesignHelper.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/GenericDeviceDialog.Designer.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/GenericDeviceDialog.cs (95%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/GenericDeviceDialog.resx (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eBno055Dialog.Designer.cs (97%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eBno055Dialog.cs (95%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eBno055Editor.cs (96%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eChannelConfigurationDialog.Designer.cs (97%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eChannelConfigurationDialog.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eDialog.Designer.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eDialog.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eDialog.resx (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eEditor.cs (97%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eHeadstageDialog.Designer.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eHeadstageDialog.cs (98%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eHeadstageDialog.resx (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/NeuropixelsV2eHeadstageEditor.cs (98%) rename OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj => OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj (78%) create mode 100644 OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj.user rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/Properties/Resources.Designer.cs (97%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/Properties/Resources.resx (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/Properties/launchSettings.json (71%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/Resources/StatusBlockedImage.png (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/Resources/StatusCriticalImage.png (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/Resources/StatusReadyImage.png (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/Resources/StatusRefreshImage.png (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/Resources/StatusWarningImage.png (100%) rename {OpenEphys.Onix/OpenEphys.Onix.Design => OpenEphys.Onix1.Design}/Resources/UploadImage.png (100%) rename OpenEphys.Onix/OpenEphys.Onix.sln => OpenEphys.Onix1.sln (56%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/BitHelper.cs (88%) create mode 100644 OpenEphys.Onix1/Bno055Data.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/Bno055DataFrame.cs (54%) create mode 100644 OpenEphys.Onix1/BreakoutAnalogInput.cs create mode 100644 OpenEphys.Onix1/BreakoutAnalogInputDataFrame.cs create mode 100644 OpenEphys.Onix1/BreakoutAnalogOutput.cs create mode 100644 OpenEphys.Onix1/BreakoutDigitalInput.cs create mode 100644 OpenEphys.Onix1/BreakoutDigitalInputDataFrame.cs create mode 100644 OpenEphys.Onix1/BreakoutDigitalOutput.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/BufferHelper.cs (98%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureBno055.cs (51%) create mode 100644 OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs create mode 100644 OpenEphys.Onix1/ConfigureBreakoutBoard.cs create mode 100644 OpenEphys.Onix1/ConfigureBreakoutDigitalIO.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureFmcLinkController.cs (75%) create mode 100644 OpenEphys.Onix1/ConfigureHarpSyncInput.cs create mode 100644 OpenEphys.Onix1/ConfigureHeadstage64.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureHeadstage64ElectricalStimulator.cs (62%) create mode 100644 OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs create mode 100644 OpenEphys.Onix1/ConfigureHeartbeat.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureLoadTester.cs (56%) create mode 100644 OpenEphys.Onix1/ConfigureMemoryMonitor.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureNeuropixelsV1e.cs (68%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureNeuropixelsV1eBno055.cs (66%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureNeuropixelsV1eHeadstage.cs (54%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureNeuropixelsV2e.cs (82%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureNeuropixelsV2eBeta.cs (82%) create mode 100644 OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureNeuropixelsV2eBno055.cs (67%) create mode 100644 OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureNeuropixelsV2eLinkController.cs (97%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ConfigureRhd2164.cs (69%) create mode 100644 OpenEphys.Onix1/ConfigureTS4231V1.cs create mode 100644 OpenEphys.Onix1/ContextHelper.cs create mode 100644 OpenEphys.Onix1/ContextTask.cs create mode 100644 OpenEphys.Onix1/CreateContext.cs rename OpenEphys.Onix/OpenEphys.Onix/ConfigureDS90UB9x.cs => OpenEphys.Onix1/DS90UB9x.cs (73%) create mode 100644 OpenEphys.Onix1/DataFrame.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/DeviceContext.cs (90%) create mode 100644 OpenEphys.Onix1/DeviceFactory.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/DeviceInfo.cs (89%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/DeviceManager.cs (63%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/DeviceNameConverter.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/Electrode.cs (97%) create mode 100644 OpenEphys.Onix1/HarpSyncInputData.cs create mode 100644 OpenEphys.Onix1/HarpSyncInputDataFrame.cs create mode 100644 OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs create mode 100644 OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs create mode 100644 OpenEphys.Onix1/HeartbeatData.cs create mode 100644 OpenEphys.Onix1/HeartbeatDataFrame.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/I2CRegisterContext.cs (98%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/IDeviceConfiguration.cs (89%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/MatHelper.cs (99%) create mode 100644 OpenEphys.Onix1/MemoryMonitorData.cs create mode 100644 OpenEphys.Onix1/MemoryMonitorDataFrame.cs create mode 100644 OpenEphys.Onix1/MultiDeviceFactory.cs create mode 100644 OpenEphys.Onix1/NeuropixelsV1eAdc.cs create mode 100644 OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs create mode 100644 OpenEphys.Onix1/NeuropixelsV1eData.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV1eDataFrame.cs (73%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV1eDeviceInfo.cs (96%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV1eMetadata.cs (98%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV1eRegisterContext.cs (93%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV2.cs (81%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV2QuadShankElectrode.cs (99%) create mode 100644 OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV2RegisterContext.cs (99%) create mode 100644 OpenEphys.Onix1/NeuropixelsV2eBetaData.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV2eBetaDataFrame.cs (79%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV2eBetaMetadata.cs (98%) create mode 100644 OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs create mode 100644 OpenEphys.Onix1/NeuropixelsV2eData.cs rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV2eDataFrame.cs (87%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV2eDeviceInfo.cs (95%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV2eMetadata.cs (98%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/NeuropixelsV2eProbeGroup.cs (99%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/ObservableExtensions.cs (70%) rename OpenEphys.Onix/OpenEphys.Onix/OpenEphys.Onix.csproj => OpenEphys.Onix1/OpenEphys.Onix1.csproj (67%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/PassthroughState.cs (60%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/Properties/AssemblyInfo.cs (76%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/Properties/launchSettings.json (71%) create mode 100644 OpenEphys.Onix1/Rhd2164Config.cs create mode 100644 OpenEphys.Onix1/Rhd2164Data.cs create mode 100644 OpenEphys.Onix1/Rhd2164DataFrame.cs rename OpenEphys.Onix/OpenEphys.Onix/HubDeviceConverter.cs => OpenEphys.Onix1/SingleDeviceFactoryConverter.cs (91%) rename {OpenEphys.Onix/OpenEphys.Onix => OpenEphys.Onix1}/StackDisposable.cs (97%) create mode 100644 OpenEphys.Onix1/StartAcquisition.cs create mode 100644 OpenEphys.Onix1/TS4231V1Data.cs create mode 100644 OpenEphys.Onix1/TS4231V1DataFrame.cs create mode 100644 OpenEphys.Onix1/TS4231V1PositionConverter.cs create mode 100644 OpenEphys.Onix1/TS4231V1PositionData.cs create mode 100644 OpenEphys.Onix1/TS4231V1PositionDataFrame.cs create mode 160000 OpenEphys.ProbeInterface create mode 100644 build/Version.props diff --git a/Bonsai/Bonsai.config b/.bonsai/Bonsai.config similarity index 100% rename from Bonsai/Bonsai.config rename to .bonsai/Bonsai.config diff --git a/Bonsai/NuGet.config b/.bonsai/NuGet.config similarity index 100% rename from Bonsai/NuGet.config rename to .bonsai/NuGet.config diff --git a/Bonsai/Setup.cmd b/.bonsai/Setup.cmd similarity index 100% rename from Bonsai/Setup.cmd rename to .bonsai/Setup.cmd diff --git a/Bonsai/Setup.ps1 b/.bonsai/Setup.ps1 similarity index 100% rename from Bonsai/Setup.ps1 rename to .bonsai/Setup.ps1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..8199ba66 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,80 @@ +name: Build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_GENERATE_ASPNET_CERTIFICATE: false + ContinuousIntegrationBuild: true + CiRunNumber: ${{ github.run_number }} + CiRunPushSuffix: ${{ github.ref_name }}-ci${{ github.run_number }} + CiRunPullSuffix: pull-${{ github.event.number }}-ci${{ github.run_number }} +jobs: + build: + strategy: + fail-fast: false + matrix: + configuration: [debug, release] + os: [ubuntu-latest, windows-latest] + include: + - os: windows-latest + configuration: release + collect-packages: true + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Restore + run: dotnet restore + + - name: Build + run: dotnet build --no-restore --configuration ${{ matrix.configuration }} + + - name: Pack + id: pack + if: matrix.collect-packages + env: + CiBuildVersionSuffix: ${{ github.event_name == 'push' && env.CiRunPushSuffix || env.CiRunPullSuffix }} + run: dotnet pack --no-build --configuration ${{ matrix.configuration }} + + - name: Collect packages + uses: actions/upload-artifact@v4 + if: matrix.collect-packages && steps.pack.outcome == 'success' && always() + with: + name: Packages + if-no-files-found: error + path: artifacts/package/${{matrix.configuration}}/** + + publish-github: + runs-on: ubuntu-latest + permissions: + packages: write + needs: [build] + if: github.event_name == 'push' + steps: + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Download packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: Packages + + - name: Push to GitHub Packages + run: dotnet nuget push "Packages/*.nupkg" --skip-duplicate --no-symbols --api-key ${{secrets.GITHUB_TOKEN}} --source https://nuget.pkg.github.com/${{github.repository_owner}} + env: + # This is a workaround for https://github.com/NuGet/Home/issues/9775 + DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER: 0 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6c985e7b..0bd1fb74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ -.vs -bin -obj -Packages -*.user -*.exe -*.exe.settings -*.exe.WebView2 \ No newline at end of file +.vs/ +/artifacts/ +.bonsai/Packages/ +.bonsai/*.exe +.bonsai/*.exe.settings +.bonsai/*.exe.WebView2/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 11cc4839..eed9e0fa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "OpenEphys.Onix/OpenEphys.ProbeInterface"] - path = OpenEphys.Onix/OpenEphys.ProbeInterface +[submodule "OpenEphys.ProbeInterface"] + path = OpenEphys.ProbeInterface url = https://github.com/open-ephys/OpenEphys.ProbeInterface diff --git a/OpenEphys.Onix/Directory.Build.props b/Directory.Build.props similarity index 50% rename from OpenEphys.Onix/Directory.Build.props rename to Directory.Build.props index 57999231..64baf9c1 100644 --- a/OpenEphys.Onix/Directory.Build.props +++ b/Directory.Build.props @@ -3,23 +3,27 @@ Open Ephys Copyright © Open Ephys and Contributors 2024 - snupkg + https://open-ephys.github.io/onix1-bonsai-docs true - ..\bin\$(Configuration) - - true - true - + snupkg + true + https://github.com/open-ephys/onix-bonsai-onix1 git + README.md LICENSE + true icon.png + 0.1.0 9.0 strict + + - - + + + \ No newline at end of file diff --git a/OpenEphys.Onix/NuGet.config b/NuGet.config similarity index 100% rename from OpenEphys.Onix/NuGet.config rename to NuGet.config diff --git a/OpenEphys.Onix/OpenEphys.Onix/AnalogInput.cs b/OpenEphys.Onix/OpenEphys.Onix/AnalogInput.cs deleted file mode 100644 index dc037fbb..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/AnalogInput.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Runtime.InteropServices; -using Bonsai; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class AnalogInput : Source - { - [TypeConverter(typeof(AnalogIO.NameConverter))] - public string DeviceName { get; set; } - - public int BufferSize { get; set; } = 100; - - public AnalogIODataType DataType { get; set; } = AnalogIODataType.S16; - - static Mat CreateVoltageScale(int bufferSize, float[] voltsPerDivision) - { - using var scaleHeader = Mat.CreateMatHeader( - voltsPerDivision, - rows: voltsPerDivision.Length, - cols: 1, - depth: Depth.F32, - channels: 1); - var voltageScale = new Mat(scaleHeader.Rows, bufferSize, scaleHeader.Depth, scaleHeader.Channels); - CV.Repeat(scaleHeader, voltageScale); - return voltageScale; - } - - public unsafe override IObservable Generate() - { - var bufferSize = BufferSize; - var dataType = DataType; - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - Observable.Create(observer => - { - var device = deviceInfo.GetDeviceContext(typeof(AnalogIO)); - var ioDeviceInfo = (AnalogIODeviceInfo)deviceInfo; - - var sampleIndex = 0; - var voltageScale = dataType == AnalogIODataType.Volts - ? CreateVoltageScale(bufferSize, ioDeviceInfo.VoltsPerDivision) - : null; - var transposeBuffer = voltageScale != null - ? new Mat(AnalogIO.ChannelCount, bufferSize, Depth.S16, 1) - : null; - var analogDataBuffer = new short[AnalogIO.ChannelCount * bufferSize]; - var hubSyncCounterBuffer = new ulong[bufferSize]; - var clockBuffer = new ulong[bufferSize]; - - var frameObserver = Observer.Create( - frame => - { - var payload = (AnalogInputPayload*)frame.Data.ToPointer(); - Marshal.Copy(new IntPtr(payload->AnalogData), analogDataBuffer, sampleIndex * AnalogIO.ChannelCount, AnalogIO.ChannelCount); - hubSyncCounterBuffer[sampleIndex] = payload->HubSyncCounter; - clockBuffer[sampleIndex] = frame.Clock; - if (++sampleIndex >= bufferSize) - { - var analogData = BufferHelper.CopyTranspose( - analogDataBuffer, - bufferSize, - AnalogIO.ChannelCount, - Depth.S16, - voltageScale, - transposeBuffer); - observer.OnNext(new AnalogInputDataFrame(clockBuffer, hubSyncCounterBuffer, analogData)); - hubSyncCounterBuffer = new ulong[bufferSize]; - clockBuffer = new ulong[bufferSize]; - sampleIndex = 0; - } - }, - observer.OnError, - observer.OnCompleted); - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .SubscribeSafe(frameObserver); - }))); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/AnalogInputDataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/AnalogInputDataFrame.cs deleted file mode 100644 index ccadf7b5..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/AnalogInputDataFrame.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Runtime.InteropServices; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class AnalogInputDataFrame - { - public AnalogInputDataFrame(ulong[] clock, ulong[] hubSyncCounter, Mat analogData) - { - Clock = clock; - HubSyncCounter = hubSyncCounter; - AnalogData = analogData; - } - - public ulong[] Clock { get; } - - public ulong[] HubSyncCounter { get; } - - public Mat AnalogData { get; } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - unsafe struct AnalogInputPayload - { - public ulong HubSyncCounter; - public fixed short AnalogData[AnalogIO.ChannelCount]; - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/AnalogOutput.cs b/OpenEphys.Onix/OpenEphys.Onix/AnalogOutput.cs deleted file mode 100644 index 64f74e57..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/AnalogOutput.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class AnalogOutput : Sink - { - const AnalogIOVoltageRange OutputRange = AnalogIOVoltageRange.TenVolts; - - [TypeConverter(typeof(AnalogIO.NameConverter))] - public string DeviceName { get; set; } - - public AnalogIODataType DataType { get; set; } = AnalogIODataType.S16; - - public override IObservable Process(IObservable source) - { - var dataType = DataType; - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var bufferSize = 0; - var scaleBuffer = default(Mat); - var transposeBuffer = default(Mat); - var sampleScale = dataType == AnalogIODataType.Volts - ? 1 / AnalogIODeviceInfo.GetVoltsPerDivision(OutputRange) - : 1; - var device = deviceInfo.GetDeviceContext(typeof(AnalogIO)); - return source.Do(data => - { - if (dataType == AnalogIODataType.S16 && data.Depth != Depth.S16 || - dataType == AnalogIODataType.Volts && data.Depth != Depth.F32) - { - ThrowDataTypeException(data.Depth); - } - - AssertChannelCount(data.Rows); - if (bufferSize != data.Cols) - { - bufferSize = data.Cols; - transposeBuffer = bufferSize > 1 - ? new Mat(data.Cols, data.Rows, data.Depth, 1) - : null; - if (sampleScale != 1) - { - scaleBuffer = transposeBuffer != null - ? new Mat(data.Cols, data.Rows, Depth.S16, 1) - : new Mat(data.Rows, data.Cols, Depth.S16, 1); - } - else scaleBuffer = null; - } - - var outputBuffer = data; - if (transposeBuffer != null) - { - CV.Transpose(outputBuffer, transposeBuffer); - outputBuffer = transposeBuffer; - } - - if (scaleBuffer != null) - { - CV.ConvertScale(outputBuffer, scaleBuffer, sampleScale); - outputBuffer = scaleBuffer; - } - - var dataSize = outputBuffer.Step * outputBuffer.Rows; - device.Write(outputBuffer.Data, dataSize); - }); - })); - } - - public IObservable Process(IObservable source) - { - if (DataType != AnalogIODataType.S16) - ThrowDataTypeException(Depth.S16); - - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var device = deviceInfo.GetDeviceContext(typeof(AnalogIO)); - return source.Do(data => - { - AssertChannelCount(data.Length); - device.Write(data); - }); - })); - } - - public IObservable Process(IObservable source) - { - if (DataType != AnalogIODataType.Volts) - ThrowDataTypeException(Depth.F32); - - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var device = deviceInfo.GetDeviceContext(typeof(AnalogIO)); - var divisionsPerVolt = 1 / AnalogIODeviceInfo.GetVoltsPerDivision(OutputRange); - return source.Do(data => - { - AssertChannelCount(data.Length); - var samples = new short[data.Length]; - for (int i = 0; i < samples.Length; i++) - { - samples[i] = (short)(data[i] * divisionsPerVolt); - } - - device.Write(samples); - }); - })); - } - - static void AssertChannelCount(int channels) - { - if (channels != AnalogIO.ChannelCount) - { - throw new InvalidOperationException( - $"The input data must have exactly {AnalogIO.ChannelCount} channels." - ); - } - } - - static void ThrowDataTypeException(Depth depth) - { - throw new InvalidOperationException( - $"Invalid input data type '{depth}' for the specified analog IO configuration." - ); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/Bno055Data.cs b/OpenEphys.Onix/OpenEphys.Onix/Bno055Data.cs deleted file mode 100644 index e012b71c..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/Bno055Data.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class Bno055Data : Source - { - [TypeConverter(typeof(Bno055.NameConverter))] - public string DeviceName { get; set; } - - public override IObservable Generate() - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var device = deviceInfo.GetDeviceContext(typeof(Bno055)); - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .Select(frame => new Bno055DataFrame(frame)); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureAnalogIO.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureAnalogIO.cs deleted file mode 100644 index 82cc7a3d..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureAnalogIO.cs +++ /dev/null @@ -1,268 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; - -namespace OpenEphys.Onix -{ - [TypeConverter(typeof(SortedPropertyConverter))] - public class ConfigureAnalogIO : SingleDeviceFactory - { - public ConfigureAnalogIO() - : base(typeof(AnalogIO)) - { - DeviceAddress = 6; - } - - [Category(ConfigurationCategory)] - [Description("Specifies whether the analog IO device is enabled.")] - public bool Enable { get; set; } = true; - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 0.")] - public AnalogIOVoltageRange InputRange0 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 1.")] - public AnalogIOVoltageRange InputRange1 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 2.")] - public AnalogIOVoltageRange InputRange2 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 3.")] - public AnalogIOVoltageRange InputRange3 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 4.")] - public AnalogIOVoltageRange InputRange4 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 5.")] - public AnalogIOVoltageRange InputRange5 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 6.")] - public AnalogIOVoltageRange InputRange6 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 7.")] - public AnalogIOVoltageRange InputRange7 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 8.")] - public AnalogIOVoltageRange InputRange8 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 9.")] - public AnalogIOVoltageRange InputRange9 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 10.")] - public AnalogIOVoltageRange InputRange10 { get; set; } - - [Category(ConfigurationCategory)] - [Description("The input voltage range of channel 11.")] - public AnalogIOVoltageRange InputRange11 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 0.")] - public AnalogIODirection Direction0 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 1.")] - public AnalogIODirection Direction1 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 2.")] - public AnalogIODirection Direction2 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 3.")] - public AnalogIODirection Direction3 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 4.")] - public AnalogIODirection Direction4 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 5.")] - public AnalogIODirection Direction5 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 6.")] - public AnalogIODirection Direction6 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 7.")] - public AnalogIODirection Direction7 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 8.")] - public AnalogIODirection Direction8 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 9.")] - public AnalogIODirection Direction9 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 10.")] - public AnalogIODirection Direction10 { get; set; } - - [Category(AcquisitionCategory)] - [Description("The direction of channel 11.")] - public AnalogIODirection Direction11 { get; set; } - - public override IObservable Process(IObservable source) - { - var deviceName = DeviceName; - var deviceAddress = DeviceAddress; - return source.ConfigureDevice(context => - { - var device = context.GetDeviceContext(deviceAddress, AnalogIO.ID); - device.WriteRegister(AnalogIO.ENABLE, Enable ? 1u : 0u); - device.WriteRegister(AnalogIO.CH00INRANGE, (uint)InputRange0); - device.WriteRegister(AnalogIO.CH01INRANGE, (uint)InputRange1); - device.WriteRegister(AnalogIO.CH02INRANGE, (uint)InputRange2); - device.WriteRegister(AnalogIO.CH03INRANGE, (uint)InputRange3); - device.WriteRegister(AnalogIO.CH04INRANGE, (uint)InputRange4); - device.WriteRegister(AnalogIO.CH05INRANGE, (uint)InputRange5); - device.WriteRegister(AnalogIO.CH06INRANGE, (uint)InputRange6); - device.WriteRegister(AnalogIO.CH07INRANGE, (uint)InputRange7); - device.WriteRegister(AnalogIO.CH08INRANGE, (uint)InputRange8); - device.WriteRegister(AnalogIO.CH09INRANGE, (uint)InputRange9); - device.WriteRegister(AnalogIO.CH10INRANGE, (uint)InputRange10); - device.WriteRegister(AnalogIO.CH11INRANGE, (uint)InputRange11); - - // Build the whole value for CHDIR and write it once - static uint SetIO(uint io_reg, int channel, AnalogIODirection direction) => - (io_reg & ~((uint)1 << channel)) | ((uint)(direction) << channel); - - var io_reg = 0u; - io_reg = SetIO(io_reg, 0, Direction0); - io_reg = SetIO(io_reg, 1, Direction1); - io_reg = SetIO(io_reg, 2, Direction2); - io_reg = SetIO(io_reg, 3, Direction3); - io_reg = SetIO(io_reg, 4, Direction4); - io_reg = SetIO(io_reg, 5, Direction5); - io_reg = SetIO(io_reg, 6, Direction6); - io_reg = SetIO(io_reg, 7, Direction7); - io_reg = SetIO(io_reg, 8, Direction8); - io_reg = SetIO(io_reg, 9, Direction9); - io_reg = SetIO(io_reg, 10, Direction10); - io_reg = SetIO(io_reg, 11, Direction11); - device.WriteRegister(AnalogIO.CHDIR, io_reg); - - var deviceInfo = new AnalogIODeviceInfo(device, this); - return DeviceManager.RegisterDevice(deviceName, deviceInfo); - }); - } - - class SortedPropertyConverter : ExpandableObjectConverter - { - public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) - { - var properties = base.GetProperties(context, value, attributes); - var sortedOrder = properties.Cast() - .Where(p => p.PropertyType == typeof(AnalogIOVoltageRange) - || p.PropertyType == typeof(AnalogIODirection)) - .OrderBy(p => p.PropertyType.MetadataToken) - .Select(p => p.Name) - .Prepend(nameof(Enable)) - .ToArray(); - return properties.Sort(sortedOrder); - } - } - } - - static class AnalogIO - { - public const int ID = 22; - - // constants - public const int ChannelCount = 12; - public const int NumberOfDivisions = 1 << 16; - - // managed registers - public const uint ENABLE = 0; - public const uint CHDIR = 1; - public const uint CH00INRANGE = 2; - public const uint CH01INRANGE = 3; - public const uint CH02INRANGE = 4; - public const uint CH03INRANGE = 5; - public const uint CH04INRANGE = 6; - public const uint CH05INRANGE = 7; - public const uint CH06INRANGE = 8; - public const uint CH07INRANGE = 9; - public const uint CH08INRANGE = 10; - public const uint CH09INRANGE = 11; - public const uint CH10INRANGE = 12; - public const uint CH11INRANGE = 13; - - internal class NameConverter : DeviceNameConverter - { - public NameConverter() - : base(typeof(AnalogIO)) - { - } - } - } - - class AnalogIODeviceInfo : DeviceInfo - { - public AnalogIODeviceInfo(DeviceContext device, ConfigureAnalogIO deviceFactory) - : base(device, deviceFactory.DeviceType) - { - VoltsPerDivision = new[] - { - GetVoltsPerDivision(deviceFactory.InputRange0), - GetVoltsPerDivision(deviceFactory.InputRange1), - GetVoltsPerDivision(deviceFactory.InputRange2), - GetVoltsPerDivision(deviceFactory.InputRange3), - GetVoltsPerDivision(deviceFactory.InputRange4), - GetVoltsPerDivision(deviceFactory.InputRange5), - GetVoltsPerDivision(deviceFactory.InputRange6), - GetVoltsPerDivision(deviceFactory.InputRange7), - GetVoltsPerDivision(deviceFactory.InputRange8), - GetVoltsPerDivision(deviceFactory.InputRange9), - GetVoltsPerDivision(deviceFactory.InputRange10), - GetVoltsPerDivision(deviceFactory.InputRange11) - }; - } - - public static float GetVoltsPerDivision(AnalogIOVoltageRange voltageRange) - { - return voltageRange switch - { - AnalogIOVoltageRange.TenVolts => 20.0f / AnalogIO.NumberOfDivisions, - AnalogIOVoltageRange.TwoPointFiveVolts => 5.0f / AnalogIO.NumberOfDivisions, - AnalogIOVoltageRange.FiveVolts => 10.0f / AnalogIO.NumberOfDivisions, - _ => throw new ArgumentOutOfRangeException(nameof(voltageRange)), - }; - } - - public float[] VoltsPerDivision { get; } - } - - public enum AnalogIOVoltageRange - { - [Description("+/-10.0 V")] - TenVolts = 0, - [Description("+/-2.5 V")] - TwoPointFiveVolts = 1, - [Description("+/-5.0 V")] - FiveVolts, - } - - public enum AnalogIODirection - { - Input = 0, - Output = 1 - } - - public enum AnalogIODataType - { - S16, - Volts - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureBreakoutBoard.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureBreakoutBoard.cs deleted file mode 100644 index 779bdbe7..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureBreakoutBoard.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; - -namespace OpenEphys.Onix -{ - public class ConfigureBreakoutBoard : HubDeviceFactory - { - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureHeartbeat Heartbeat { get; set; } = new(); - - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureAnalogIO AnalogIO { get; set; } = new(); - - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureDigitalIO DigitalIO { get; set; } = new(); - - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureMemoryMonitor MemoryMonitor { get; set; } = new(); - - internal override IEnumerable GetDevices() - { - yield return Heartbeat; - yield return AnalogIO; - yield return DigitalIO; - yield return MemoryMonitor; - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureDigitalIO.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureDigitalIO.cs deleted file mode 100644 index eedf9797..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureDigitalIO.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.ComponentModel; - -namespace OpenEphys.Onix -{ - public class ConfigureDigitalIO : SingleDeviceFactory - { - public ConfigureDigitalIO() - : base(typeof(DigitalIO)) - { - DeviceAddress = 7; - } - - [Category(ConfigurationCategory)] - [Description("Specifies whether the digital IO device is enabled.")] - public bool Enable { get; set; } = true; - - public override IObservable Process(IObservable source) - { - var deviceName = DeviceName; - var deviceAddress = DeviceAddress; - return source.ConfigureDevice(context => - { - var device = context.GetDeviceContext(deviceAddress, DigitalIO.ID); - device.WriteRegister(DigitalIO.ENABLE, Enable ? 1u : 0); - return DeviceManager.RegisterDevice(deviceName, device, DeviceType); - }); - } - } - - static class DigitalIO - { - public const int ID = 18; - - // managed registers - public const uint ENABLE = 0x0; // Enable or disable the data output stream - - internal class NameConverter : DeviceNameConverter - { - public NameConverter() - : base(typeof(DigitalIO)) - { - } - } - } - - [Flags] - public enum DigitalPortState : ushort - { - Pin0 = 0x1, - Pin1 = 0x2, - Pin2 = 0x4, - Pin3 = 0x8, - Pin4 = 0x10, - Pin5 = 0x20, - Pin6 = 0x40, - Pin7 = 0x80, - } - - [Flags] - public enum BreakoutButtonState : ushort - { - Moon = 0x1, - Triangle = 0x2, - X = 0x4, - Check = 0x8, - Circle = 0x10, - Square = 0x20, - Reserved0 = 0x40, - Reserved1 = 0x80, - PortDOn = 0x100, - PortCOn = 0x200, - PortBOn = 0x400, - PortAOn = 0x800, - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHarpSyncInput.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureHarpSyncInput.cs deleted file mode 100644 index 4f5debf9..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHarpSyncInput.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.ComponentModel; - -namespace OpenEphys.Onix -{ - public class ConfigureHarpSyncInput : SingleDeviceFactory - { - public ConfigureHarpSyncInput() - : base(typeof(HarpSyncInput)) - { - DeviceAddress = 12; - } - - [Category(ConfigurationCategory)] - [Description("Specifies whether the Harp sync input device is enabled.")] - public bool Enable { get; set; } = true; - - [Category(ConfigurationCategory)] - [Description("Specifies the physical Harp clock input source.")] - public HarpSyncSource Source { get; set; } = HarpSyncSource.Breakout; - - public override IObservable Process(IObservable source) - { - var deviceName = DeviceName; - var deviceAddress = DeviceAddress; - return source.ConfigureDevice(context => - { - var device = context.GetDeviceContext(deviceAddress, HarpSyncInput.ID); - device.WriteRegister(HarpSyncInput.ENABLE, Enable ? 1u : 0); - device.WriteRegister(HarpSyncInput.SOURCE, (uint)Source); - return DeviceManager.RegisterDevice(deviceName, device, DeviceType); - }); - } - } - - static class HarpSyncInput - { - public const int ID = 30; - - // managed registers - public const uint ENABLE = 0x0; // Enable or disable the data stream - public const uint SOURCE = 0x1; // Select the clock input source - - internal class NameConverter : DeviceNameConverter - { - public NameConverter() - : base(typeof(HarpSyncInput)) - { - } - } - } - - public enum HarpSyncSource - { - Breakout = 0, - ClockAdapter = 1 - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstage64.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstage64.cs deleted file mode 100644 index 1132130a..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstage64.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.Threading; - -namespace OpenEphys.Onix -{ - public class ConfigureHeadstage64 : HubDeviceFactory - { - PortName port; - readonly ConfigureHeadstage64LinkController LinkController = new(); - - public ConfigureHeadstage64() - { - // TODO: The issue with this headstage is that its locking voltage is far, far lower than the voltage required for full - // functionality. Locking occurs at around 2V on the headstage (enough to turn 1.8V on). Full functionality is at 5.0 volts. - // Whats worse: the port voltage can only go down to 3.3V, which means that its very hard to find the true lowest voltage - // for a lock and then add a large offset to that. - Port = PortName.PortA; - LinkController.HubConfiguration = HubConfiguration.Standard; - } - - [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureRhd2164 Rhd2164 { get; set; } = new(); - - [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureBno055 Bno055 { get; set; } = new(); - - [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureTS4231 TS4231 { get; set; } = new() { Enable = false }; - - [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureHeadstage64ElectricalStimulator ElectricalStimulator { get; set; } = new(); - - public PortName Port - { - get { return port; } - set - { - port = value; - var offset = (uint)port << 8; - LinkController.DeviceAddress = (uint)port; - Rhd2164.DeviceAddress = offset + 0; - Bno055.DeviceAddress = offset + 1; - TS4231.DeviceAddress = offset + 2; - ElectricalStimulator.DeviceAddress = offset + 3; - } - } - - [Description("If defined, it will override automated voltage discovery and apply the specified voltage" + - "to the headstage. Warning: this device requires 5.5V to 6.0V for proper operation." + - "Supplying higher voltages may result in damage to the headstage.")] - public double? PortVoltage - { - get => LinkController.PortVoltage; - set => LinkController.PortVoltage = value; - } - - internal override IEnumerable GetDevices() - { - yield return LinkController; - yield return Rhd2164; - yield return Bno055; - yield return TS4231; - yield return ElectricalStimulator; - } - - class ConfigureHeadstage64LinkController : ConfigureFmcLinkController - { - protected override bool ConfigurePortVoltage(DeviceContext device) - { - // TODO: It takes a huge amount of time to get to 0, almost 10 seconds. - // The best we can do at the moment is drive port voltage to minimum which - // is an active process and then settle from there to zero volts. - const uint MinVoltage = 33; - const uint MaxVoltage = 60; - const uint VoltageOffset = 34; - const uint VoltageIncrement = 02; - - // Start with highest voltage and ramp it down to find lowest lock voltage - var voltage = MaxVoltage; - for (; voltage >= MinVoltage; voltage -= VoltageIncrement) - { - device.WriteRegister(FmcLinkController.PORTVOLTAGE, voltage); - Thread.Sleep(200); - if (!CheckLinkState(device)) - { - if (voltage == MaxVoltage) return false; - else break; - } - } - - device.WriteRegister(FmcLinkController.PORTVOLTAGE, MinVoltage); - device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0); - Thread.Sleep(1000); - device.WriteRegister(FmcLinkController.PORTVOLTAGE, voltage + VoltageOffset); - Thread.Sleep(200); - return CheckLinkState(device); - } - } - } - - public enum PortName - { - PortA = 1, - PortB = 2 - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeartbeat.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeartbeat.cs deleted file mode 100644 index c0c2c9f5..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeartbeat.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.ComponentModel; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class ConfigureHeartbeat : SingleDeviceFactory - { - readonly BehaviorSubject beatsPerSecond = new(10); - - public ConfigureHeartbeat() - : base(typeof(Heartbeat)) - { - } - - [Category(ConfigurationCategory)] - [Description("Specifies whether the heartbeat device is enabled.")] - public bool Enable { get; set; } = true; - - [Range(1, 10e6)] - [Category(ConfigurationCategory)] - [Description("Rate at which beats are produced.")] - public uint BeatsPerSecond - { - get => beatsPerSecond.Value; - set => beatsPerSecond.OnNext(value); - } - - public override IObservable Process(IObservable source) - { - var deviceName = DeviceName; - var deviceAddress = DeviceAddress; - return source.ConfigureDevice(context => - { - var device = context.GetDeviceContext(deviceAddress, Heartbeat.ID); - device.WriteRegister(Heartbeat.ENABLE, 1); - var subscription = beatsPerSecond.Subscribe(newValue => - { - var clkHz = device.ReadRegister(Heartbeat.CLK_HZ); - device.WriteRegister(Heartbeat.CLK_DIV, clkHz / newValue); - }); - - return new CompositeDisposable( - DeviceManager.RegisterDevice(deviceName, device, DeviceType), - subscription - ); - }); - } - } - - static class Heartbeat - { - public const int ID = 12; - - public const uint ENABLE = 0; // Enable the heartbeat - public const uint CLK_DIV = 1; // Heartbeat clock divider ratio. Default results in 10 Hz heartbeat. Values less than CLK_HZ / 10e6 Hz will result in 1kHz. - public const uint CLK_HZ = 2; // The frequency parameter, CLK_HZ, used in the calculation of CLK_DIV - - internal class NameConverter : DeviceNameConverter - { - public NameConverter() - : base(typeof(Heartbeat)) - { - } - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureMemoryMonitor.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureMemoryMonitor.cs deleted file mode 100644 index 633407c2..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureMemoryMonitor.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.ComponentModel; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class ConfigureMemoryMonitor : SingleDeviceFactory - { - - public ConfigureMemoryMonitor() - : base(typeof(MemoryMonitor)) - { - DeviceAddress = 10; - } - - [Category(ConfigurationCategory)] - [Description("Specifies whether the monitor device is enabled.")] - public bool Enable { get; set; } = false; - - [Range(1, 1000)] - [Category(ConfigurationCategory)] - [Description("Frequency at which memory usage is recorded (Hz).")] - public uint SampleFrequency { get; set; } = 10; - - public override IObservable Process(IObservable source) - { - var deviceName = DeviceName; - var deviceAddress = DeviceAddress; - return source.ConfigureDevice(context => - { - var device = context.GetDeviceContext(deviceAddress, MemoryMonitor.ID); - device.WriteRegister(MemoryMonitor.ENABLE, 1); - device.WriteRegister(MemoryMonitor.CLK_DIV, device.ReadRegister(MemoryMonitor.CLK_HZ) / SampleFrequency); - - return DeviceManager.RegisterDevice(deviceName, device, DeviceType); - }); - } - } - - static class MemoryMonitor - { - public const int ID = 28; - - public const uint ENABLE = 0; // Enable the monitor - public const uint CLK_DIV = 1; // Sample clock divider ratio. Values less than CLK_HZ / 10e6 Hz will result in 1kHz. - public const uint CLK_HZ = 2; // The frequency parameter, CLK_HZ, used in the calculation of CLK_DIV - public const uint TOTAL_MEM = 3; // Total available memory in 32-bit words - - internal class NameConverter : DeviceNameConverter - { - public NameConverter() - : base(typeof(MemoryMonitor)) - { - } - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBetaHeadstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBetaHeadstage.cs deleted file mode 100644 index 6356a6a8..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBetaHeadstage.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; - -namespace OpenEphys.Onix -{ - public class ConfigureNeuropixelsV2eBetaHeadstage : HubDeviceFactory - { - PortName port; - readonly ConfigureNeuropixelsV2eLinkController LinkController = new(); - - public ConfigureNeuropixelsV2eBetaHeadstage() - { - Port = PortName.PortA; - LinkController.HubConfiguration = HubConfiguration.Passthrough; - } - - [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureNeuropixelsV2eBeta NeuropixelsV2Beta { get; set; } = new(); - - [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureNeuropixelsV2eBno055 Bno055 { get; set; } = new(); - - public PortName Port - { - get { return port; } - set - { - port = value; - var offset = (uint)port << 8; - LinkController.DeviceAddress = (uint)port; - NeuropixelsV2Beta.DeviceAddress = offset + 0; - Bno055.DeviceAddress = offset + 1; - } - } - - [Description("If defined, overrides automated voltage discovery and applies " + - "the specified voltage to the headstage. Warning: this device requires 3.0V to 5.0V " + - "for proper operation. Higher voltages can damage the headstage.")] - public double? PortVoltage - { - get => LinkController.PortVoltage; - set => LinkController.PortVoltage = value; - } - - internal override IEnumerable GetDevices() - { - yield return LinkController; - yield return NeuropixelsV2Beta; - yield return Bno055; - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs deleted file mode 100644 index e3283e1d..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; - -namespace OpenEphys.Onix -{ - [Editor("OpenEphys.Onix.Design.NeuropixelsV2eHeadstageEditor, OpenEphys.Onix.Design", typeof(ComponentEditor))] - public class ConfigureNeuropixelsV2eHeadstage : HubDeviceFactory - { - PortName port; - readonly ConfigureNeuropixelsV2eLinkController LinkController = new(); - - public ConfigureNeuropixelsV2eHeadstage() - { - Port = PortName.PortA; - LinkController.HubConfiguration = HubConfiguration.Passthrough; - } - - [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureNeuropixelsV2e NeuropixelsV2e { get; set; } = new(); - - [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureNeuropixelsV2eBno055 Bno055 { get; set; } = new(); - - public PortName Port - { - get { return port; } - set - { - port = value; - var offset = (uint)port << 8; - LinkController.DeviceAddress = (uint)port; - NeuropixelsV2e.DeviceAddress = offset + 0; - Bno055.DeviceAddress = offset + 1; - } - } - - [Description("If defined, overrides automated voltage discovery and applies " + - "the specified voltage to the headstage. Warning: this device requires 3.0V to 5.5V " + - "for proper operation. Higher voltages can damage the headstage.")] - public double? PortVoltage - { - get => LinkController.PortVoltage; - set => LinkController.PortVoltage = value; - } - - internal override IEnumerable GetDevices() - { - yield return LinkController; - yield return NeuropixelsV2e; - yield return Bno055; - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureTS4231.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureTS4231.cs deleted file mode 100644 index 1c954e71..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureTS4231.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.ComponentModel; - -namespace OpenEphys.Onix -{ - public class ConfigureTS4231 : SingleDeviceFactory - { - public ConfigureTS4231() - : base(typeof(TS4231)) - { - } - - [Category(ConfigurationCategory)] - [Description("Specifies whether the TS4231 device is enabled.")] - public bool Enable { get; set; } = true; - - public override IObservable Process(IObservable source) - { - var deviceName = DeviceName; - var deviceAddress = DeviceAddress; - return source.ConfigureDevice(context => - { - var device = context.GetDeviceContext(deviceAddress, TS4231.ID); - device.WriteRegister(TS4231.ENABLE, Enable ? 1u : 0); - return DeviceManager.RegisterDevice(deviceName, device, DeviceType); - }); - } - } - - static class TS4231 - { - public const int ID = 25; - - // managed registers - public const uint ENABLE = 0x0; // Enable or disable the data output stream - - internal class NameConverter : DeviceNameConverter - { - public NameConverter() - : base(typeof(TS4231)) - { - } - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureTest0.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureTest0.cs deleted file mode 100644 index b1138b61..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureTest0.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.ComponentModel; -using System.Reactive.Subjects; -using System.Xml.Serialization; - -namespace OpenEphys.Onix -{ - public class ConfigureTest0 : SingleDeviceFactory - { - readonly BehaviorSubject message = new(0); - - public ConfigureTest0() - : base(typeof(Test0)) - { - } - - [Category(ConfigurationCategory)] - [Description("Specifies whether the Test0 device is enabled.")] - public bool Enable { get; set; } = true; - - [Category(AcquisitionCategory)] - [Description("Specifies the first 16-bit word that appears in the device to host frame.")] - public short Message - { - get => message.Value; - set => message.OnNext(value); - } - - [XmlIgnore] - [Category(ConfigurationCategory)] - [Description("Indicates the number of 16-bit numbers, 0 to PayloadWords - 1, that follow Message in each frame.")] - public uint DummyCount { get; private set; } - - [XmlIgnore] - [Category(ConfigurationCategory)] - [Description("Indicates the rate at which frames are produced. 0 indicates that the frame rate is unspecified (variable or upstream controlled).")] - public uint FrameRateHz { get; private set; } - - public override IObservable Process(IObservable source) - { - var deviceName = DeviceName; - var deviceAddress = DeviceAddress; - return source.ConfigureDevice(context => - { - var device = context.GetDeviceContext(deviceAddress, Test0.ID); - device.WriteRegister(Test0.ENABLE, Enable ? 1u : 0); - FrameRateHz = device.ReadRegister(Test0.FRAMERATE); - DummyCount = device.ReadRegister(Test0.NUMTESTWORDS); - return DeviceManager.RegisterDevice(deviceName, device, DeviceType); - }); - } - } - - static class Test0 - { - public const int ID = 10; - - public const uint ENABLE = 0x0; - public const uint MESSAGE = 0x1; - public const uint NUMTESTWORDS = 0x2; - public const uint FRAMERATE = 0x3; - - internal class NameConverter : DeviceNameConverter - { - public NameConverter() - : base(typeof(Test0)) - { - } - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ContextHelper.cs b/OpenEphys.Onix/OpenEphys.Onix/ContextHelper.cs deleted file mode 100644 index 87a4b721..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ContextHelper.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using oni; - -namespace OpenEphys.Onix -{ - static class ContextHelper - { - public static DeviceContext GetDeviceContext(this ContextTask context, uint address, int id) - { - if (!context.DeviceTable.TryGetValue(address, out Device device)) - { - throw new InvalidOperationException($"The specified device '{id}:{address}' is not present in the device table."); - } - - if (device.ID != id) - { - throw new InvalidOperationException($"The selected device is not a {id} device."); - } - - return new DeviceContext(context, device); - } - - public static DeviceContext GetDeviceContext(this DeviceInfo deviceInfo, Type expectedType) - { - deviceInfo.AssertType(expectedType); - if (!deviceInfo.Context.DeviceTable.TryGetValue(deviceInfo.DeviceAddress, out Device device)) - { - throw new InvalidOperationException( - $"The specified device '{expectedType}:{deviceInfo.DeviceAddress}' is not present in the device table." - ); - } - - return new DeviceContext(deviceInfo.Context, device); - } - - public static DeviceContext GetPassthroughDeviceContext(this ContextTask context, uint address, int id) - { - var passthroughDeviceAddress = context.GetPassthroughDeviceAddress(address); - return GetDeviceContext(context, passthroughDeviceAddress, id); - } - - public static DeviceContext GetPassthroughDeviceContext(this DeviceContext device, int id) - { - return GetPassthroughDeviceContext(device.Context, device.Address, id); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs b/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs deleted file mode 100644 index 883d36a5..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs +++ /dev/null @@ -1,430 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Subjects; -using System.Threading; -using System.Threading.Tasks; - -namespace OpenEphys.Onix -{ - public class ContextTask : IDisposable - { - private oni.Context ctx; - - /// - /// Maximum amount of frames the reading queue will hold. If the queue fills or the read - /// thread is not performant enough to fill it faster than data is produced, frame reading - /// will throttle, filling host memory instead of userspace memory. - /// - private const int MaxQueuedFrames = 2_000_000; - - /// - /// Timeout in ms for queue reads. This should not be critical as the read operation will - /// cancel if the task is stopped - /// - private const int QueueTimeoutMilliseconds = 200; - - // NB: Decouple OnNext() form hadware reads - private Task readFrames; - private Task distributeFrames; - private BlockingCollection FrameQueue; - private CancellationTokenSource CollectFramesTokenSource; - private CancellationToken CollectFramesToken; - private IDisposable ContextConfiguration; - event Func configureHost; - event Func configureLink; - event Func configureDevice; - - // NOTE: There was a GC memory leak around here - internal Subject FrameReceived = new(); - - public static readonly string DefaultDriver = "riffa"; - public static readonly int DefaultIndex = 0; - - // TODO: These work for RIFFA implementation, but potentially not others!! - private readonly object readLock = new(); - private readonly object writeLock = new(); - private readonly object regLock = new(); - private readonly object disposeLock = new(); - private bool running = false; - - private readonly string contextDriver = DefaultDriver; - private readonly int contextIndex = DefaultIndex; - - public ContextTask(string driver, int index) - { - contextDriver = driver; - contextIndex = index; - Initialize(); - } - - private void Initialize() - { - ctx = new oni.Context(contextDriver, contextIndex); - SystemClockHz = ctx.SystemClockHz; - AcquisitionClockHz = ctx.AcquisitionClockHz; - MaxReadFrameSize = ctx.MaxReadFrameSize; - MaxWriteFrameSize = ctx.MaxWriteFrameSize; - DeviceTable = ctx.DeviceTable; - } - - public void Reset() - { - lock (disposeLock) - lock (regLock) - { - Stop(); - lock (readLock) - lock (writeLock) - { - ctx?.Dispose(); - Initialize(); - } - } - } - - public uint SystemClockHz { get; private set; } - public uint AcquisitionClockHz { get; private set; } - public uint MaxReadFrameSize { get; private set; } - public uint MaxWriteFrameSize { get; private set; } - public Dictionary DeviceTable { get; private set; } - - void AssertConfigurationContext() - { - if (running) - { - throw new InvalidOperationException("Configuration cannot be changed while acquisition context is running."); - } - } - - // NB: This is where actions that reconfigure the hub state, or otherwise - // change the device table should be executed - internal void ConfigureHost(Func configure) - { - lock (regLock) - { - AssertConfigurationContext(); - configureHost += configure; - } - } - - // NB: This is where actions that calibrate port voltage or otherwise - // check link lock state should be executed - internal void ConfigureLink(Func configure) - { - lock (regLock) - { - AssertConfigurationContext(); - configureLink += configure; - } - } - - // NB: Actions queued using this method should assume that the device table - // is finalized and cannot be changed - internal void ConfigureDevice(Func configure) - { - lock (regLock) - { - AssertConfigurationContext(); - configureDevice += configure; - } - } - - private IDisposable ConfigureContext() - { - var hostAction = Interlocked.Exchange(ref configureHost, null); - var linkAction = Interlocked.Exchange(ref configureLink, null); - var deviceAction = Interlocked.Exchange(ref configureDevice, null); - var disposable = new StackDisposable(); - ConfigureResources(disposable, hostAction); - ConfigureResources(disposable, linkAction); - ConfigureResources(disposable, deviceAction); - return disposable; - } - - void ConfigureResources(StackDisposable disposable, Func action) - { - if (action != null) - { - var invocationList = action.GetInvocationList(); - try - { - foreach (var selector in invocationList.Cast>()) - { - disposable.Push(selector(this)); - } - } - catch - { - disposable.Dispose(); - throw; - } - finally { Reset(); } - } - } - - internal void Start(int blockReadSize, int blockWriteSize) - { - lock (regLock) - { - if (running) return; - - // NB: Configure context before starting acquisition - ContextConfiguration = ConfigureContext(); - ctx.BlockReadSize = blockReadSize; - ctx.BlockWriteSize = blockWriteSize; - - // NB: Stuff related to sync mode is 100% ONIX, not ONI, so long term another place - // to do this separation might be needed - int addr = ctx.HardwareAddress; - int mode = (addr & 0x00FF0000) >> 16; - if (mode == 0) // Standalone mode - { - ctx.Start(true); - } - else // If synchronized mode, reset counter independently - { - ctx.ResetFrameClock(); - ctx.Start(false); - } - - CollectFramesTokenSource = new CancellationTokenSource(); - CollectFramesToken = CollectFramesTokenSource.Token; - - FrameQueue = new BlockingCollection(MaxQueuedFrames); - - readFrames = Task.Factory.StartNew(() => - { - try - { - while (!CollectFramesToken.IsCancellationRequested) - { - // NB: This is a blocking call and there is no safe way to terminate it - // other than ending the process. For this reason, it is the job of the - // hardware to provide enough data (e.g. through a HeartbeatDevice") for - // this call to return. - oni.Frame frame = ReadFrame(); - FrameQueue.Add(frame, CollectFramesToken); - - } - } - catch (OperationCanceledException) - { -#if DEBUG - // NB: If FrameQueue.Add has not been called, frame has ref count 0 when it exits - // while loop context and will be disposed. - Console.WriteLine("Frame collection task has been cancelled by " + this.GetType()); -#endif - }; - }, - CollectFramesToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Default); - - distributeFrames = Task.Factory.StartNew(() => - { - try - { - while (!CollectFramesToken.IsCancellationRequested) - { - if (FrameQueue.TryTake(out oni.Frame frame, QueueTimeoutMilliseconds, CollectFramesToken)) - { - FrameReceived.OnNext(frame); - frame.Dispose(); - } - } - } - catch (OperationCanceledException) - { -#if DEBUG - // NB: If the thread stops no frame has been collected - Console.WriteLine("Frame distribution task has been cancelled by " + this.GetType()); -#endif - } - }, - CollectFramesToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Default); - - running = true; - } - } - - internal void Stop() - { - lock (regLock) - { - if (!running) return; - if ((distributeFrames != null || readFrames != null) && !distributeFrames.IsCanceled) - { - CollectFramesTokenSource.Cancel(); - Task.WaitAll(new Task[] { distributeFrames, readFrames }); - } - CollectFramesTokenSource?.Dispose(); - CollectFramesTokenSource = null; - - // Clear queue and free memory - while (FrameQueue?.Count > 0) - { - var frame = FrameQueue.Take(); - frame.Dispose(); - } - FrameQueue?.Dispose(); - FrameQueue = null; - ctx.Stop(); - running = false; - - ContextConfiguration?.Dispose(); - } - } - - #region oni.Context delegates - internal Action SetCustomOption => ctx.SetCustomOption; - internal Func GetCustomOption => ctx.GetCustomOption; - internal Action ResetFrameClock => ctx.ResetFrameClock; - - internal bool Running - { - get - { - return ctx.Running; - } - } - - public int HardwareAddress - { - get - { - return ctx.HardwareAddress; - } - set - { - ctx.HardwareAddress = value; - } - } - - public int BlockReadSize - { - get - { - return ctx.BlockReadSize; - } - } - - public int BlockWriteSize - { - get - { - return ctx.BlockWriteSize; - } - } - - public PassthroughState HubState - { - get - { - return (PassthroughState)ctx.GetCustomOption((int)oni.ONIXOption.PORTFUNC); - } - set - { - // PortA and PortB each have a bit in portfunc - ctx.SetCustomOption((int)oni.ONIXOption.PORTFUNC, (int)value); - } - } - - // NB: This is for actions that require synchronized register access and might - // be called asynchronously with context dispose - internal void EnsureContext(Action action) - { - lock (disposeLock) - { - if (ctx != null) - action(); - } - } - - internal uint ReadRegister(uint deviceAddress, uint registerAddress) - { - lock (regLock) - { - return ctx.ReadRegister(deviceAddress, registerAddress); - } - } - - internal void WriteRegister(uint deviceAddress, uint registerAddress, uint value) - { - lock (regLock) - { - ctx.WriteRegister(deviceAddress, registerAddress, value); - } - } - - public oni.Frame ReadFrame() - { - lock (readLock) - { - return ctx.ReadFrame(); - } - } - - public void Write(uint deviceAddress, T data) where T : unmanaged - { - lock (writeLock) - { - ctx.Write(deviceAddress, data); - } - } - - public void Write(uint deviceAddress, T[] data) where T : unmanaged - { - lock (writeLock) - { - ctx.Write(deviceAddress, data); - } - } - - public void Write(uint deviceAddress, IntPtr data, int dataSize) - { - lock (writeLock) - { - ctx.Write(deviceAddress, data, dataSize); - } - } - - public oni.Hub GetHub(uint deviceAddress) => ctx.GetHub(deviceAddress); - - public virtual uint GetPassthroughDeviceAddress(uint deviceAddress) - { - var hubAddress = (deviceAddress & 0xFF00u) >> 8; - if (hubAddress == 0) - { - throw new ArgumentException( - "Device addresses on hub zero cannot be used to create passthrough devices.", - nameof(deviceAddress)); - } - - return hubAddress + 7; - } - - #endregion - - public void Dispose() - { - lock (disposeLock) - lock (regLock) - { - Stop(); - lock (readLock) - lock (writeLock) - { - ctx?.Dispose(); - ctx = null; - } - } - - GC.SuppressFinalize(this); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/CreateContext.cs b/OpenEphys.Onix/OpenEphys.Onix/CreateContext.cs deleted file mode 100644 index eedc7c29..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/CreateContext.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Bonsai; -using System; -using System.ComponentModel; -using System.Reactive.Linq; - -namespace OpenEphys.Onix -{ - [Description("")] - [Combinator(MethodName = nameof(Generate))] - [WorkflowElementCategory(ElementCategory.Source)] - public class CreateContext - { - public string Driver { get; set; } = "riffa"; - - public int Index { get; set; } - - public IObservable Generate() - { - return Observable.Create(observer => - { - var driver = Driver; - var index = Index; - var context = new ContextTask(driver, index); - try - { - observer.OnNext(context); - return context; - } - catch - { - context.Dispose(); - throw; - } - }); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/DeviceFactory.cs b/OpenEphys.Onix/OpenEphys.Onix/DeviceFactory.cs deleted file mode 100644 index cdd8ea09..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/DeviceFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using Bonsai; - -namespace OpenEphys.Onix -{ - public abstract class DeviceFactory : Sink - { - internal const string ConfigurationCategory = "Configuration"; - internal const string AcquisitionCategory = "Acquisition"; - - internal abstract IEnumerable GetDevices(); - } - - public abstract class SingleDeviceFactory : DeviceFactory, IDeviceConfiguration - { - internal SingleDeviceFactory(Type deviceType) - { - DeviceType = deviceType ?? throw new ArgumentNullException(nameof(deviceType)); - } - - public string DeviceName { get; set; } - - public uint DeviceAddress { get; set; } - - [Browsable(false)] - public Type DeviceType { get; } - - internal override IEnumerable GetDevices() - { - yield return this; - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/DeviceID.cs b/OpenEphys.Onix/OpenEphys.Onix/DeviceID.cs deleted file mode 100644 index 06af9e25..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/DeviceID.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace OpenEphys.Onix -{ - internal enum DeviceID - { - Null = 0, - Info = 1, - Rhd2132 = 2, - Rhd2164 = 3, - ElectricalStimulator = 4, - OpticalStimulator = 5, - TS4231 = 6, - DigitalInput32 = 7, - DigitalOutput32 = 8, - Bno055 = 9, - Test = 10, - NeuropixelsV1 = 11, - Heartbeat = 12, - AD51X2 = 13, - FmcVoltageController = 14, - AD7617 = 15, - AD576X = 16, - TestRegisterV0 = 17, - BreakoutDigitalIO = 18, - FmcClockInput = 19, - FmcClockOutput = 20, - TS4231V2Array = 21, - BreakoutAnalogIO = 22, - FmcLinkController = 23, - DS90UB9X = 24, - TS4231V1Array = 25, - Max10AdcCore = 26, - LoadTest = 27, - MemoryUsage = 28, - HarpSyncInput = 30, - Rhs2116 = 31, - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/DigitalInput.cs b/OpenEphys.Onix/OpenEphys.Onix/DigitalInput.cs deleted file mode 100644 index fb47c51c..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/DigitalInput.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class DigitalInput : Source - { - [TypeConverter(typeof(DigitalIO.NameConverter))] - public string DeviceName { get; set; } - - public unsafe override IObservable Generate() - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var device = deviceInfo.GetDeviceContext(typeof(DigitalIO)); - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .Select(frame => new DigitalInputDataFrame(frame)); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/DigitalInputDataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/DigitalInputDataFrame.cs deleted file mode 100644 index 185920e8..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/DigitalInputDataFrame.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Runtime.InteropServices; - -namespace OpenEphys.Onix -{ - public class DigitalInputDataFrame - { - public unsafe DigitalInputDataFrame(oni.Frame frame) - { - Clock = frame.Clock; - var payload = (DigitalInputPayload*)frame.Data.ToPointer(); - HubClock = payload->HubClock; - DigitalInputs = payload->DigitalInputs; - Buttons = payload->Buttons; - } - - public ulong Clock { get; } - - public ulong HubClock { get; } - - public DigitalPortState DigitalInputs { get; } - - public BreakoutButtonState Buttons { get; } - - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct DigitalInputPayload - { - public ulong HubClock; - public DigitalPortState DigitalInputs; - public BreakoutButtonState Buttons; - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/DigitalOutput.cs b/OpenEphys.Onix/OpenEphys.Onix/DigitalOutput.cs deleted file mode 100644 index 209940fb..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/DigitalOutput.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class DigitalOutput : Sink - { - [TypeConverter(typeof(DigitalIO.NameConverter))] - public string DeviceName { get; set; } - - public override IObservable Process(IObservable source) - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var device = deviceInfo.GetDeviceContext(typeof(DigitalIO)); - return source.Do(value => device.Write((uint)value)); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/HarpSyncInputData.cs b/OpenEphys.Onix/OpenEphys.Onix/HarpSyncInputData.cs deleted file mode 100644 index 3f6d981c..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/HarpSyncInputData.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class HarpSyncInputData : Source - { - [TypeConverter(typeof(HarpSyncInput.NameConverter))] - public string DeviceName { get; set; } - - public override IObservable Generate() - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var device = deviceInfo.GetDeviceContext(typeof(HarpSyncInput)); - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .Select(frame => new HarpSyncInputDataFrame(frame)); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/HarpSyncInputDataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/HarpSyncInputDataFrame.cs deleted file mode 100644 index e67d1212..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/HarpSyncInputDataFrame.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Runtime.InteropServices; - -namespace OpenEphys.Onix -{ - public class HarpSyncInputDataFrame - { - public unsafe HarpSyncInputDataFrame(oni.Frame frame) - { - Clock = frame.Clock; - var payload = (HarpSyncInputPayload*)frame.Data.ToPointer(); - HubClock = payload->HubClock; - HarpTime = payload->HarpTime; - } - - public ulong Clock { get; } - - public ulong HubClock { get; } - - public uint HarpTime { get; } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct HarpSyncInputPayload - { - public ulong HubClock; - public uint HarpTime; - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/Headstage64ElectricalStimulatorTrigger.cs b/OpenEphys.Onix/OpenEphys.Onix/Headstage64ElectricalStimulatorTrigger.cs deleted file mode 100644 index 164d9f28..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/Headstage64ElectricalStimulatorTrigger.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.ComponentModel; -using System.Drawing.Design; -using System.Linq; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class Headstage64ElectricalStimulatorTrigger: Sink - { - readonly BehaviorSubject enable = new(true); - readonly BehaviorSubject phaseOneCurrent = new(0); - readonly BehaviorSubject interPhaseCurrent = new(0); - readonly BehaviorSubject phaseTwoCurrent = new(0); - readonly BehaviorSubject phaseOneDuration = new(0); - readonly BehaviorSubject interPhaseInterval = new(0); - readonly BehaviorSubject phaseTwoDuration = new(0); - readonly BehaviorSubject interPulseInterval = new(0); - readonly BehaviorSubject burstPulseCount = new(0); - readonly BehaviorSubject interBurstInterval = new(0); - readonly BehaviorSubject trainBurstCount = new(0); - readonly BehaviorSubject trainDelay = new(0); - readonly BehaviorSubject powerEnable = new(false); - - const double DacBitDepth = 16; - const double AbsMaxMicroAmps = 2500; - - [TypeConverter(typeof(Headstage64ElectricalStimulator.NameConverter))] - public string DeviceName { get; set; } - - [Description("Specifies whether the electrical stimulation subcircuit will respect triggers.")] - public bool Enable - { - get => enable.Value; - set => enable.OnNext(value); - } - - [Description("Phase 1 pulse current (uA).")] - [Range(-AbsMaxMicroAmps, AbsMaxMicroAmps)] - [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] - [Precision(3, 1)] - public double PhaseOneCurrent - { - get => phaseOneCurrent.Value; - set => phaseOneCurrent.OnNext(value); - } - - [Description("Interphase rest current (uA).")] - [Range(-AbsMaxMicroAmps, AbsMaxMicroAmps)] - [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] - [Precision(3, 1)] - public double InterPhaseCurrent - { - get => interPhaseCurrent.Value; - set => interPhaseCurrent.OnNext(value); - } - - [Description("Phase 2 pulse current (uA).")] - [Range(-AbsMaxMicroAmps, AbsMaxMicroAmps)] - [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] - [Precision(3, 1)] - public double PhaseTwoCurrent - { - get => phaseTwoCurrent.Value; - set => phaseTwoCurrent.OnNext(value); - } - - [Description("Pulse train start delay (uSec).")] - [Range(0, uint.MaxValue)] - public uint TrainDelay - { - get => trainDelay.Value; - set => trainDelay.OnNext(value); - } - - [Description("Phase 1 pulse duration (uSec).")] - [Range(0, uint.MaxValue)] - public uint PhaseOneDuration - { - get => phaseOneDuration.Value; - set => phaseOneDuration.OnNext(value); - } - - [Description("Inter-phase interval (uSec).")] - [Range(0, uint.MaxValue)] - public uint InterPhaseInterval - { - get => interPhaseInterval.Value; - set => interPhaseInterval.OnNext(value); - } - - [Description("Phase 2 pulse duration (uSec).")] - [Range(0, uint.MaxValue)] - public uint PhaseTwoDuration - { - get => phaseTwoDuration.Value; - set => phaseTwoDuration.OnNext(value); - } - - [Description("Inter-pulse interval (uSec).")] - [Range(0, uint.MaxValue)] - public uint InterPulseInterval - { - get => interPulseInterval.Value; - set => interPulseInterval.OnNext(value); - } - - [Description("Inter-burst interval (uSec).")] - [Range(0, uint.MaxValue)] - public uint InterBurstInterval - { - get => interBurstInterval.Value; - set => interBurstInterval.OnNext(value); - } - - [Description("Number of pulses in each burst.")] - [Range(0, uint.MaxValue)] - public uint BurstPulseCount - { - get => burstPulseCount.Value; - set => burstPulseCount.OnNext(value); - } - - [Description("Number of bursts in each train.")] - [Range(0, uint.MaxValue)] - public uint TrainBurstCount - { - get => trainBurstCount.Value; - set => trainBurstCount.OnNext(value); - } - - [Description("Stimulator power on/off.")] - [Range(0, uint.MaxValue)] - public bool PowerEnable - { - get => powerEnable.Value; - set => powerEnable.OnNext(value); - } - - public override IObservable Process(IObservable source) - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - Observable.Create(observer => - { - var device = deviceInfo.GetDeviceContext(typeof(Headstage64ElectricalStimulator)); - var triggerObserver = Observer.Create( - value => device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, value ? 1u : 0u), - observer.OnError, - observer.OnCompleted); - - static uint uAToCode(double currentuA) - { - var k = 1 / (2 * AbsMaxMicroAmps / (Math.Pow(2, DacBitDepth) - 1)); // static - return (uint)(k * (currentuA + AbsMaxMicroAmps)); - } - - return new CompositeDisposable( - enable.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.ENABLE, value ? 1u : 0u)), - phaseOneCurrent.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT1, uAToCode(value))), - interPhaseCurrent.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.RESTCURR, uAToCode(value))), - phaseTwoCurrent.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT2, uAToCode(value))), - trainDelay.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.TRAINDELAY, value)), - phaseOneDuration.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR1, value)), - interPhaseInterval.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.INTERPHASEINTERVAL, value)), - phaseTwoDuration.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR2, value)), - interPulseInterval.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.INTERPULSEINTERVAL, value)), - interBurstInterval.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.INTERBURSTINTERVAL, value)), - burstPulseCount.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.BURSTCOUNT, value)), - trainBurstCount.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.TRAINCOUNT, value)), - powerEnable.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.POWERON, value ? 1u : 0u)), - source.SubscribeSafe(triggerObserver) - ); - }))); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/HeartbeatCounter.cs b/OpenEphys.Onix/OpenEphys.Onix/HeartbeatCounter.cs deleted file mode 100644 index 88b6d8f9..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/HeartbeatCounter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class HeartbeatCounter : Source> - { - [TypeConverter(typeof(Heartbeat.NameConverter))] - public string DeviceName { get; set; } - - public override IObservable> Generate() - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var device = deviceInfo.GetDeviceContext(typeof(Heartbeat)); - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .Select(frame => new ManagedFrame(frame)); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/HubConfiguration.cs b/OpenEphys.Onix/OpenEphys.Onix/HubConfiguration.cs deleted file mode 100644 index 3d2f4e40..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/HubConfiguration.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace OpenEphys.Onix -{ - public enum HubConfiguration - { - Standard, - Passthrough - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/HubDeviceFactory.cs b/OpenEphys.Onix/OpenEphys.Onix/HubDeviceFactory.cs deleted file mode 100644 index 0682d008..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/HubDeviceFactory.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using Bonsai; - -namespace OpenEphys.Onix -{ - public abstract class HubDeviceFactory : DeviceFactory, INamedElement - { - const string BaseTypePrefix = "Configure"; - string _name; - - protected HubDeviceFactory() - { - var baseName = GetType().Name; - var prefixIndex = baseName.IndexOf(BaseTypePrefix); - Name = prefixIndex >= 0 ? baseName.Substring(prefixIndex + BaseTypePrefix.Length) : baseName; - } - - public string Name - { - get { return _name; } - set - { - _name = value; - UpdateDeviceNames(); - } - } - - protected string GetFullDeviceName(string deviceName) - { - return !string.IsNullOrEmpty(_name) ? $"{_name}/{deviceName}" : string.Empty; - } - - internal virtual void UpdateDeviceNames() - { - foreach (var device in GetDevices()) - { - device.DeviceName = GetFullDeviceName(device.DeviceType.Name); - } - } - - public override IObservable Process(IObservable source) - { - if (string.IsNullOrEmpty(_name)) - { - throw new InvalidOperationException("A valid hub device name must be specified."); - } - - var output = source; - foreach (var device in GetDevices()) - { - output = device.Process(output); - } - - return output; - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ManagedFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/ManagedFrame.cs deleted file mode 100644 index 521d636a..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/ManagedFrame.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace OpenEphys.Onix -{ - /// - /// Managed copy of with strongly-typed data array. - /// - /// The data type of the Sample array - public class ManagedFrame where T : unmanaged - { - public ManagedFrame(oni.Frame frame) - { - Sample = frame.GetData(); - FrameClock = frame.Clock; - DeviceAddress = frame.DeviceAddress; - } - - public readonly T[] Sample; - - public ulong FrameClock { get; private set; } - - public uint DeviceAddress { get; private set; } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/MemoryUsage.cs b/OpenEphys.Onix/OpenEphys.Onix/MemoryUsage.cs deleted file mode 100644 index 045eb8c5..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/MemoryUsage.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class MemoryUsage : Source - { - [TypeConverter(typeof(MemoryMonitor.NameConverter))] - public string DeviceName { get; set; } - - public override IObservable Generate() - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var device = deviceInfo.GetDeviceContext(typeof(MemoryMonitor)); - var totalMemory = device.ReadRegister(MemoryMonitor.TOTAL_MEM); - - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .Select(frame => new MemoryUsageDataFrame(frame, totalMemory)); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/MemoryUsageDataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/MemoryUsageDataFrame.cs deleted file mode 100644 index b2cb8c0d..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/MemoryUsageDataFrame.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Runtime.InteropServices; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class MemoryUsageDataFrame - { - public unsafe MemoryUsageDataFrame(oni.Frame frame, uint totalMemory) - { - var payload = (MemoryUsagePayload*)frame.Data.ToPointer(); - - FrameClock = frame.Clock; - DeviceAddress = frame.DeviceAddress; - HubClock = payload->HubClock; - PercentUsed = 100.0 * payload->Usage / totalMemory; - BytesUsed = payload->Usage * 4; - - } - - public ulong FrameClock { get; private set; } - - public uint DeviceAddress { get; private set; } - - public ulong HubClock { get; } - - public double PercentUsed { get; } - - public uint BytesUsed { get; } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct MemoryUsagePayload - { - public ulong HubClock; - public uint Usage; - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eAdc.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eAdc.cs deleted file mode 100644 index 9bf3f4d3..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eAdc.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace OpenEphys.Onix -{ - public class NeuropixelsV1eAdc - { - public int CompP { get; set; } = 16; - public int CompN { get; set; } = 16; - public int Slope { get; set; } = 0; - public int Coarse { get; set; } = 0; - public int Fine { get; set; } = 0; - public int Cfix { get; set; } = 0; - public int Offset { get; set; } = 0; - public int Threshold { get; set; } = 512; - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eBno055Data.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eBno055Data.cs deleted file mode 100644 index fba34333..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eBno055Data.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class NeuropixelsV1eBno055Data : Source - { - [TypeConverter(typeof(NeuropixelsV1eBno055.NameConverter))] - public string DeviceName { get; set; } - - public override IObservable Generate() - { - // Max of 100 Hz, but limited by I2C bus - var source = Observable.Interval(TimeSpan.FromSeconds(0.01)); - return Generate(source); - } - - public unsafe IObservable Generate(IObservable source) - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany( - deviceInfo => Observable.Create(observer => - { - var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV1eBno055)); - var passthrough = device.GetPassthroughDeviceContext(DS90UB9x.ID); - var i2c = new I2CRegisterContext(passthrough, NeuropixelsV1eBno055.BNO055Address); - - var pollingObserver = Observer.Create( - _ => - { - Bno055DataFrame frame = default; - device.Context.EnsureContext(() => - { - var data = i2c.ReadBytes(NeuropixelsV1eBno055.DataAddress, sizeof(Bno055DataPayload)); - ulong clock = passthrough.ReadRegister(DS90UB9x.LASTI2CL); - clock += (ulong)passthrough.ReadRegister(DS90UB9x.LASTI2CH) << 32; - fixed (byte* dataPtr = data) - { - frame = new Bno055DataFrame(clock, (Bno055DataPayload*)dataPtr); - } - }); - - if (frame != null) - { - observer.OnNext(frame); - } - }, - observer.OnError, - observer.OnCompleted); - return source.SubscribeSafe(pollingObserver); - }))); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eData.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eData.cs deleted file mode 100644 index 506ca3dd..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eData.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using Bonsai; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class NeuropixelsV1eData : Source - { - [TypeConverter(typeof(NeuropixelsV1e.NameConverter))] - public string DeviceName { get; set; } - - int bufferSize = 36; - [Description("Number of super-frames (384 channels from spike band and 32 channels from " + - "LFP band) to buffer before propogating data. Must be a mulitple of 12.")] - public int BufferSize - { - get => bufferSize; - set => bufferSize = (int)(Math.Ceiling((double)value / NeuropixelsV1e.FramesPerRoundRobin) * NeuropixelsV1e.FramesPerRoundRobin); - } - - public unsafe override IObservable Generate() - { - var spikeBufferSize = BufferSize; - var lfpBufferSize = spikeBufferSize / NeuropixelsV1e.FramesPerRoundRobin; - - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var info = (NeuropixelsV1eDeviceInfo)deviceInfo; - var device = info.GetDeviceContext(typeof(NeuropixelsV1e)); - var passthrough = device.GetPassthroughDeviceContext(DS90UB9x.ID); - var probeData = device.Context.FrameReceived.Where(frame => frame.DeviceAddress == passthrough.Address); - - return Observable.Create(observer => - { - var sampleIndex = 0; - var spikeBuffer = new ushort[NeuropixelsV1e.ChannelCount, spikeBufferSize]; - var lfpBuffer = new ushort[NeuropixelsV1e.ChannelCount, lfpBufferSize]; - var frameCountBuffer = new int[spikeBufferSize * NeuropixelsV1e.FramesPerSuperFrame]; - var hubClockBuffer = new ulong[spikeBufferSize]; - var clockBuffer = new ulong[spikeBufferSize]; - - var frameObserver = Observer.Create( - frame => - { - var payload = (NeuropixelsV1ePayload*)frame.Data.ToPointer(); - NeuropixelsV1eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, frameCountBuffer, spikeBuffer, lfpBuffer, sampleIndex, info.ApGainCorrection, info.LfpGainCorrection, info.AdcThresholds, info.AdcOffsets); - hubClockBuffer[sampleIndex] = payload->HubClock; - clockBuffer[sampleIndex] = frame.Clock; - if (++sampleIndex >= spikeBufferSize) - { - var spikeData = Mat.FromArray(spikeBuffer); - var lfpData = Mat.FromArray(lfpBuffer); - observer.OnNext(new NeuropixelsV1eDataFrame(clockBuffer, hubClockBuffer, frameCountBuffer, spikeData, lfpData)); - frameCountBuffer = new int[spikeBufferSize * NeuropixelsV1e.FramesPerSuperFrame]; - hubClockBuffer = new ulong[spikeBufferSize]; - clockBuffer = new ulong[spikeBufferSize]; - sampleIndex = 0; - } - }, - observer.OnError, - observer.OnCompleted); - return probeData.SubscribeSafe(frameObserver); - }); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankProbeConfiguration.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankProbeConfiguration.cs deleted file mode 100644 index dd9409e7..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankProbeConfiguration.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using Bonsai; -using Newtonsoft.Json; -using System.Text; -using System.Xml.Serialization; - -namespace OpenEphys.Onix -{ - public enum NeuropixelsV2QuadShankReference : uint - { - External, - Tip1, - Tip2, - Tip3, - Tip4 - } - public enum NeuropixelsV2QuadShankBank - { - A, - B, - C, - D, - } - - public class NeuropixelsV2QuadShankProbeConfiguration - { - //public static readonly IReadOnlyList ProbeModel = CreateProbeModel(); - - public NeuropixelsV2QuadShankReference Reference { get; set; } = NeuropixelsV2QuadShankReference.External; - - [XmlIgnore] - public List ChannelMap { get; } - - - [XmlIgnore] - [Category("Configuration")] - [Description("Defines the shape of the probe, and which contacts are currently selected for streaming")] - public NeuropixelsV2eProbeGroup ChannelConfiguration { get; private set; } = new(); - - [Browsable(false)] - [Externalizable(false)] - [XmlElement(nameof(ChannelConfiguration))] - public string ChannelConfigurationString - { - get - { - var jsonString = JsonConvert.SerializeObject(ChannelConfiguration); - return Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString)); - } - set - { - var jsonString = Encoding.UTF8.GetString(Convert.FromBase64String(value)); - ChannelConfiguration = JsonConvert.DeserializeObject(jsonString); - } - } - - public NeuropixelsV2QuadShankProbeConfiguration() - { - //ChannelMap = new List(NeuropixelsV2.ChannelCount); - //for (int i = 0; i < NeuropixelsV2.ChannelCount; i++) - //{ - // ChannelMap.Add(ProbeModel.FirstOrDefault(e => e.Channel == i)); - //} - } - - //private static List CreateProbeModel() - //{ - // var electrodes = new List(NeuropixelsV2.ElectrodePerShank * 4); - // for (int i = 0; i < NeuropixelsV2.ElectrodePerShank * 4; i++) - // { - // electrodes.Add(new NeuropixelsV2QuadShankElectrode(i)); - // } - // return electrodes; - //} - - //public void SelectElectrodes(List electrodes) - //{ - // foreach (var e in electrodes) - // { - // ChannelMap[e.Channel] = e; - // } - //} - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaData.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaData.cs deleted file mode 100644 index 9be5f2e2..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaData.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using Bonsai; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class NeuropixelsV2eBetaData : Source - { - [TypeConverter(typeof(NeuropixelsV2eBeta.NameConverter))] - public string DeviceName { get; set; } - - public int BufferSize { get; set; } = 30; - - public NeuropixelsV2Probe ProbeIndex { get; set; } - - public unsafe override IObservable Generate() - { - var bufferSize = BufferSize; - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var info = (NeuropixelsV2eDeviceInfo)deviceInfo; - var device = info.GetDeviceContext(typeof(NeuropixelsV2eBeta)); - var passthrough = device.GetPassthroughDeviceContext(DS90UB9x.ID); - var probeData = device.Context.FrameReceived.Where(frame => - frame.DeviceAddress == passthrough.Address && - NeuropixelsV2eBetaDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); - - var gainCorrection = ProbeIndex switch - { - NeuropixelsV2Probe.ProbeA => (ushort)info.GainCorrectionA, - NeuropixelsV2Probe.ProbeB => (ushort)info.GainCorrectionB, - _ => throw new ArgumentOutOfRangeException(nameof(ProbeIndex), $"Unexpected ProbeIndex value: {ProbeIndex}"), - }; - - return Observable.Create(observer => - { - var sampleIndex = 0; - var amplifierBuffer = new ushort[NeuropixelsV2.ChannelCount, bufferSize]; - var frameCounter = new int[NeuropixelsV2eBeta.FramesPerSuperFrame * bufferSize]; - var hubClockBuffer = new ulong[bufferSize]; - var clockBuffer = new ulong[bufferSize]; - - var frameObserver = Observer.Create( - frame => - { - var payload = (NeuropixelsV2BetaPayload*)frame.Data.ToPointer(); - NeuropixelsV2eBetaDataFrame.CopyAmplifierBuffer(payload->SuperFrame, amplifierBuffer, frameCounter, sampleIndex, gainCorrection); - hubClockBuffer[sampleIndex] = payload->HubClock; - clockBuffer[sampleIndex] = frame.Clock; - if (++sampleIndex >= bufferSize) - { - var amplifierData = Mat.FromArray(amplifierBuffer); - var dataFrame = new NeuropixelsV2eBetaDataFrame( - clockBuffer, - hubClockBuffer, - amplifierData, - frameCounter); - observer.OnNext(dataFrame); - frameCounter = new int[NeuropixelsV2eBeta.FramesPerSuperFrame * bufferSize]; - hubClockBuffer = new ulong[bufferSize]; - clockBuffer = new ulong[bufferSize]; - sampleIndex = 0; - } - }, - observer.OnError, - observer.OnCompleted); - return probeData.SubscribeSafe(frameObserver); - }); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBno055Data.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBno055Data.cs deleted file mode 100644 index ddf3c053..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBno055Data.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class NeuropixelsV2eBno055Data : Source - { - [TypeConverter(typeof(NeuropixelsV2eBno055.NameConverter))] - public string DeviceName { get; set; } - - public override IObservable Generate() - { - // Max of 100 Hz, but limited by I2C bus - var source = Observable.Interval(TimeSpan.FromSeconds(0.01)); - return Generate(source); - } - - public unsafe IObservable Generate(IObservable source) - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany( - deviceInfo => Observable.Create(observer => - { - var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2eBno055)); - var passthrough = device.GetPassthroughDeviceContext(DS90UB9x.ID); - var i2c = new I2CRegisterContext(passthrough, NeuropixelsV2eBno055.BNO055Address); - - var pollingObserver = Observer.Create( - _ => - { - Bno055DataFrame frame = default; - device.Context.EnsureContext(() => - { - var data = i2c.ReadBytes(NeuropixelsV2eBno055.DataAddress, sizeof(Bno055DataPayload)); - ulong clock = passthrough.ReadRegister(DS90UB9x.LASTI2CL); - clock += (ulong)passthrough.ReadRegister(DS90UB9x.LASTI2CH) << 32; - fixed (byte* dataPtr = data) - { - frame = new Bno055DataFrame(clock, (Bno055DataPayload*)dataPtr); - } - }); - - if (frame != null) - { - observer.OnNext(frame); - } - }, - observer.OnError, - observer.OnCompleted); - return source.SubscribeSafe(pollingObserver); - }))); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eData.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eData.cs deleted file mode 100644 index a86d6eb5..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eData.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using Bonsai; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class NeuropixelsV2eData : Source - { - [TypeConverter(typeof(NeuropixelsV2e.NameConverter))] - public string DeviceName { get; set; } - - public int BufferSize { get; set; } = 30; - - public NeuropixelsV2Probe ProbeIndex { get; set; } - - public unsafe override IObservable Generate() - { - var bufferSize = BufferSize; - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var info = (NeuropixelsV2eDeviceInfo)deviceInfo; - var device = info.GetDeviceContext(typeof(NeuropixelsV2e)); - var passthrough = device.GetPassthroughDeviceContext(DS90UB9x.ID); - var probeData = device.Context.FrameReceived.Where(frame => - frame.DeviceAddress == passthrough.Address && - NeuropixelsV2eDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); - - var gainCorrection = ProbeIndex switch - { - NeuropixelsV2Probe.ProbeA => (ushort)info.GainCorrectionA, - NeuropixelsV2Probe.ProbeB => (ushort)info.GainCorrectionB, - _ => throw new ArgumentOutOfRangeException(nameof(ProbeIndex), $"Unexpected ProbeIndex value: {ProbeIndex}"), - }; - - return Observable.Create(observer => - { - var sampleIndex = 0; - var amplifierBuffer = new ushort[NeuropixelsV2e.ChannelCount, bufferSize]; - var hubClockBuffer = new ulong[bufferSize]; - var clockBuffer = new ulong[bufferSize]; - - var frameObserver = Observer.Create( - frame => - { - var payload = (NeuropixelsV2Payload*)frame.Data.ToPointer(); - NeuropixelsV2eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, amplifierBuffer, sampleIndex, gainCorrection); - hubClockBuffer[sampleIndex] = payload->HubClock; - clockBuffer[sampleIndex] = frame.Clock; - if (++sampleIndex >= bufferSize) - { - var amplifierData = Mat.FromArray(amplifierBuffer); - observer.OnNext(new NeuropixelsV2eDataFrame(clockBuffer, hubClockBuffer, amplifierData)); - hubClockBuffer = new ulong[bufferSize]; - clockBuffer = new ulong[bufferSize]; - sampleIndex = 0; - } - }, - observer.OnError, - observer.OnCompleted); - return probeData.SubscribeSafe(frameObserver); - }); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/Rhd2164Config.cs b/OpenEphys.Onix/OpenEphys.Onix/Rhd2164Config.cs deleted file mode 100644 index 37547686..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/Rhd2164Config.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System.Collections.Generic; - -namespace OpenEphys.Onix -{ - public static class Rhd2164Config - { - public static readonly IReadOnlyDictionary> AnalogLowCutoffToRegisters = - new Dictionary>() - { - { Rhd2164AnalogLowCutoff.Low500Hz, new[] { 13, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low300Hz, new[] { 15, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low250Hz, new[] { 17, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low200Hz, new[] { 18, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low150Hz, new[] { 21, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low100Hz, new[] { 25, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low75Hz, new[] { 28, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low50Hz, new[] { 34, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low30Hz, new[] { 44, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low25Hz, new[] { 48, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low20Hz, new[] { 54, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low15Hz, new[] { 62, 0, 0 } }, - { Rhd2164AnalogLowCutoff.Low10Hz, new[] { 5, 1, 0 } }, - { Rhd2164AnalogLowCutoff.Low7500mHz, new[] { 18, 1, 0 } }, - { Rhd2164AnalogLowCutoff.Low5000mHz, new[] { 40, 1, 0 } }, - { Rhd2164AnalogLowCutoff.Low3090mHz, new[] { 20, 2, 0 } }, - { Rhd2164AnalogLowCutoff.Low2500mHz, new[] { 42, 2, 0 } }, - { Rhd2164AnalogLowCutoff.Low2000mHz, new[] { 8, 3, 0 } }, - { Rhd2164AnalogLowCutoff.Low1500mHz, new[] { 9, 4, 0 } }, - { Rhd2164AnalogLowCutoff.Low1000mHz, new[] { 44, 6, 0 } }, - { Rhd2164AnalogLowCutoff.Low750mHz, new[] { 49, 9, 0 } }, - { Rhd2164AnalogLowCutoff.Low500mHz, new[] { 35, 17, 0 } }, - { Rhd2164AnalogLowCutoff.Low300mHz, new[] { 1, 40, 0 } }, - { Rhd2164AnalogLowCutoff.Low250mHz, new[] { 56, 54, 0 } }, - { Rhd2164AnalogLowCutoff.Low100mHz, new[] { 16, 60, 1 } }, - }; - - public static readonly IReadOnlyDictionary> AnalogHighCutoffToRegisters = - new Dictionary>() - { - { Rhd2164AnalogHighCutoff.High20000Hz, new[] { 8, 0, 4, 0 } }, - { Rhd2164AnalogHighCutoff.High15000Hz, new[] { 11, 0, 8, 0 } }, - { Rhd2164AnalogHighCutoff.High10000Hz, new[] { 17, 0, 16, 0 } }, - { Rhd2164AnalogHighCutoff.High7500Hz, new[] { 22, 0, 23, 0 } }, - { Rhd2164AnalogHighCutoff.High5000Hz, new[] { 33, 0, 37, 0 } }, - { Rhd2164AnalogHighCutoff.High3000Hz, new[] { 3, 1, 13, 1 } }, - { Rhd2164AnalogHighCutoff.High2500Hz, new[] { 13, 1, 25, 1 } }, - { Rhd2164AnalogHighCutoff.High2000Hz, new[] { 27, 1, 44, 1 } }, - { Rhd2164AnalogHighCutoff.High1500Hz, new[] { 1, 2, 23, 2 } }, - { Rhd2164AnalogHighCutoff.High1000Hz, new[] { 46, 2, 30, 3 } }, - { Rhd2164AnalogHighCutoff.High750Hz, new[] { 41, 3, 36, 4 } }, - { Rhd2164AnalogHighCutoff.High500Hz, new[] { 30, 5, 43, 6 } }, - { Rhd2164AnalogHighCutoff.High300Hz, new[] { 6, 9, 2, 11 } }, - { Rhd2164AnalogHighCutoff.High250Hz, new[] { 42, 10, 5, 13 } }, - { Rhd2164AnalogHighCutoff.High200Hz, new[] { 24, 13, 7, 16 } }, - { Rhd2164AnalogHighCutoff.High150Hz, new[] { 44, 17, 8, 21 } }, - { Rhd2164AnalogHighCutoff.High100Hz, new[] { 38, 26, 5, 31 } }, - }; - - - } - - public enum Rhd2164AnalogLowCutoff - { - Low500Hz, - Low300Hz, - Low250Hz, - Low200Hz, - Low150Hz, - Low100Hz, - Low75Hz, - Low50Hz, - Low30Hz, - Low25Hz, - Low20Hz, - Low15Hz, - Low10Hz, - Low7500mHz, - Low5000mHz, - Low3090mHz, - Low2500mHz, - Low2000mHz, - Low1500mHz, - Low1000mHz, - Low750mHz, - Low500mHz, - Low300mHz, - Low250mHz, - Low100mHz - } - - public enum Rhd2164AnalogHighCutoff - { - High20000Hz, - High15000Hz, - High10000Hz, - High7500Hz, - High5000Hz, - High3000Hz, - High2500Hz, - High2000Hz, - High1500Hz, - High1000Hz, - High750Hz, - High500Hz, - High300Hz, - High250Hz, - High200Hz, - High150Hz, - High100Hz - } - - public enum Rhd2164DspCutoff - { - /// - /// - /// - Differential = 0, - - /// - /// 3310 Hz - /// - Dsp3309Hz, - - /// - /// 1370 Hz - /// - Dsp1374Hz, - - /// - /// 638 Hz - /// - Dsp638Hz, - - /// - /// 308 Hz - /// - Dsp308Hz, - - /// - /// 152 Hz - /// - Dsp152Hz, - - /// - /// 75.2 Hz - /// - Dsp75Hz, - - /// - /// 37.4 Hz - /// - Dsp37Hz, - - /// - /// 18.7 Hz - /// - Dsp19Hz, - - /// - /// 9.34 Hz - /// - Dsp9336mHz, - - /// - /// 4.67 Hz - /// - Dsp4665mHz, - - /// - /// 2.33 Hz - /// - Dsp2332mHz, - - /// - /// 1.17 Hz - /// - Dsp1166mHz, - - /// - /// 0.583 Hz - /// - Dsp583mHz, - - /// - /// 0.291 Hz - /// - Dsp291mHz, - - /// - /// 0.146 Hz - /// - Dsp146mHz, - - /// - /// - /// - Off - } - - public enum Rhd2164AmplifierDataFormat - { - Unsigned, - TwosComplement - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/Rhd2164Data.cs b/OpenEphys.Onix/OpenEphys.Onix/Rhd2164Data.cs deleted file mode 100644 index f37a7f7a..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/Rhd2164Data.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Runtime.InteropServices; -using Bonsai; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class Rhd2164Data : Source - { - [TypeConverter(typeof(Rhd2164.NameConverter))] - public string DeviceName { get; set; } - - public int BufferSize { get; set; } = 30; - - public unsafe override IObservable Generate() - { - var bufferSize = BufferSize; - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - Observable.Create(observer => - { - var sampleIndex = 0; - var device = deviceInfo.GetDeviceContext(typeof(Rhd2164)); - var amplifierBuffer = new short[Rhd2164.AmplifierChannelCount * bufferSize]; - var auxBuffer = new short[Rhd2164.AuxChannelCount * bufferSize]; - var hubClockBuffer = new ulong[bufferSize]; - var clockBuffer = new ulong[bufferSize]; - - var frameObserver = Observer.Create( - frame => - { - var payload = (Rhd2164Payload*)frame.Data.ToPointer(); - Marshal.Copy(new IntPtr(payload->AmplifierData), amplifierBuffer, sampleIndex * Rhd2164.AmplifierChannelCount, Rhd2164.AmplifierChannelCount); - Marshal.Copy(new IntPtr(payload->AuxData), auxBuffer, sampleIndex * Rhd2164.AuxChannelCount, Rhd2164.AuxChannelCount); - hubClockBuffer[sampleIndex] = payload->HubClock; - clockBuffer[sampleIndex] = frame.Clock; - if (++sampleIndex >= bufferSize) - { - var amplifierData = BufferHelper.CopyTranspose(amplifierBuffer, bufferSize, Rhd2164.AmplifierChannelCount, Depth.U16); - var auxData = BufferHelper.CopyTranspose(auxBuffer, bufferSize, Rhd2164.AuxChannelCount, Depth.U16); - observer.OnNext(new Rhd2164DataFrame(clockBuffer, hubClockBuffer, amplifierData, auxData)); - hubClockBuffer = new ulong[bufferSize]; - clockBuffer = new ulong[bufferSize]; - sampleIndex = 0; - } - }, - observer.OnError, - observer.OnCompleted); - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .SubscribeSafe(frameObserver); - }))); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/Rhd2164DataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/Rhd2164DataFrame.cs deleted file mode 100644 index 72e592d5..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/Rhd2164DataFrame.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Runtime.InteropServices; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class Rhd2164DataFrame - { - public Rhd2164DataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData, Mat auxData) - { - Clock = clock; - HubClock = hubClock; - AmplifierData = amplifierData; - AuxData = auxData; - } - - public ulong[] Clock { get; } - - public ulong[] HubClock { get; } - - public Mat AmplifierData { get; } - - public Mat AuxData { get; } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - unsafe struct Rhd2164Payload - { - public ulong HubClock; - public fixed ushort AmplifierData[Rhd2164.AmplifierChannelCount]; - public fixed ushort AuxData[Rhd2164.AuxChannelCount]; - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/StartAcquisition.cs b/OpenEphys.Onix/OpenEphys.Onix/StartAcquisition.cs deleted file mode 100644 index e2a4468e..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/StartAcquisition.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class StartAcquisition : Combinator - { - public int ReadSize { get; set; } = 2048; - - public int WriteSize { get; set; } = 2048; - - public override IObservable Process(IObservable source) - { - return source.SelectMany(context => - { - return Observable.Create(observer => - { - var disposable = context.FrameReceived.SubscribeSafe(observer); - try - { - context.Start(ReadSize, WriteSize); - } - catch - { - disposable.Dispose(); - throw; - } - return Disposable.Create(() => - { - context.Stop(); - disposable.Dispose(); - }); - }); - }); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/TS4231Data.cs b/OpenEphys.Onix/OpenEphys.Onix/TS4231Data.cs deleted file mode 100644 index 6d0eb694..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/TS4231Data.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix -{ - public class TS4231Data : Source - { - [TypeConverter(typeof(TS4231.NameConverter))] - public string DeviceName { get; set; } - - public override IObservable Generate() - { - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - { - var device = deviceInfo.GetDeviceContext(typeof(TS4231)); - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .Select(frame => new TS4231DataFrame(frame)); - })); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/TS4231DataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/TS4231DataFrame.cs deleted file mode 100644 index 47db0e85..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/TS4231DataFrame.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Runtime.InteropServices; - -namespace OpenEphys.Onix -{ - public class TS4231DataFrame - { - public unsafe TS4231DataFrame(oni.Frame frame) - { - Clock = frame.Clock; - var payload = (TS4231Payload*)frame.Data.ToPointer(); - HubClock = payload->HubClock; - SensorIndex = payload->SensorIndex; - EnvelopeWidth = payload->EnvelopeWidth; - EnvelopeType = payload->EnvelopeType; - } - - public ulong Clock { get; } - - public ulong HubClock { get; } - - public int SensorIndex { get; } - - public uint EnvelopeWidth { get; } - - public TS4231Envelope EnvelopeType { get; } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct TS4231Payload - { - public ulong HubClock; - public ushort SensorIndex; - public uint EnvelopeWidth; - public TS4231Envelope EnvelopeType; - } - - public enum TS4231Envelope : short - { - Sweep, - J0, - K0, - J1, - K1, - J2, - K2 - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/Test0Data.cs b/OpenEphys.Onix/OpenEphys.Onix/Test0Data.cs deleted file mode 100644 index a552e69c..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/Test0Data.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Runtime.InteropServices; -using Bonsai; -using Bonsai.Reactive; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class Test0Data : Source - { - [TypeConverter(typeof(Test0.NameConverter))] - public string DeviceName { get; set; } - - [Category(DeviceFactory.ConfigurationCategory)] - [Range(1, 1000000)] - [Description("The number of frames making up a single data block that is propagated in the observable sequence.")] - public int BufferSize { get; set; } = 100; - - public unsafe override IObservable Generate() - { - var bufferSize = BufferSize; // TODO: Branch for bufferSize = 1? - - return Observable.Using( - () => DeviceManager.ReserveDevice(DeviceName), - disposable => disposable.Subject.SelectMany(deviceInfo => - Observable.Create(observer => - { - // Find number of dummy words in the frame - var device = deviceInfo.GetDeviceContext(typeof(Test0)); - var dummyWords = (int)device.ReadRegister(Test0.NUMTESTWORDS); - - var sampleIndex = 0; - var dummyBuffer = new short[dummyWords * bufferSize]; - var messageBuffer = new short[bufferSize]; - var hubClockBuffer = new ulong[bufferSize]; - var clockBuffer = new ulong[bufferSize]; - - var frameObserver = Observer.Create( - frame => - { - var payload = (Test0PayloadHeader*)frame.Data.ToPointer(); - Marshal.Copy(new IntPtr(payload + 1), dummyBuffer, sampleIndex * dummyWords, dummyWords); - messageBuffer[sampleIndex] = payload->Message; - hubClockBuffer[sampleIndex] = payload->HubClock; - clockBuffer[sampleIndex] = frame.Clock; - if (++sampleIndex >= bufferSize) - { - var dummy = BufferHelper.CopyTranspose(dummyBuffer, bufferSize, dummyWords, Depth.S16); - var message = BufferHelper.CopyTranspose(messageBuffer, bufferSize, 1, Depth.S16); - observer.OnNext(new Test0DataFrame(clockBuffer, hubClockBuffer, message, dummy)); - hubClockBuffer = new ulong[bufferSize]; - clockBuffer = new ulong[bufferSize]; - sampleIndex = 0; - } - }, - observer.OnError, - observer.OnCompleted); - - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .SubscribeSafe(frameObserver); - }))); - } - } -} diff --git a/OpenEphys.Onix/OpenEphys.Onix/Test0DataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/Test0DataFrame.cs deleted file mode 100644 index 30f9bb01..00000000 --- a/OpenEphys.Onix/OpenEphys.Onix/Test0DataFrame.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Runtime.InteropServices; -using OpenCV.Net; - -namespace OpenEphys.Onix -{ - public class Test0DataFrame - { - public Test0DataFrame(ulong[] clock, ulong[] hubClock, Mat message, Mat dummy) - { - Clock = clock; - HubClock = hubClock; - Message = message; - Dummy = dummy; - } - - public ulong[] Clock { get; } - - public ulong[] HubClock { get; } - - public Mat Message { get; } - - public Mat Dummy { get; } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - struct Test0PayloadHeader - { - public ulong HubClock; - public short Message; - } -} diff --git a/OpenEphys.Onix/OpenEphys.ProbeInterface b/OpenEphys.Onix/OpenEphys.ProbeInterface deleted file mode 160000 index 701d7739..00000000 --- a/OpenEphys.Onix/OpenEphys.ProbeInterface +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 701d77392dfab615b833c13ecb36bb211312284c diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.Designer.cs rename to OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs index ed30d585..8ae07de3 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs @@ -1,4 +1,4 @@ -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { partial class ChannelConfigurationDialog { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs rename to OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index d8081db3..0d968aed 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -7,7 +7,7 @@ using OpenEphys.ProbeInterface; using System.Collections.Generic; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { /// /// Simple dialog window that serves as the base class for all Channel Configuration windows. @@ -55,7 +55,7 @@ public ChannelConfigurationDialog(ProbeGroup probeGroup) } /// - /// Return the default channel layout of the current device, which fully instatiates the probe group object + /// Return the default channel layout of the current device, which fully instantiates the probe group object /// /// /// Using a class that inherits from ProbeGroup, the general usage would @@ -75,7 +75,7 @@ internal virtual void LoadDefaultChannelLayout() } /// - /// After every zoom event, check that the axis liimits are equal to maintain the equal + /// After every zoom event, check that the axis limits are equal to maintain the equal /// aspect ratio of the graph, ensuring that all contacts do not look smashed or stretched. /// /// Incoming object diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.resx b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.resx similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/ChannelConfigurationDialog.resx rename to OpenEphys.Onix1.Design/ChannelConfigurationDialog.resx diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/ContactTag.cs b/OpenEphys.Onix1.Design/ContactTag.cs similarity index 98% rename from OpenEphys.Onix/OpenEphys.Onix.Design/ContactTag.cs rename to OpenEphys.Onix1.Design/ContactTag.cs index 2a7603a2..6c899d8f 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/ContactTag.cs +++ b/OpenEphys.Onix1.Design/ContactTag.cs @@ -1,6 +1,6 @@ using System; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { public class ContactTag { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs b/OpenEphys.Onix1.Design/DesignHelper.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs rename to OpenEphys.Onix1.Design/DesignHelper.cs index 59bda56b..f179e00c 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/DesignHelper.cs +++ b/OpenEphys.Onix1.Design/DesignHelper.cs @@ -5,7 +5,7 @@ using System.Windows.Forms; using Newtonsoft.Json; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { public static class DesignHelper { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.Designer.cs b/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.Designer.cs rename to OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs index e6dbc7e1..9fbeb09b 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs @@ -1,4 +1,4 @@ -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { partial class GenericDeviceDialog { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.cs b/OpenEphys.Onix1.Design/GenericDeviceDialog.cs similarity index 95% rename from OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.cs rename to OpenEphys.Onix1.Design/GenericDeviceDialog.cs index 77838366..8bdb310d 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.cs +++ b/OpenEphys.Onix1.Design/GenericDeviceDialog.cs @@ -1,6 +1,6 @@ using System.Windows.Forms; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { public abstract partial class GenericDeviceDialog : Form { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.resx b/OpenEphys.Onix1.Design/GenericDeviceDialog.resx similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/GenericDeviceDialog.resx rename to OpenEphys.Onix1.Design/GenericDeviceDialog.resx diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.Designer.cs similarity index 97% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.Designer.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.Designer.cs index c5d424e2..34e1bfb6 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.Designer.cs @@ -1,4 +1,4 @@ -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { partial class NeuropixelsV2eBno055Dialog { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs similarity index 95% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs index 4b287986..858acd67 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Dialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs @@ -1,6 +1,6 @@ using System; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { public partial class NeuropixelsV2eBno055Dialog : GenericDeviceDialog { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Editor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Editor.cs similarity index 96% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Editor.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eBno055Editor.cs index 83e3ec1a..15388e2f 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eBno055Editor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Editor.cs @@ -3,7 +3,7 @@ using Bonsai.Design; using System.Windows.Forms; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { internal class NeuropixelsV2eBno055Editor : WorkflowComponentEditor { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs similarity index 97% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs index 908c0453..a36df969 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.Designer.cs @@ -1,4 +1,4 @@ -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { partial class NeuropixelsV2eChannelConfigurationDialog { @@ -36,4 +36,4 @@ private void InitializeComponent() #endregion } -} \ No newline at end of file +} diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index d3037ebd..e375e419 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -6,7 +6,7 @@ using OpenEphys.ProbeInterface; using ZedGraph; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { public partial class NeuropixelsV2eChannelConfigurationDialog : ChannelConfigurationDialog { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs index dfb4642b..1f7eadb5 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs @@ -1,4 +1,4 @@ -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { partial class NeuropixelsV2eDialog { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs index 302df1ee..ee8190a4 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Windows.Forms; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { public partial class NeuropixelsV2eDialog : Form { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eDialog.resx rename to OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs similarity index 97% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eEditor.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs index f37ff17e..c6851a02 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs @@ -3,7 +3,7 @@ using System.Windows.Forms; using Bonsai.Design; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { public class NeuropixelsV2eEditor : WorkflowComponentEditor { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs index 45cafc83..1a457684 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs @@ -1,4 +1,4 @@ -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { partial class NeuropixelsV2eHeadstageDialog { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs similarity index 98% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs index 188b6886..0e4d9cb9 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs @@ -1,6 +1,6 @@ using System.Windows.Forms; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { public partial class NeuropixelsV2eHeadstageDialog : Form { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.resx similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageDialog.resx rename to OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.resx diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs similarity index 98% rename from OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs rename to OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs index 103c238f..ac57fd42 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/NeuropixelsV2eHeadstageEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs @@ -3,7 +3,7 @@ using System.Windows.Forms; using System; -namespace OpenEphys.Onix.Design +namespace OpenEphys.Onix1.Design { public class NeuropixelsV2eHeadstageEditor : WorkflowComponentEditor { diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj similarity index 78% rename from OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj rename to OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj index c702bc4c..9bd92917 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj +++ b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj @@ -1,12 +1,13 @@ - + - OpenEphys.Onix.Design + OpenEphys.Onix1.Design Bonsai Library containing visual interfaces for configuring ONIX devices. Bonsai Rx Open Ephys Onix Design net472 true - 0.1.0 + false + x64 @@ -16,8 +17,8 @@ - - + + diff --git a/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj.user b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj.user new file mode 100644 index 00000000..760e23fa --- /dev/null +++ b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj.user @@ -0,0 +1,29 @@ + + + + + + Form + + + Form + + + Form + + + Form + + + Form + + + Form + + + + + Designer + + + \ No newline at end of file diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.Designer.cs b/OpenEphys.Onix1.Design/Properties/Resources.Designer.cs similarity index 97% rename from OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.Designer.cs rename to OpenEphys.Onix1.Design/Properties/Resources.Designer.cs index e3d85ae3..e45b694d 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.Designer.cs +++ b/OpenEphys.Onix1.Design/Properties/Resources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace OpenEphys.Onix.Design.Properties { +namespace OpenEphys.Onix1.Design.Properties { using System; @@ -39,7 +39,7 @@ internal Resources() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenEphys.Onix.Design.Properties.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenEphys.Onix1.Design.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.resx b/OpenEphys.Onix1.Design/Properties/Resources.resx similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/Properties/Resources.resx rename to OpenEphys.Onix1.Design/Properties/Resources.resx diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/launchSettings.json b/OpenEphys.Onix1.Design/Properties/launchSettings.json similarity index 71% rename from OpenEphys.Onix/OpenEphys.Onix.Design/Properties/launchSettings.json rename to OpenEphys.Onix1.Design/Properties/launchSettings.json index 8e6d143e..e8640d60 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/Properties/launchSettings.json +++ b/OpenEphys.Onix1.Design/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Bonsai": { "commandName": "Executable", - "executablePath": "$(SolutionDir)..\\Bonsai\\Bonsai.exe", + "executablePath": "$(SolutionDir).bonsai/Bonsai.exe", "commandLineArgs": "--lib:$(TargetDir).", "nativeDebugging": true } diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusBlockedImage.png b/OpenEphys.Onix1.Design/Resources/StatusBlockedImage.png similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusBlockedImage.png rename to OpenEphys.Onix1.Design/Resources/StatusBlockedImage.png diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusCriticalImage.png b/OpenEphys.Onix1.Design/Resources/StatusCriticalImage.png similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusCriticalImage.png rename to OpenEphys.Onix1.Design/Resources/StatusCriticalImage.png diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusReadyImage.png b/OpenEphys.Onix1.Design/Resources/StatusReadyImage.png similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusReadyImage.png rename to OpenEphys.Onix1.Design/Resources/StatusReadyImage.png diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusRefreshImage.png b/OpenEphys.Onix1.Design/Resources/StatusRefreshImage.png similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusRefreshImage.png rename to OpenEphys.Onix1.Design/Resources/StatusRefreshImage.png diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusWarningImage.png b/OpenEphys.Onix1.Design/Resources/StatusWarningImage.png similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/Resources/StatusWarningImage.png rename to OpenEphys.Onix1.Design/Resources/StatusWarningImage.png diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/Resources/UploadImage.png b/OpenEphys.Onix1.Design/Resources/UploadImage.png similarity index 100% rename from OpenEphys.Onix/OpenEphys.Onix.Design/Resources/UploadImage.png rename to OpenEphys.Onix1.Design/Resources/UploadImage.png diff --git a/OpenEphys.Onix/OpenEphys.Onix.sln b/OpenEphys.Onix1.sln similarity index 56% rename from OpenEphys.Onix/OpenEphys.Onix.sln rename to OpenEphys.Onix1.sln index ab2ae21c..cae6e96b 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.sln +++ b/OpenEphys.Onix1.sln @@ -3,63 +3,49 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.3.32825.248 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenEphys.Onix", "OpenEphys.Onix\OpenEphys.Onix.csproj", "{353B1EBC-F8EB-4D99-8331-9FF15EC17F38}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenEphys.Onix1", "OpenEphys.Onix1\OpenEphys.Onix1.csproj", "{353B1EBC-F8EB-4D99-8331-9FF15EC17F38}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenEphys.Onix.Design", "OpenEphys.Onix.Design\OpenEphys.Onix.Design.csproj", "{149E86EC-B865-463D-81A8-8290CA7F8871}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenEphys.Onix1.Design", "OpenEphys.Onix1.Design\OpenEphys.Onix1.Design.csproj", "{149E86EC-B865-463D-81A8-8290CA7F8871}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F8644FAC-94E5-4E73-B809-925ABABE35B1}" ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenEphys.ProbeInterface", "OpenEphys.ProbeInterface\OpenEphys.ProbeInterface\OpenEphys.ProbeInterface\OpenEphys.ProbeInterface.csproj", "{3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenEphys.ProbeInterface", "OpenEphys.ProbeInterface\OpenEphys.ProbeInterface\OpenEphys.ProbeInterface.csproj", "{6DE45906-E96C-4E58-929D-55655ABB2655}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU - Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|Any CPU.ActiveCfg = Debug|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|Any CPU.Build.0 = Debug|x64 - {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|ARM64.ActiveCfg = Debug|x64 - {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|ARM64.Build.0 = Debug|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|x64.ActiveCfg = Debug|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|x64.Build.0 = Debug|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|Any CPU.ActiveCfg = Release|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|Any CPU.Build.0 = Release|x64 - {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|ARM64.ActiveCfg = Release|ARM64 - {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|ARM64.Build.0 = Release|ARM64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|x64.ActiveCfg = Release|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|x64.Build.0 = Release|x64 - {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|Any CPU.Build.0 = Debug|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|ARM64.Build.0 = Debug|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|x64.ActiveCfg = Debug|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|x64.Build.0 = Debug|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|Any CPU.ActiveCfg = Release|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|Any CPU.Build.0 = Release|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|ARM64.ActiveCfg = Release|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|ARM64.Build.0 = Release|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|x64.ActiveCfg = Release|Any CPU - {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|x64.Build.0 = Release|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Debug|ARM64.Build.0 = Debug|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Debug|x64.ActiveCfg = Debug|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Debug|x64.Build.0 = Debug|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Release|Any CPU.Build.0 = Release|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Release|ARM64.ActiveCfg = Release|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Release|ARM64.Build.0 = Release|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Release|x64.ActiveCfg = Release|Any CPU - {3FA9A55F-C23B-4897-9CEB-9DBD791B79A2}.Release|x64.Build.0 = Release|Any CPU + {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|Any CPU.ActiveCfg = Debug|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|Any CPU.Build.0 = Debug|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|x64.ActiveCfg = Debug|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|x64.Build.0 = Debug|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|Any CPU.ActiveCfg = Release|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|Any CPU.Build.0 = Release|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|x64.ActiveCfg = Release|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|x64.Build.0 = Release|x64 + {6DE45906-E96C-4E58-929D-55655ABB2655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DE45906-E96C-4E58-929D-55655ABB2655}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DE45906-E96C-4E58-929D-55655ABB2655}.Debug|x64.ActiveCfg = Debug|Any CPU + {6DE45906-E96C-4E58-929D-55655ABB2655}.Debug|x64.Build.0 = Debug|Any CPU + {6DE45906-E96C-4E58-929D-55655ABB2655}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DE45906-E96C-4E58-929D-55655ABB2655}.Release|Any CPU.Build.0 = Release|Any CPU + {6DE45906-E96C-4E58-929D-55655ABB2655}.Release|x64.ActiveCfg = Release|Any CPU + {6DE45906-E96C-4E58-929D-55655ABB2655}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OpenEphys.Onix/OpenEphys.Onix/BitHelper.cs b/OpenEphys.Onix1/BitHelper.cs similarity index 88% rename from OpenEphys.Onix/OpenEphys.Onix/BitHelper.cs rename to OpenEphys.Onix1/BitHelper.cs index 1bcd7b74..c2f00306 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/BitHelper.cs +++ b/OpenEphys.Onix1/BitHelper.cs @@ -1,6 +1,6 @@ using System.Net; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { static class BitHelper { diff --git a/OpenEphys.Onix1/Bno055Data.cs b/OpenEphys.Onix1/Bno055Data.cs new file mode 100644 index 00000000..b2f1c523 --- /dev/null +++ b/OpenEphys.Onix1/Bno055Data.cs @@ -0,0 +1,40 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that generates a sequence of 3D orientation measurements produced by BNO055 9-axis inertial measurement unit. + /// + /// + /// This data stream class must be linked to an appropriate configuration, such as a , + /// in order to stream 3D orientation data. + /// + [Description("Generates a sequence of 3D orientation measurements produced by a BNO055 9-axis inertial measurement unit.")] + public class Bno055Data : Source + { + /// + [TypeConverter(typeof(Bno055.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence of objects, each of which contains a 3D orientation sample + /// in various formats along with device metadata. + /// + /// A sequence of objects. + public override IObservable Generate() + { + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(Bno055)); + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .Select(frame => new Bno055DataFrame(frame)); + }); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/Bno055DataFrame.cs b/OpenEphys.Onix1/Bno055DataFrame.cs similarity index 54% rename from OpenEphys.Onix/OpenEphys.Onix/Bno055DataFrame.cs rename to OpenEphys.Onix1/Bno055DataFrame.cs index e5178bab..c2bbd916 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/Bno055DataFrame.cs +++ b/OpenEphys.Onix1/Bno055DataFrame.cs @@ -2,10 +2,17 @@ using System.Numerics; using System.Runtime.InteropServices; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - public class Bno055DataFrame + /// + /// A class that contains 3D orientation data produced by a Bosch BNO055 9-axis inertial measurement unit (IMU). + /// + public class Bno055DataFrame : DataFrame { + /// + /// Initializes a new instance of the class. + /// + /// An ONI data frame containing BNO055 data. public unsafe Bno055DataFrame(oni.Frame frame) : this(frame.Clock, (Bno055Payload*)frame.Data.ToPointer()) { @@ -18,8 +25,8 @@ internal unsafe Bno055DataFrame(ulong clock, Bno055Payload* payload) } internal unsafe Bno055DataFrame(ulong clock, Bno055DataPayload* payload) + : base(clock) { - Clock = clock; EulerAngle = new Vector3( x: Bno055.EulerAngleScale * payload->EulerAngle[0], y: Bno055.EulerAngleScale * payload->EulerAngle[1], @@ -41,20 +48,42 @@ internal unsafe Bno055DataFrame(ulong clock, Bno055DataPayload* payload) Calibration = payload->Calibration; } - public ulong Clock { get; } - - public ulong HubClock { get; } - + /// + /// Gets the 3D orientation in Euler angle format with units of degrees. + /// + /// + /// The Tait-Bryan formalism is used: + /// + /// Yaw: 0 to 360 degrees. + /// Roll: -180 to 180 degrees + /// Pitch: -90 to 90 degrees + /// + /// public Vector3 EulerAngle { get; } + /// + /// Gets the 3D orientation represented as a Quaternion. + /// public Quaternion Quaternion { get; } + /// + /// Gets the linear acceleration vector in units of m / s^2. + /// public Vector3 Acceleration { get; } + /// + /// Gets the gravity acceleration vector in units of m / s^2. + /// public Vector3 Gravity { get; } + /// + /// Gets the chip temperature in Celsius. + /// public int Temperature { get; } + /// + /// Gets MEMS subsystem and sensor fusion calibration status. + /// public Bno055CalibrationFlags Calibration { get; } } @@ -76,13 +105,31 @@ unsafe struct Bno055DataPayload public Bno055CalibrationFlags Calibration; } + /// + /// Specifies the MEMS subsystem and sensor fusion calibration status. + /// [Flags] public enum Bno055CalibrationFlags : byte { + /// + /// Specifies that no sub-system is calibrated. + /// None = 0, + /// + /// Specifies all three sub-systems (gyroscope, accelerometer, and magnetometer) along with sensor fusion are calibrated. + /// System = 0x3, + /// + /// Specifies that the gyroscope is calibrated. + /// Gyroscope = 0xC, + /// + /// Specifies that the accelerometer is calibrated. + /// Accelerometer = 0x30, + /// + /// Specifies that the magnetometer is calibrated. + /// Magnetometer = 0xC0 } } diff --git a/OpenEphys.Onix1/BreakoutAnalogInput.cs b/OpenEphys.Onix1/BreakoutAnalogInput.cs new file mode 100644 index 00000000..3a09fd2f --- /dev/null +++ b/OpenEphys.Onix1/BreakoutAnalogInput.cs @@ -0,0 +1,117 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Runtime.InteropServices; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// Produces a sequence of analog input frames from an ONIX breakout board. + /// + [Description("Produces a sequence of analog input frames from an ONIX breakout board.")] + public class BreakoutAnalogInput : Source + { + /// + [TypeConverter(typeof(BreakoutAnalogIO.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Gets or sets the number of samples collected for each channel that are use to create a single . + /// + /// + /// This property determines the number of analog samples that are buffered for each channel before data is propagated. For instance, if this + /// value is set to 100, then 100 samples, along with corresponding clock values, will be collected from each of the input channels + /// and packed into each . Because channels are sampled at 100 kHz, this is equivalent to 1 + /// millisecond of data from each channel. + /// + [Description("The number of analog samples that are buffered for each channel before data is propagated.")] + public int BufferSize { get; set; } = 100; + + /// + /// Gets or sets the data type used to represent analog samples. + /// + /// + /// If is selected, each ADC sample is represented at a signed, twos-complement encoded + /// 16-bit integer. samples can be converted to a voltage using each channels' + /// selection. For instance, channel 0 can be converted using . + /// When is selected, the voltage conversion is performed automatically and samples + /// are represented as 32-bit floating point voltages. + /// + [Description("The data type used to represent analog samples.")] + public BreakoutAnalogIODataType DataType { get; set; } = BreakoutAnalogIODataType.S16; + + static Mat CreateVoltageScale(int bufferSize, float[] voltsPerDivision) + { + + using var scaleHeader = Mat.CreateMatHeader( + voltsPerDivision, + rows: voltsPerDivision.Length, + cols: 1, + depth: Depth.F32, + channels: 1); + var voltageScale = new Mat(scaleHeader.Rows, bufferSize, scaleHeader.Depth, scaleHeader.Channels); + CV.Repeat(scaleHeader, voltageScale); + return voltageScale; + } + + /// + /// Generates a sequence of . + /// + /// A sequence of + public unsafe override IObservable Generate() + { + var bufferSize = BufferSize; + var dataType = DataType; + return DeviceManager.GetDevice(DeviceName).SelectMany( + deviceInfo => Observable.Create(observer => + { + var device = deviceInfo.GetDeviceContext(typeof(BreakoutAnalogIO)); + var ioDeviceInfo = (BreakoutAnalogIODeviceInfo)deviceInfo; + + var sampleIndex = 0; + var voltageScale = dataType == BreakoutAnalogIODataType.Volts + ? CreateVoltageScale(bufferSize, ioDeviceInfo.VoltsPerDivision) + : null; + var transposeBuffer = voltageScale != null + ? new Mat(BreakoutAnalogIO.ChannelCount, bufferSize, Depth.S16, 1) + : null; + var analogDataBuffer = new short[BreakoutAnalogIO.ChannelCount * bufferSize]; + var hubClockBuffer = new ulong[bufferSize]; + var clockBuffer = new ulong[bufferSize]; + + var frameObserver = Observer.Create( + frame => + { + var payload = (BreakoutAnalogInputPayload*)frame.Data.ToPointer(); + Marshal.Copy(new IntPtr(payload->AnalogData), analogDataBuffer, sampleIndex * BreakoutAnalogIO.ChannelCount, BreakoutAnalogIO.ChannelCount); + hubClockBuffer[sampleIndex] = payload->HubClock; + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= bufferSize) + { + var analogData = BufferHelper.CopyTranspose( + analogDataBuffer, + bufferSize, + BreakoutAnalogIO.ChannelCount, + Depth.S16, + voltageScale, + transposeBuffer); + observer.OnNext(new BreakoutAnalogInputDataFrame(clockBuffer, hubClockBuffer, analogData)); + hubClockBuffer = new ulong[bufferSize]; + clockBuffer = new ulong[bufferSize]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .SubscribeSafe(frameObserver); + })); + } + } +} diff --git a/OpenEphys.Onix1/BreakoutAnalogInputDataFrame.cs b/OpenEphys.Onix1/BreakoutAnalogInputDataFrame.cs new file mode 100644 index 00000000..bb62cd01 --- /dev/null +++ b/OpenEphys.Onix1/BreakoutAnalogInputDataFrame.cs @@ -0,0 +1,35 @@ +using System.Runtime.InteropServices; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// Buffered analog data produced by the ONIX breakout board. + /// + public class BreakoutAnalogInputDataFrame : BufferedDataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// A buffered array of values. + /// A buffered array of hub clock counter values. + /// A buffered array of multi-channel analog data. + public BreakoutAnalogInputDataFrame(ulong[] clock, ulong[] hubClock, Mat analogData) + : base(clock, hubClock) + { + AnalogData = analogData; + } + + /// + /// Get the buffered analog data array. + /// + public Mat AnalogData { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + unsafe struct BreakoutAnalogInputPayload + { + public ulong HubClock; + public fixed short AnalogData[BreakoutAnalogIO.ChannelCount]; + } +} diff --git a/OpenEphys.Onix1/BreakoutAnalogOutput.cs b/OpenEphys.Onix1/BreakoutAnalogOutput.cs new file mode 100644 index 00000000..0b3c47b0 --- /dev/null +++ b/OpenEphys.Onix1/BreakoutAnalogOutput.cs @@ -0,0 +1,161 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// Sends analog output data to an ONIX breakout board. + /// + [Description("Sends analog output data to an ONIX breakout board.")] + public class BreakoutAnalogOutput : Sink + { + const BreakoutAnalogIOVoltageRange OutputRange = BreakoutAnalogIOVoltageRange.TenVolts; + + /// + [TypeConverter(typeof(BreakoutAnalogIO.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Gets or sets the data type used to represent analog samples. + /// + /// + /// If is selected, each DAC value is represented by a signed, twos-complement encoded + /// 16-bit integer. In this case, the output voltage always corresponds to . + /// When is selected, 32-bit floating point voltages between -10 and 10 volts are sent + /// directly to the DACs. + /// + [Description("The data type used to represent analog samples.")] + public BreakoutAnalogIODataType DataType { get; set; } = BreakoutAnalogIODataType.S16; + + /// + /// Send samples to analog outputs. + /// + /// A sequence of 12xN sample matrices containing the analog data to write to channels 0 to 11. + /// A sequence of 12xN sample matrices containing the analog data that were written to channels 0 to 11. + public override IObservable Process(IObservable source) + { + var dataType = DataType; + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var bufferSize = 0; + var scaleBuffer = default(Mat); + var transposeBuffer = default(Mat); + var sampleScale = dataType == BreakoutAnalogIODataType.Volts + ? 1 / BreakoutAnalogIODeviceInfo.GetVoltsPerDivision(OutputRange) + : 1; + var device = deviceInfo.GetDeviceContext(typeof(BreakoutAnalogIO)); + return source.Do(data => + { + if (dataType == BreakoutAnalogIODataType.S16 && data.Depth != Depth.S16 || + dataType == BreakoutAnalogIODataType.Volts && data.Depth != Depth.F32) + { + ThrowDataTypeException(data.Depth); + } + + AssertChannelCount(data.Rows); + if (bufferSize != data.Cols) + { + bufferSize = data.Cols; + transposeBuffer = bufferSize > 1 + ? new Mat(data.Cols, data.Rows, data.Depth, 1) + : null; + if (sampleScale != 1) + { + scaleBuffer = transposeBuffer != null + ? new Mat(data.Cols, data.Rows, Depth.S16, 1) + : new Mat(data.Rows, data.Cols, Depth.S16, 1); + } + else scaleBuffer = null; + } + + var outputBuffer = data; + if (transposeBuffer != null) + { + CV.Transpose(outputBuffer, transposeBuffer); + outputBuffer = transposeBuffer; + } + + if (scaleBuffer != null) + { + CV.ConvertScale(outputBuffer, scaleBuffer, sampleScale); + outputBuffer = scaleBuffer; + } + + var dataSize = outputBuffer.Step * outputBuffer.Rows; + device.Write(outputBuffer.Data, dataSize); + }); + }); + } + + /// + /// Send samples to analog outputs. + /// + /// A sequence of 12x1 element arrays each containing the analog data to write to channels 0 to 11. + /// A sequence of 12x1 element arrays each containing the analog data to write to channels 0 to 11. + public IObservable Process(IObservable source) + { + if (DataType != BreakoutAnalogIODataType.S16) + ThrowDataTypeException(Depth.S16); + + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(BreakoutAnalogIO)); + return source.Do(data => + { + AssertChannelCount(data.Length); + device.Write(data); + }); + }); + } + + /// + /// Send samples to analog outputs. + /// + /// A sequence of 12x1 element arrays each containing the analog data to write to channels 0 to 11. + /// A sequence of 12x1 element arrays each containing the analog data to write to channels 0 to 11. + public IObservable Process(IObservable source) + { + if (DataType != BreakoutAnalogIODataType.Volts) + ThrowDataTypeException(Depth.F32); + + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(BreakoutAnalogIO)); + var divisionsPerVolt = 1 / BreakoutAnalogIODeviceInfo.GetVoltsPerDivision(OutputRange); + return source.Do(data => + { + AssertChannelCount(data.Length); + var samples = new short[data.Length]; + for (int i = 0; i < samples.Length; i++) + { + samples[i] = (short)(data[i] * divisionsPerVolt); + } + + device.Write(samples); + }); + }); + } + + static void AssertChannelCount(int channels) + { + if (channels != BreakoutAnalogIO.ChannelCount) + { + throw new InvalidOperationException( + $"The input data must have exactly {BreakoutAnalogIO.ChannelCount} channels." + ); + } + } + + static void ThrowDataTypeException(Depth depth) + { + throw new InvalidOperationException( + $"Invalid input data type '{depth}' for the specified analog IO configuration." + ); + } + } +} diff --git a/OpenEphys.Onix1/BreakoutDigitalInput.cs b/OpenEphys.Onix1/BreakoutDigitalInput.cs new file mode 100644 index 00000000..448925dd --- /dev/null +++ b/OpenEphys.Onix1/BreakoutDigitalInput.cs @@ -0,0 +1,44 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that produces a sequence of digital input frames from an ONIX breakout board. + /// + /// + /// This data stream class must be linked to an appropriate configuration, such as a , + /// in order to stream data. + /// + [Description("Produces a sequence of digital input frames from an ONIX breakout board.")] + public class BreakoutDigitalInput : Source + { + /// + [TypeConverter(typeof(BreakoutDigitalIO.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence of objects, which contains information about breakout + /// board's digital input state. + /// + /// + /// Digital inputs are not regularly sampled. Instead, a new is produced each + /// whenever any digital state (i.e. a digital input pin, button, or switch state) changes. + /// + /// A sequence of objects. + public unsafe override IObservable Generate() + { + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(BreakoutDigitalIO)); + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .Select(frame => new BreakoutDigitalInputDataFrame(frame)); + }); + } + } +} diff --git a/OpenEphys.Onix1/BreakoutDigitalInputDataFrame.cs b/OpenEphys.Onix1/BreakoutDigitalInputDataFrame.cs new file mode 100644 index 00000000..c95122f6 --- /dev/null +++ b/OpenEphys.Onix1/BreakoutDigitalInputDataFrame.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that contains information about a digital event on the ONIX breakout board. + /// + public class BreakoutDigitalInputDataFrame : DataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// A frame produced by an ONIX breakout board's digital IO device. + public unsafe BreakoutDigitalInputDataFrame(oni.Frame frame) + : base(frame.Clock) + { + var payload = (BreakoutDigitalInputPayload*)frame.Data.ToPointer(); + HubClock = payload->HubClock; + DigitalInputs = payload->DigitalInputs; + Buttons = payload->Buttons; + } + + /// + /// Gets the state of the breakout board's 8-bit digital input port. + /// + public BreakoutDigitalPortState DigitalInputs { get; } + + /// + /// Gets the state of the breakout board's buttons and switches. + /// + public BreakoutButtonState Buttons { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct BreakoutDigitalInputPayload + { + public ulong HubClock; + public BreakoutDigitalPortState DigitalInputs; + public BreakoutButtonState Buttons; + } +} diff --git a/OpenEphys.Onix1/BreakoutDigitalOutput.cs b/OpenEphys.Onix1/BreakoutDigitalOutput.cs new file mode 100644 index 00000000..14dc1d88 --- /dev/null +++ b/OpenEphys.Onix1/BreakoutDigitalOutput.cs @@ -0,0 +1,34 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// Sends digital output data to an ONIX breakout board. + /// + [Description("Sends digital output data to an ONIX breakout board.")] + public class BreakoutDigitalOutput : Sink + { + /// + [TypeConverter(typeof(BreakoutDigitalIO.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Updates the digital output port state. + /// + /// A sequence of values indicating the state of the breakout board's 8 digital output pins + /// A sequence that is identical to . + public override IObservable Process(IObservable source) + { + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(BreakoutDigitalIO)); + return source.Do(value => device.Write((uint)value)); + }); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/BufferHelper.cs b/OpenEphys.Onix1/BufferHelper.cs similarity index 98% rename from OpenEphys.Onix/OpenEphys.Onix/BufferHelper.cs rename to OpenEphys.Onix1/BufferHelper.cs index 0d3f80f2..d2083865 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/BufferHelper.cs +++ b/OpenEphys.Onix1/BufferHelper.cs @@ -1,6 +1,6 @@ using OpenCV.Net; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { static class BufferHelper { diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureBno055.cs b/OpenEphys.Onix1/ConfigureBno055.cs similarity index 51% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureBno055.cs rename to OpenEphys.Onix1/ConfigureBno055.cs index b2b297ac..95f35c8e 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureBno055.cs +++ b/OpenEphys.Onix1/ConfigureBno055.cs @@ -1,26 +1,52 @@ using System; using System.ComponentModel; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { + /// + /// A class for configuring a Bosch BNO055 9-axis inertial measurement unit (IMU). + /// + /// + /// This configuration class can be linked to a instance to stream orientation data from the IMU. + /// + [Description("Configures a Bosch BNO055 9-axis IMU device.")] public class ConfigureBno055 : SingleDeviceFactory { + /// + /// Initializes a new instance of the class. + /// public ConfigureBno055() : base(typeof(Bno055)) { } + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, a instance that is linked to this configuration will produce data. If set to false, + /// it will not produce data. + /// [Category(ConfigurationCategory)] [Description("Specifies whether the BNO055 device is enabled.")] public bool Enable { get; set; } = true; + /// + /// Configures a Bosch BNO055 9-axis IMU device. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that holds configuration actions. + /// The original sequence modified by adding additional configuration actions required to configure a BNO055 device. public override IObservable Process(IObservable source) { var deviceName = DeviceName; var deviceAddress = DeviceAddress; return source.ConfigureDevice(context => { - var device = context.GetDeviceContext(deviceAddress, Bno055.ID); + var device = context.GetDeviceContext(deviceAddress, DeviceType); device.WriteRegister(Bno055.ENABLE, Enable ? 1u : 0); return DeviceManager.RegisterDevice(deviceName, device, DeviceType); }); diff --git a/OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs b/OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs new file mode 100644 index 00000000..4a65ae10 --- /dev/null +++ b/OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs @@ -0,0 +1,397 @@ +using System; +using System.ComponentModel; +using System.Linq; + +namespace OpenEphys.Onix1 +{ + /// + /// A class for configuring the ONIX breakout board's analog inputs and outputs. + /// + [TypeConverter(typeof(SortedPropertyConverter))] + [Description("Configures the analog input and output device in the ONIX breakout board.")] + public class ConfigureBreakoutAnalogIO : SingleDeviceFactory + { + /// + /// Initialize a new instance of ConfigureAnalogIO. + /// + public ConfigureBreakoutAnalogIO() + : base(typeof(BreakoutAnalogIO)) + { + DeviceAddress = 6; + } + + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, will produce data. If set to false, will not produce data. + /// + [Category(ConfigurationCategory)] + [Description("Specifies whether the analog IO device is enabled.")] + public bool Enable { get; set; } = true; + + /// + /// Gets or sets the input voltage range of channel 0. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 0.")] + public BreakoutAnalogIOVoltageRange InputRange0 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 1. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 1.")] + public BreakoutAnalogIOVoltageRange InputRange1 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 2. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 2.")] + public BreakoutAnalogIOVoltageRange InputRange2 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 3. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 3.")] + public BreakoutAnalogIOVoltageRange InputRange3 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 4. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 4.")] + public BreakoutAnalogIOVoltageRange InputRange4 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 5. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 5.")] + public BreakoutAnalogIOVoltageRange InputRange5 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 6. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 6.")] + public BreakoutAnalogIOVoltageRange InputRange6 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 7. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 7.")] + public BreakoutAnalogIOVoltageRange InputRange7 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 8. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 8.")] + public BreakoutAnalogIOVoltageRange InputRange8 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 9. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 9.")] + public BreakoutAnalogIOVoltageRange InputRange9 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 10. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 10.")] + public BreakoutAnalogIOVoltageRange InputRange10 { get; set; } + + /// + /// Gets or sets the input voltage range of channel 11. + /// + [Category(ConfigurationCategory)] + [Description("The input voltage range of channel 11.")] + public BreakoutAnalogIOVoltageRange InputRange11 { get; set; } + + /// + /// Gets or sets the direction of channel 0. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 0.")] + public BreakoutAnalogIODirection Direction0 { get; set; } + + /// + /// Gets or sets the direction of channel 1. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 1.")] + public BreakoutAnalogIODirection Direction1 { get; set; } + + /// + /// Gets or sets the direction of channel 2. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 2.")] + public BreakoutAnalogIODirection Direction2 { get; set; } + + /// + /// Gets or sets the direction of channel 3. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 3.")] + public BreakoutAnalogIODirection Direction3 { get; set; } + + /// + /// Gets or sets the direction of channel 4. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 4.")] + public BreakoutAnalogIODirection Direction4 { get; set; } + + /// + /// Gets or sets the direction of channel 5. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 5.")] + public BreakoutAnalogIODirection Direction5 { get; set; } + + /// + /// Gets or sets the direction of channel 6. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 6.")] + public BreakoutAnalogIODirection Direction6 { get; set; } + + /// + /// Gets or sets the direction of channel 7. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 7.")] + public BreakoutAnalogIODirection Direction7 { get; set; } + + /// + /// Gets or sets the direction of channel 8. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 8.")] + public BreakoutAnalogIODirection Direction8 { get; set; } + + /// + /// Gets or sets the direction of channel 9. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 9.")] + public BreakoutAnalogIODirection Direction9 { get; set; } + + /// + /// Gets or sets the direction of channel 10. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 10.")] + public BreakoutAnalogIODirection Direction10 { get; set; } + + /// + /// Gets or sets the direction of channel 11. + /// + [Category(AcquisitionCategory)] + [Description("The direction of channel 11.")] + public BreakoutAnalogIODirection Direction11 { get; set; } + + /// + /// Configures the analog input and output device in the ONIX breakout board. + /// + /// + /// This will schedule analog IO hardware configuration actions that can be applied by a + /// object prior to data collection. + /// + /// + /// The sequence of objects on which to apply the analog IO configuration. + /// + /// + /// A sequence of objects that is identical to + /// in which each has been instructed to apply the analog IO configuration. + /// + public override IObservable Process(IObservable source) + { + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + var device = context.GetDeviceContext(deviceAddress, DeviceType); + device.WriteRegister(BreakoutAnalogIO.ENABLE, Enable ? 1u : 0u); + device.WriteRegister(BreakoutAnalogIO.CH00INRANGE, (uint)InputRange0); + device.WriteRegister(BreakoutAnalogIO.CH01INRANGE, (uint)InputRange1); + device.WriteRegister(BreakoutAnalogIO.CH02INRANGE, (uint)InputRange2); + device.WriteRegister(BreakoutAnalogIO.CH03INRANGE, (uint)InputRange3); + device.WriteRegister(BreakoutAnalogIO.CH04INRANGE, (uint)InputRange4); + device.WriteRegister(BreakoutAnalogIO.CH05INRANGE, (uint)InputRange5); + device.WriteRegister(BreakoutAnalogIO.CH06INRANGE, (uint)InputRange6); + device.WriteRegister(BreakoutAnalogIO.CH07INRANGE, (uint)InputRange7); + device.WriteRegister(BreakoutAnalogIO.CH08INRANGE, (uint)InputRange8); + device.WriteRegister(BreakoutAnalogIO.CH09INRANGE, (uint)InputRange9); + device.WriteRegister(BreakoutAnalogIO.CH10INRANGE, (uint)InputRange10); + device.WriteRegister(BreakoutAnalogIO.CH11INRANGE, (uint)InputRange11); + + // Build the whole value for CHDIR and write it once + static uint SetIO(uint io_reg, int channel, BreakoutAnalogIODirection direction) => + (io_reg & ~((uint)1 << channel)) | ((uint)(direction) << channel); + + var io_reg = 0u; + io_reg = SetIO(io_reg, 0, Direction0); + io_reg = SetIO(io_reg, 1, Direction1); + io_reg = SetIO(io_reg, 2, Direction2); + io_reg = SetIO(io_reg, 3, Direction3); + io_reg = SetIO(io_reg, 4, Direction4); + io_reg = SetIO(io_reg, 5, Direction5); + io_reg = SetIO(io_reg, 6, Direction6); + io_reg = SetIO(io_reg, 7, Direction7); + io_reg = SetIO(io_reg, 8, Direction8); + io_reg = SetIO(io_reg, 9, Direction9); + io_reg = SetIO(io_reg, 10, Direction10); + io_reg = SetIO(io_reg, 11, Direction11); + device.WriteRegister(BreakoutAnalogIO.CHDIR, io_reg); + + var deviceInfo = new BreakoutAnalogIODeviceInfo(device, this); + return DeviceManager.RegisterDevice(deviceName, deviceInfo); + }); + } + + class SortedPropertyConverter : ExpandableObjectConverter + { + public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) + { + var properties = base.GetProperties(context, value, attributes); + var sortedOrder = properties.Cast() + .Where(p => p.PropertyType == typeof(BreakoutAnalogIOVoltageRange) + || p.PropertyType == typeof(BreakoutAnalogIODirection)) + .OrderBy(p => p.PropertyType.MetadataToken) + .Select(p => p.Name) + .Prepend(nameof(Enable)) + .ToArray(); + return properties.Sort(sortedOrder); + } + } + } + + static class BreakoutAnalogIO + { + public const int ID = 22; + + // constants + public const int ChannelCount = 12; + public const int NumberOfDivisions = 1 << 16; + + // managed registers + public const uint ENABLE = 0; + public const uint CHDIR = 1; + public const uint CH00INRANGE = 2; + public const uint CH01INRANGE = 3; + public const uint CH02INRANGE = 4; + public const uint CH03INRANGE = 5; + public const uint CH04INRANGE = 6; + public const uint CH05INRANGE = 7; + public const uint CH06INRANGE = 8; + public const uint CH07INRANGE = 9; + public const uint CH08INRANGE = 10; + public const uint CH09INRANGE = 11; + public const uint CH10INRANGE = 12; + public const uint CH11INRANGE = 13; + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(BreakoutAnalogIO)) + { + } + } + } + + class BreakoutAnalogIODeviceInfo : DeviceInfo + { + public BreakoutAnalogIODeviceInfo(DeviceContext device, ConfigureBreakoutAnalogIO deviceFactory) + : base(device, deviceFactory.DeviceType) + { + VoltsPerDivision = new[] + { + GetVoltsPerDivision(deviceFactory.InputRange0), + GetVoltsPerDivision(deviceFactory.InputRange1), + GetVoltsPerDivision(deviceFactory.InputRange2), + GetVoltsPerDivision(deviceFactory.InputRange3), + GetVoltsPerDivision(deviceFactory.InputRange4), + GetVoltsPerDivision(deviceFactory.InputRange5), + GetVoltsPerDivision(deviceFactory.InputRange6), + GetVoltsPerDivision(deviceFactory.InputRange7), + GetVoltsPerDivision(deviceFactory.InputRange8), + GetVoltsPerDivision(deviceFactory.InputRange9), + GetVoltsPerDivision(deviceFactory.InputRange10), + GetVoltsPerDivision(deviceFactory.InputRange11) + }; + } + + public static float GetVoltsPerDivision(BreakoutAnalogIOVoltageRange voltageRange) + { + return voltageRange switch + { + BreakoutAnalogIOVoltageRange.TenVolts => 20.0f / BreakoutAnalogIO.NumberOfDivisions, + BreakoutAnalogIOVoltageRange.TwoPointFiveVolts => 5.0f / BreakoutAnalogIO.NumberOfDivisions, + BreakoutAnalogIOVoltageRange.FiveVolts => 10.0f / BreakoutAnalogIO.NumberOfDivisions, + _ => throw new ArgumentOutOfRangeException(nameof(voltageRange)), + }; + } + + public float[] VoltsPerDivision { get; } + } + + /// + /// Specifies the analog input ADC voltage range. + /// + public enum BreakoutAnalogIOVoltageRange + { + /// + /// ±10.0 volts. + /// + [Description("+/-10.0 volts")] + TenVolts = 0, + /// + /// ±2.5 volts. + /// + [Description("+/-2.5 volts")] + TwoPointFiveVolts = 1, + /// + /// ±5.0 volts. + /// + [Description("+/-5.0 volts")] + FiveVolts, + } + + /// + /// Specifies analog channel direction. + /// + public enum BreakoutAnalogIODirection + { + /// + /// Input to breakout board. + /// + Input = 0, + /// + /// Output from breakout board with loopback. + /// + Output = 1 + } + + /// + /// Specifies the analog sample representation. + /// + public enum BreakoutAnalogIODataType + { + /// + /// Twos-complement encoded signed 16-bit integer + /// + S16, + /// + /// 32-bit floating point voltage. + /// + Volts + } +} diff --git a/OpenEphys.Onix1/ConfigureBreakoutBoard.cs b/OpenEphys.Onix1/ConfigureBreakoutBoard.cs new file mode 100644 index 00000000..bdd68354 --- /dev/null +++ b/OpenEphys.Onix1/ConfigureBreakoutBoard.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that configures an ONIX breakout board. + /// + [Description("Configures an ONIX breakout board.")] + public class ConfigureBreakoutBoard : MultiDeviceFactory + { + /// + /// Gets or sets the heartbeat configuration. + /// + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the heartbeat device in the ONIX breakout board.")] + public ConfigureHeartbeat Heartbeat { get; set; } = new(); + + /// + /// Gets or sets the breakout board's analog IO configuration. + /// + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the analog IO device in the ONIX breakout board.")] + public ConfigureBreakoutAnalogIO AnalogIO { get; set; } = new(); + + /// + /// Gets or sets the breakout board's digital IO configuration. + /// + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the digital IO device in the ONIX breakout board.")] + public ConfigureBreakoutDigitalIO DigitalIO { get; set; } = new(); + + /// + /// Gets or sets the hardware memory monitor configuration. + /// + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the memory monitor device in the ONIX breakout board.")] + public ConfigureMemoryMonitor MemoryMonitor { get; set; } = new(); + + internal override IEnumerable GetDevices() + { + yield return Heartbeat; + yield return AnalogIO; + yield return DigitalIO; + yield return MemoryMonitor; + } + } +} diff --git a/OpenEphys.Onix1/ConfigureBreakoutDigitalIO.cs b/OpenEphys.Onix1/ConfigureBreakoutDigitalIO.cs new file mode 100644 index 00000000..850a7a80 --- /dev/null +++ b/OpenEphys.Onix1/ConfigureBreakoutDigitalIO.cs @@ -0,0 +1,166 @@ +using System; +using System.ComponentModel; + +namespace OpenEphys.Onix1 +{ + /// + /// A class for configuring the ONIX breakout board's digital inputs and outputs. + /// + [Description("Configures the digital input and output device in the ONIX breakout board.")] + public class ConfigureBreakoutDigitalIO : SingleDeviceFactory + { + /// + /// Initialize a new instance of . + /// + public ConfigureBreakoutDigitalIO() + : base(typeof(BreakoutDigitalIO)) + { + DeviceAddress = 7; + } + + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, will produce data. If set to false, will not produce data. + /// + [Category(ConfigurationCategory)] + [Description("Specifies whether the digital IO device is enabled.")] + public bool Enable { get; set; } = true; + + /// + /// Configures the digital input and output device in the ONIX breakout board. + /// + /// + /// This will schedule digital IO hardware configuration actions that can be applied by a + /// object prior to data collection. + /// + /// A sequence of instances that hold configuration actions. + /// + /// The original sequence modified by adding additional configuration actions required to configure a digital IO device. + /// + public override IObservable Process(IObservable source) + { + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + var device = context.GetDeviceContext(deviceAddress, DeviceType); + device.WriteRegister(BreakoutDigitalIO.ENABLE, Enable ? 1u : 0); + return DeviceManager.RegisterDevice(deviceName, device, DeviceType); + }); + } + } + + static class BreakoutDigitalIO + { + public const int ID = 18; + + // managed registers + public const uint ENABLE = 0x0; // Enable or disable the data output stream + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(BreakoutDigitalIO)) + { + } + } + } + + /// + /// Specifies the state of the ONIX breakout board's digital input pins. + /// + [Flags] + public enum BreakoutDigitalPortState : ushort + { + /// + /// Specifies that pin 0 is high. + /// + Pin0 = 0x1, + /// + /// Specifies that pin 1 is high. + /// + Pin1 = 0x2, + /// + /// Specifies that pin 2 is high. + /// + Pin2 = 0x4, + /// + /// Specifies that pin 3 is high. + /// + Pin3 = 0x8, + /// + /// Specifies that pin 4 is high. + /// + Pin4 = 0x10, + /// + /// Specifies that pin 5 is high. + /// + Pin5 = 0x20, + /// + /// Specifies that pin 6 is high. + /// + Pin6 = 0x40, + /// + /// Specifies that pin 7 is high. + /// + Pin7 = 0x80, + } + + /// + /// Specifies the state of the ONIX breakout board's switches and buttons. + /// + [Flags] + public enum BreakoutButtonState : ushort + { + /// + /// Specifies that the ☾ key is depressed. + /// + Moon = 0x1, + /// + /// Specifies that the △ key is depressed. + /// + Triangle = 0x2, + /// + /// Specifies that the × key is depressed. + /// + X = 0x4, + /// + /// Specifies that the ✓ key is depressed. + /// + Check = 0x8, + /// + /// Specifies that the ◯ key is depressed. + /// + Circle = 0x10, + /// + /// Specifies that the □ key is depressed. + /// + Square = 0x20, + /// + /// Specifies that reserved bit 0 is high. + /// + Reserved0 = 0x40, + /// + /// Specifies that reserved bit 1 is high. + /// + Reserved1 = 0x80, + /// + /// Specifies that port D power switch is set to on. + /// + PortDOn = 0x100, + /// + /// Specifies that port C power switch is set to on. + /// + PortCOn = 0x200, + /// + /// Specifies that port B power switch is set to on. + /// + PortBOn = 0x400, + /// + /// Specifies that port A power switch is set to on. + /// + PortAOn = 0x800, + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs b/OpenEphys.Onix1/ConfigureFmcLinkController.cs similarity index 75% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs rename to OpenEphys.Onix1/ConfigureFmcLinkController.cs index 79b45d66..818c9e7f 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs +++ b/OpenEphys.Onix1/ConfigureFmcLinkController.cs @@ -3,9 +3,9 @@ using System.Reactive.Disposables; using System.Threading; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - public abstract class ConfigureFmcLinkController : SingleDeviceFactory + internal abstract class ConfigureFmcLinkController : SingleDeviceFactory { public ConfigureFmcLinkController() : base(typeof(FmcLinkController)) @@ -53,7 +53,7 @@ public override IObservable Process(IObservable source }) .ConfigureLink(context => { - var device = context.GetDeviceContext(deviceAddress, FmcLinkController.ID); + var device = context.GetDeviceContext(deviceAddress, DeviceType); void dispose() => device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0); device.WriteRegister(FmcLinkController.ENABLE, 1); @@ -73,20 +73,26 @@ public override IObservable Process(IObservable source return DeviceManager.RegisterDevice(deviceName, deviceInfo); }); } + } - internal static class FmcLinkController - { - public const int ID = 23; + internal static class FmcLinkController + { + public const int ID = 23; - public const uint ENABLE = 0; // The LSB is used to enable or disable the device data stream - public const uint GPOSTATE = 1; // GPO output state (bits 31 downto 3: ignore. bits 2 downto 0: ‘1’ = high, ‘0’ = low) - public const uint DESPWR = 2; // Set link deserializer PDB state, 0 = deserializer power off else on. Does not affect port voltage. - public const uint PORTVOLTAGE = 3; // 10 * link voltage - public const uint SAVEVOLTAGE = 4; // Save link voltage to non-volatile EEPROM if greater than 0. This voltage will be applied after POR. - public const uint LINKSTATE = 5; // bit 1 pass; bit 0 lock + public const uint ENABLE = 0; // The LSB is used to enable or disable the device data stream + public const uint GPOSTATE = 1; // GPO output state (bits 31 downto 3: ignore. bits 2 downto 0: ‘1’ = high, ‘0’ = low) + public const uint DESPWR = 2; // Set link deserializer PDB state, 0 = deserializer power off else on. Does not affect port voltage. + public const uint PORTVOLTAGE = 3; // 10 * link voltage + public const uint SAVEVOLTAGE = 4; // Save link voltage to non-volatile EEPROM if greater than 0. This voltage will be applied after POR. + public const uint LINKSTATE = 5; // bit 1 pass; bit 0 lock - public const uint LINKSTATE_PP = 0x2; // parity check pass bit - public const uint LINKSTATE_SL = 0x1; // SERDES lock bit - } + public const uint LINKSTATE_PP = 0x2; // parity check pass bit + public const uint LINKSTATE_SL = 0x1; // SERDES lock bit + } + + internal enum HubConfiguration + { + Standard, + Passthrough } } diff --git a/OpenEphys.Onix1/ConfigureHarpSyncInput.cs b/OpenEphys.Onix1/ConfigureHarpSyncInput.cs new file mode 100644 index 00000000..3b8ce24a --- /dev/null +++ b/OpenEphys.Onix1/ConfigureHarpSyncInput.cs @@ -0,0 +1,126 @@ +using System; +using System.ComponentModel; + +namespace OpenEphys.Onix1 +{ + /// + /// A class for configuring the ONIX breakout board Harp sync input device. + /// + /// + /// + /// Harp is a standard for asynchronous real-time data acquisition and experimental + /// control in neuroscience. It includes a clock synchronization protocol which allows + /// Harp devices to be connected to a shared clock line and continuously self-synchronize + /// their clocks to a precision of tens of microseconds. This means that all experimental + /// events are timestamped on the same clock and no post-hoc alignment of timing is necessary. + /// + /// + /// The Harp clock signal is transmitted over a serial line every second. + /// Every time the Harp sync input device in the ONIX breakout board detects a full Harp + /// synchronization packet, a new data frame is emitted pairing the current value of the + /// Harp clock with the local ONIX acquisition clock. + /// + /// + /// Logging the sequence of all Harp synchronization packets can greatly facilitate post-hoc + /// analysis and interpretation of timing signals. For more information see + /// . + /// + /// + [Description("Configures a ONIX breakout board Harp sync input device.")] + public class ConfigureHarpSyncInput : SingleDeviceFactory + { + /// + /// Initializes a new instance of the class. + /// + public ConfigureHarpSyncInput() + : base(typeof(HarpSyncInput)) + { + DeviceAddress = 12; + } + + /// + /// Gets or sets a value specifying whether the Harp sync input device is enabled. + /// + [Category(ConfigurationCategory)] + [Description("Specifies whether the Harp sync input device is enabled.")] + public bool Enable { get; set; } = true; + + /// + /// Gets or sets a value specifying the physical Harp clock input source. + /// + /// + /// In standard ONIX breakout boards, the Harp mini-jack connector on the side of the + /// breakout is configured to receive Harp clock synchronization signals. + /// + /// In early access versions of the ONIX breakout board, the Harp mini-jack connector is + /// configured for output only, so a special adapter is needed to transmit the + /// Harp clock synchronization signal to the breakout clock input zero. + /// + [Category(ConfigurationCategory)] + [Description("Specifies the physical Harp clock input source.")] + public HarpSyncSource Source { get; set; } = HarpSyncSource.Breakout; + + /// + /// Configures a ONIX breakout board Harp sync input device. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that hold configuration actions. + /// + /// The original sequence modified by adding additional configuration actions required to configure + /// a ONIX breakout board Harp sync input device. + /// + public override IObservable Process(IObservable source) + { + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + var device = context.GetDeviceContext(deviceAddress, DeviceType); + device.WriteRegister(HarpSyncInput.ENABLE, Enable ? 1u : 0); + device.WriteRegister(HarpSyncInput.SOURCE, (uint)Source); + return DeviceManager.RegisterDevice(deviceName, device, DeviceType); + }); + } + } + + static class HarpSyncInput + { + public const int ID = 30; + + // managed registers + public const uint ENABLE = 0x0; // Enable or disable the data stream + public const uint SOURCE = 0x1; // Select the clock input source + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(HarpSyncInput)) + { + } + } + } + + /// + /// Specifies the physical Harp clock input source. + /// + public enum HarpSyncSource + { + /// + /// Specifies the Harp 3.5-mm audio jack connector on the side of the ONIX breakout board. + /// + Breakout = 0, + + /// + /// Specifies SMA clock input 0 on the ONIX breakout board. + /// + /// + /// In early access versions of the ONIX breakout board, Harp 3.5-mm audio jack connector was + /// configured for output only, so a special adapter was needed to transmit the Harp clock + /// synchronization signal to the breakout clock input zero. + /// + ClockAdapter = 1 + } +} diff --git a/OpenEphys.Onix1/ConfigureHeadstage64.cs b/OpenEphys.Onix1/ConfigureHeadstage64.cs new file mode 100644 index 00000000..9e7fd150 --- /dev/null +++ b/OpenEphys.Onix1/ConfigureHeadstage64.cs @@ -0,0 +1,193 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that configures an ONIX headstage-64 in the specified port. + /// + [Description("Configures an ONIX headstage-64 in the specified port.")] + public class ConfigureHeadstage64 : MultiDeviceFactory + { + PortName port; + readonly ConfigureHeadstage64LinkController LinkController = new(); + + /// + /// Initializes a new instance of the class. + /// + /// + /// Headstage-64 is a 1.5g serialized, multifunction headstage for small animals. This headstage is designed to function + /// with tetrode microdrives. Alternatively it can be used with other passive probes (e.g. silicon arrays, EEG/ECOG arrays, + /// etc.). It provides the following features on the headstage: + /// + /// 64 analog ephys channels and 3 auxiliary channels sampled at 30 kHz per channel. + /// A BNO055 9-axis IMU for real-time, 3D orientation tracking. + /// Three TS4231 light to digital converters for real-time, 3D position tracking with HTC Vive base stations. + /// A single electrical stimulator (current controlled, +/-15V compliance, automatic electrode discharge). + /// Two optical stimulators (800 mA peak current per channel). + /// + /// + public ConfigureHeadstage64() + { + // WONTFIX: The issue with this headstage is that its locking voltage is far, far lower than the voltage required for full + // functionality. Locking occurs at around 2V on the headstage (enough to turn 1.8V on). Full functionality is at 5.0 volts. + // The FMC port voltage can only go down to 3.3V, which means that its very hard to find the true lowest voltage + // for a lock and then add a large offset to that. Fixing this requires a hardware change. + Port = PortName.PortA; + LinkController.HubConfiguration = HubConfiguration.Standard; + } + + /// + /// Gets or sets the Rhd2164 configuration. + /// + [Category(ConfigurationCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the Rhd2164 device in the headstage-64.")] + public ConfigureRhd2164 Rhd2164 { get; set; } = new(); + + /// + /// Gets or sets the Bno055 9-axis inertial measurement unit configuration. + /// + [Category(ConfigurationCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the Bno055 device in the headstage-64.")] + public ConfigureBno055 Bno055 { get; set; } = new(); + + /// + /// Gets or sets the SteamVR V1 basestation 3D tracking array configuration. + /// + [Category(ConfigurationCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the TS4231 device in the headstage-64.")] + public ConfigureTS4231V1 TS4231 { get; set; } = new() { Enable = false }; + + /// + /// Gets or sets onboard electrical stimulator configuration. + /// + [Category(ConfigurationCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the ElectricalStimulator device in the headstage-64.")] + public ConfigureHeadstage64ElectricalStimulator ElectricalStimulator { get; set; } = new(); + + /// + /// Gets or sets onboard optical stimulator configuration. + /// + [Category(ConfigurationCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the OpticalStimulator device in the headstage-64.")] + public ConfigureHeadstage64OpticalStimulator OpticalStimulator { get; set; } = new(); + + /// + /// Gets or sets the port. + /// + /// + /// The port is the physical connection to the ONIX breakout board and must be specified prior to operation. + /// + [Description("Specifies the physical connection of the headstage to the ONIX breakout board.")] + public PortName Port + { + get { return port; } + set + { + port = value; + var offset = (uint)port << 8; + LinkController.DeviceAddress = (uint)port; + Rhd2164.DeviceAddress = offset + 0; + Bno055.DeviceAddress = offset + 1; + TS4231.DeviceAddress = offset + 2; + ElectricalStimulator.DeviceAddress = offset + 3; + OpticalStimulator.DeviceAddress = offset + 4; + } + } + + /// + /// Gets or sets the port voltage override. + /// + /// + /// + /// If defined, it will override automated voltage discovery and apply the specified voltage to the headstage. + /// If left blank, an automated headstage detection algorithm will attempt to communicate with the headstage and + /// apply an appropriate voltage for stable operation. Because ONIX allows any coaxial tether to be used, some of + /// which are thin enough to result in a significant voltage drop, its may be required to manually specify the + /// port voltage. + /// + /// + /// Warning: this device requires 5.5V to 6.0V, measured at the headstage, for proper operation. Supplying higher + /// voltages may result in damage. + /// + /// + [Description("If defined, it will override automated voltage discovery and apply the specified voltage" + + "to the headstage. Warning: this device requires 5.5V to 6.0V for proper operation." + + "Supplying higher voltages may result in damage to the headstage.")] + public double? PortVoltage + { + get => LinkController.PortVoltage; + set => LinkController.PortVoltage = value; + } + + internal override IEnumerable GetDevices() + { + yield return LinkController; + yield return Rhd2164; + yield return Bno055; + yield return TS4231; + yield return ElectricalStimulator; + yield return OpticalStimulator; + } + + class ConfigureHeadstage64LinkController : ConfigureFmcLinkController + { + protected override bool ConfigurePortVoltage(DeviceContext device) + { + // WONTFIX: It takes a huge amount of time to get to 0, almost 10 seconds. + // The best we can do at the moment is drive port voltage to minimum which + // is an active process and then settle from there to zero volts. This requires + // a hardware revision that discharges the headstage between cycles to fix. + const uint MinVoltage = 33; + const uint MaxVoltage = 60; + const uint VoltageOffset = 34; + const uint VoltageIncrement = 02; + + // Start with highest voltage and ramp it down to find lowest lock voltage + var voltage = MaxVoltage; + for (; voltage >= MinVoltage; voltage -= VoltageIncrement) + { + device.WriteRegister(FmcLinkController.PORTVOLTAGE, voltage); + Thread.Sleep(200); + if (!CheckLinkState(device)) + { + if (voltage == MaxVoltage) return false; + else break; + } + } + + device.WriteRegister(FmcLinkController.PORTVOLTAGE, MinVoltage); + device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0); + Thread.Sleep(1000); + device.WriteRegister(FmcLinkController.PORTVOLTAGE, voltage + VoltageOffset); + Thread.Sleep(200); + return CheckLinkState(device); + } + } + } + + /// + /// Specifies the physical port that a headstage is plugged into. + /// + /// + /// ONIX uses a common protocol to communicate with a variety of devices using the same physical connection. For this reason + /// lots of different headstage types can be plugged into a headstage port. + /// + public enum PortName + { + /// + /// Specifies Port A. + /// + PortA = 1, + /// + /// Specifies Port B. + /// + PortB = 2 + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstage64ElectricalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs similarity index 62% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstage64ElectricalStimulator.cs rename to OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs index fe5d8c5e..053afe34 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstage64ElectricalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs @@ -1,21 +1,44 @@ using System; +using System.ComponentModel; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { + /// + /// A class that configures a headstage-64 onboard electrical stimulator. + /// + /// + /// This configuration class can be linked to a instance to deliver + /// current controlled electrical micro-stimulation through a contact on the probe connector on the bottom of the headstage + /// or the corresponding contact on a compatible electrode interface board. + /// + [Description("Configures a headstage-64 onboard electrical stimulator.")] public class ConfigureHeadstage64ElectricalStimulator : SingleDeviceFactory { + /// + /// Initializes a new instance of the class. + /// public ConfigureHeadstage64ElectricalStimulator() : base(typeof(Headstage64ElectricalStimulator)) { } + /// + /// Configure a headstage-64 onboard electrical stimulator. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that holds configuration actions. + /// The original sequence modified by adding additional configuration actions required to configure a headstage-64 + /// onboard electrical stimulator. public override IObservable Process(IObservable source) { var deviceName = DeviceName; var deviceAddress = DeviceAddress; return source.ConfigureDevice(context => { - var device = context.GetDeviceContext(deviceAddress, Headstage64ElectricalStimulator.ID); + var device = context.GetDeviceContext(deviceAddress, DeviceType); device.WriteRegister(Headstage64ElectricalStimulator.ENABLE, 0); return DeviceManager.RegisterDevice(deviceName, device, DeviceType); }); @@ -26,6 +49,10 @@ static class Headstage64ElectricalStimulator { public const int ID = 4; + // NB: could be read from REZ but these are constant + public const double DacBitDepth = 16; + public const double AbsMaxMicroAmps = 2500; + // managed registers public const uint NULLPARM = 0; // No command public const uint BIPHASIC = 1; // Biphasic pulse (0 = monophasic, 1 = biphasic; NB: currently ignored) diff --git a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs new file mode 100644 index 00000000..64de716d --- /dev/null +++ b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs @@ -0,0 +1,81 @@ +using System; +using System.ComponentModel; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that configures a headstage-64 dual-channel optical stimulator. + /// + /// + /// This configuration class can be linked to a instance to drive current + /// through laser diodes or LEDs connected to two contacts on the probe connector on the bottom of the headstage + /// or the corresponding contacts on a compatible electrode interface board. + /// + [Description("Configures a headstage-64 dual-channel optical stimulator.")] + public class ConfigureHeadstage64OpticalStimulator : SingleDeviceFactory + { + /// + /// Initializes a new instance of the class. + /// + public ConfigureHeadstage64OpticalStimulator() + : base(typeof(Headstage64OpticalStimulator)) + { + } + + /// + /// Configure a headstage-64 dual-channel optical stimulator. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that holds configuration actions. + /// The original sequence modified by adding additional configuration actions required to configure a + /// headstage-64 dual-channel optical stimulator. + public override IObservable Process(IObservable source) + { + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + var device = context.GetDeviceContext(deviceAddress, DeviceType); + device.WriteRegister(Headstage64OpticalStimulator.ENABLE, 0); + return DeviceManager.RegisterDevice(deviceName, device, DeviceType); + }); + } + } + + static class Headstage64OpticalStimulator + { + public const int ID = 5; + + // NB: can be read with MINRHEOR and POTRES, but will not change + public const uint MinRheostatResistanceOhms = 590; + public const uint PotResistanceOhms = 100_000; + + // managed registers + public const uint NULLPARM = 0; // No command + public const uint MAXCURRENT = 1; // Max LED/LD current, (0 to 255 = 800mA to 0 mA.See fig XX of CAT4016 datasheet) + public const uint PULSEMASK = 2; // Bitmask determining which of the(up to 32) channels is affected by trigger + public const uint PULSEDUR = 3; // Pulse duration, microseconds + public const uint PULSEPERIOD = 4; // Inter-pulse interval, microseconds + public const uint BURSTCOUNT = 5; // Number of pulses in burst + public const uint IBI = 6; // Inter-burst interval, microseconds + public const uint TRAINCOUNT = 7; // Number of bursts in train + public const uint TRAINDELAY = 8; // Stimulus start delay, microseconds + public const uint TRIGGER = 9; // Trigger stimulation (0 = off, 1 = deliver) + public const uint ENABLE = 10; // 1: enables the stimulator, 0: stimulator ignores triggers (so that a common trigger can be used) + public const uint RESTMASK = 11; // Bitmask determining the off state of the up to 32 current channels + public const uint RESET = 12; // None If 1, Reset all parameters to default (not implemented) + public const uint MINRHEOR = 13; // The series resistor between the potentiometer (rheostat) and RSET bin on the CAT4016 + public const uint POTRES = 14; // The resistance value of the potentiometer connected in rheostat config to RSET on CAT4016 + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(Headstage64OpticalStimulator)) + { + } + } + } +} diff --git a/OpenEphys.Onix1/ConfigureHeartbeat.cs b/OpenEphys.Onix1/ConfigureHeartbeat.cs new file mode 100644 index 00000000..a8d070e5 --- /dev/null +++ b/OpenEphys.Onix1/ConfigureHeartbeat.cs @@ -0,0 +1,104 @@ +using System; +using System.ComponentModel; +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class for configuring a heartbeat device. + /// + /// This configuration class can be linked to a instance to stream + /// heartbeats from the acquisition system. + /// + /// + [Description("Configures a heartbeat device.")] + public class ConfigureHeartbeat : SingleDeviceFactory + { + readonly BehaviorSubject beatsPerSecond = new(10); + + /// + /// Initializes and new instance of the class. + /// + public ConfigureHeartbeat() + : base(typeof(Heartbeat)) + { + } + + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, a instance that is linked to this configuration will produce data. + /// If set to false, it will not produce data. + /// + [Category(ConfigurationCategory)] + [Description("Specifies whether the heartbeat device is enabled.")] + public bool Enable { get; set; } = true; + + /// + /// Gets or sets the rate at which beats are produced in Hz. + /// + /// + /// If set to true, a instance that is linked to this configuration will produce data. + /// If set to false, it will not produce data. + /// + [Range(1, 10e6)] + [Category(AcquisitionCategory)] + [Description("Rate at which beats are produced (Hz).")] + public uint BeatsPerSecond + { + get => beatsPerSecond.Value; + set => beatsPerSecond.OnNext(value); + } + + /// + /// Configures a heartbeat device. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that holds configuration actions. + /// The original sequence modified by adding additional configuration actions required to configure a heartbeat device./> + public override IObservable Process(IObservable source) + { + var enable = Enable; + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice((context, observer) => + { + var device = context.GetDeviceContext(deviceAddress, DeviceType); + device.WriteRegister(Heartbeat.ENABLE, enable ? 1u : 0u); + var subscription = beatsPerSecond.SubscribeSafe(observer, newValue => + { + var clkHz = device.ReadRegister(Heartbeat.CLK_HZ); + device.WriteRegister(Heartbeat.CLK_DIV, clkHz / newValue); + }); + + return new CompositeDisposable( + DeviceManager.RegisterDevice(deviceName, device, DeviceType), + subscription + ); + }); + } + } + + static class Heartbeat + { + public const int ID = 12; + + public const uint ENABLE = 0; // Enable the heartbeat + public const uint CLK_DIV = 1; // Heartbeat clock divider ratio. Default results in 10 Hz heartbeat. Values less than CLK_HZ / 10e6 Hz will result in 1kHz. + public const uint CLK_HZ = 2; // The frequency parameter, CLK_HZ, used in the calculation of CLK_DIV + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(Heartbeat)) + { + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureLoadTester.cs b/OpenEphys.Onix1/ConfigureLoadTester.cs similarity index 56% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureLoadTester.cs rename to OpenEphys.Onix1/ConfigureLoadTester.cs index 4c02a155..12259869 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureLoadTester.cs +++ b/OpenEphys.Onix1/ConfigureLoadTester.cs @@ -5,67 +5,108 @@ using System.Reactive.Subjects; using Bonsai; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { + /// + /// A class for configuring a load testing device. + /// + /// + /// The load tester device can be configured to produce data at user-settable size and rate + /// to stress test various communication links and test closed-loop response latency. + /// + [Description("Configures a load testing device.")] public class ConfigureLoadTester : SingleDeviceFactory { readonly BehaviorSubject frameHz = new(1000); + /// + /// Initializes a new instance of the class. + /// public ConfigureLoadTester() : base(typeof(LoadTester)) { } + /// + /// Gets or sets a value specifying whether the load testing device is enabled. + /// + [Category(ConfigurationCategory)] + [Description("Specifies whether the load testing device is enabled.")] + public bool Enable { get; set; } = false; + + /// + /// Gets or sets the number of repetitions of the 16-bit unsigned integer 42 sent with each read-frame. + /// [Category(ConfigurationCategory)] [Description("Number of repetitions of the 16-bit unsigned integer 42 sent with each read-frame.")] [Range(0, 10e6)] public uint ReceivedWords { get; set; } + /// + /// Gets or sets the number of repetitions of the 32-bit integer 42 sent with each write frame. + /// [Category(ConfigurationCategory)] [Description("Number of repetitions of the 32-bit integer 42 sent with each write frame.")] [Range(0, 10e6)] public uint TransmittedWords { get; set; } + /// + /// Gets or sets a value specifying the rate at which frames are produced, in Hz. + /// [Category(AcquisitionCategory)] - [Description("Specifies the rate at which frames are produced.")] - public uint FrameHz + [Description("Specifies the rate at which frames are produced (Hz).")] + public uint FramesPerSecond { get { return frameHz.Value; } set { frameHz.OnNext(value); } } + /// + /// Configures a load testing device. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that hold configuration actions. + /// + /// The original sequence modified by adding additional configuration actions required to configure + /// a load testing device. + /// public override IObservable Process(IObservable source) { + var enable = Enable; var deviceName = DeviceName; var deviceAddress = DeviceAddress; var receivedWords = ReceivedWords; var transmittedWords = TransmittedWords; - return source.ConfigureDevice(context => + return source.ConfigureDevice((context, observer) => { - var device = context.GetDeviceContext(deviceAddress, LoadTester.ID); - device.WriteRegister(LoadTester.ENABLE, 1); + var device = context.GetDeviceContext(deviceAddress, DeviceType); + device.WriteRegister(LoadTester.ENABLE, enable ? 1u : 0u); + + var clockHz = device.ReadRegister(LoadTester.CLK_HZ); - var clk_hz = device.ReadRegister(LoadTester.CLK_HZ); // Assumes 8-byte timer uint ValidSize() { - var clk_div = device.ReadRegister(LoadTester.CLK_DIV); - return clk_div - 4 - 10; // -10 is overhead hack + var clkDiv = device.ReadRegister(LoadTester.CLK_DIV); + return clkDiv - 4 - 10; // -10 is overhead hack } - var max_size = ValidSize(); - var bounded = receivedWords > max_size ? max_size : receivedWords; + var maxSize = ValidSize(); + var bounded = receivedWords > maxSize ? maxSize : receivedWords; device.WriteRegister(LoadTester.DT0H16_WORDS, bounded); var writeArray = Enumerable.Repeat((uint)42, (int)(transmittedWords + 2)).ToArray(); device.WriteRegister(LoadTester.HTOD32_WORDS, transmittedWords); - var frameHzSubscription = frameHz.Subscribe(newValue => + var frameHzSubscription = frameHz.SubscribeSafe(observer, newValue => { - device.WriteRegister(LoadTester.CLK_DIV, clk_hz / newValue); - var max_size = ValidSize(); - if (receivedWords > max_size) + device.WriteRegister(LoadTester.CLK_DIV, clockHz / newValue); + var maxSize = ValidSize(); + if (receivedWords > maxSize) { - receivedWords = max_size; + receivedWords = maxSize; } }); diff --git a/OpenEphys.Onix1/ConfigureMemoryMonitor.cs b/OpenEphys.Onix1/ConfigureMemoryMonitor.cs new file mode 100644 index 00000000..037eea9d --- /dev/null +++ b/OpenEphys.Onix1/ConfigureMemoryMonitor.cs @@ -0,0 +1,95 @@ +using System; +using System.ComponentModel; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class for configuring a hardware memory monitor device. + /// + /// + /// The memory monitor produces periodic snapshots of the system's first in, first out (FIFO) data buffer. + /// This can be useful for: + /// + /// Ensuring that data is being read by the host PC quickly enough to prevent real-time delays or overflows. + /// In the case that the PC is not keeping up with data collection, FIFO memory use will increase monotonically. + /// Tuning the value of to optimize real-time performance. + /// For optimal real-time performance, should be as small as possible and the FIFO should be bypassed + /// (memory usage should remain at 0). However, these requirements are in conflict. The memory monitor provides a way to find the minimal value of + /// value of that does not result in excessive FIFO data buffering. This tradeoff will depend on the + /// bandwidth of data being acquired, the performance of the host PC, and downstream real-time processing. + /// + /// + [Description("Configures a hardware memory monitor device.")] + public class ConfigureMemoryMonitor : SingleDeviceFactory + { + /// + /// Initialize a new instance of . + /// + public ConfigureMemoryMonitor() + : base(typeof(MemoryMonitor)) + { + DeviceAddress = 10; + } + + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, will produce data. If set to false, will not produce data. + /// + [Category(ConfigurationCategory)] + [Description("Specifies whether the memory monitor device is enabled.")] + public bool Enable { get; set; } = false; + + /// + /// Gets or sets the frequency at which memory use is recorded in Hz. + /// + [Range(1, 1000)] + [Category(ConfigurationCategory)] + [Description("Frequency at which memory use is recorded (Hz).")] + public uint SamplesPerSecond { get; set; } = 10; + + /// + /// Configures a memory monitor device. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that holds configuration actions. + /// The original sequence modified by adding additional configuration actions required to configure a memory monitor device./> + public override IObservable Process(IObservable source) + { + var enable = Enable; + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + var samplesPerSecond = SamplesPerSecond; + return source.ConfigureDevice(context => + { + var device = context.GetDeviceContext(deviceAddress, DeviceType); + device.WriteRegister(MemoryMonitor.ENABLE, enable ? 1u : 0u); + device.WriteRegister(MemoryMonitor.CLK_DIV, device.ReadRegister(MemoryMonitor.CLK_HZ) / samplesPerSecond); + return DeviceManager.RegisterDevice(deviceName, device, DeviceType); + }); + } + } + + static class MemoryMonitor + { + public const int ID = 28; + + public const uint ENABLE = 0; // Enable the monitor + public const uint CLK_DIV = 1; // Sample clock divider ratio. Values less than CLK_HZ / 10e6 Hz will result in 1kHz. + public const uint CLK_HZ = 2; // The frequency parameter, CLK_HZ, used in the calculation of CLK_DIV + public const uint TOTAL_MEM = 3; // Total available memory in 32-bit words + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(MemoryMonitor)) + { + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV1e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs similarity index 68% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV1e.cs rename to OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs index c799a73c..578bb8d7 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV1e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs @@ -4,49 +4,124 @@ using System.Threading; using Bonsai; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { + /// + /// A class that configures a NeuropixelsV1e device. + /// + [Description("Configures a NeuropixelsV1e device.")] public class ConfigureNeuropixelsV1e : SingleDeviceFactory { + /// + /// Initialize a new instance of a class. + /// public ConfigureNeuropixelsV1e() : base(typeof(NeuropixelsV1e)) { } + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, will produce data. If set to false, + /// will not produce data. + /// [Category(ConfigurationCategory)] [Description("Specifies whether the Neuropixels data stream is enabled.")] public bool Enable { get; set; } = true; + /// + /// Gets or sets the LED enable state. + /// + /// + /// If true, the headstage LED will turn on during data acquisition. If false, the LED will not turn on. + /// [Category(ConfigurationCategory)] - [Description("If true, the headstage LED will illuminate during acquisition. Otherwise it will remain off.")] + [Description("If true, the headstage LED will turn on during data acquisition. If false, the LED will not turn on.")] public bool EnableLed { get; set; } = true; + /// + /// Gets or sets the amplifier gain for the spike-band. + /// + /// + /// The spike-band is from DC to 10 kHz if is set to false, while the + /// spike-band is from 300 Hz to 10 kHz if is set to true. + /// [Category(ConfigurationCategory)] [Description("Amplifier gain for spike-band.")] public NeuropixelsV1Gain SpikeAmplifierGain { get; set; } = NeuropixelsV1Gain.Gain1000; + /// + /// Gets or sets the amplifier gain for the LFP-band. + /// + /// + /// The LFP band is from 0.5 to 500 Hz. + /// [Category(ConfigurationCategory)] [Description("Amplifier gain for LFP-band.")] public NeuropixelsV1Gain LfpAmplifierGain { get; set; } = NeuropixelsV1Gain.Gain50; + /// + /// Gets or sets the reference for all electrodes. + /// + /// + /// All electrodes are set to the same reference, which can be either + /// or . + /// Setting to will use the external reference, while + /// sets the reference to the electrode at the tip of the probe. + /// [Category(ConfigurationCategory)] [Description("Reference selection.")] public NeuropixelsV1ReferenceSource Reference { get; set; } = NeuropixelsV1ReferenceSource.External; + /// + /// Gets or sets the state of the spike-band filter. + /// + /// + /// If set to true, the spike-band has a 300 Hz high-pass filter which will be activated. If set to + /// false, the high-pass filter will not to be activated. + /// [Category(ConfigurationCategory)] [Description("If true, activates a 300 Hz high-pass filter in the spike-band data stream.")] public bool SpikeFilter { get; set; } = true; + /// + /// Gets or sets the path to the gain calibration file. + /// + /// + /// Each probe must be provided with a gain calibration file that contains calibration data + /// specific to each probe. This file is mandatory for accurate recordings. + /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the Neuropixels 1.0 gain calibration file.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] public string GainCalibrationFile { get; set; } + /// + /// Gets or sets the path to the ADC calibration file. + /// + /// + /// Each probe must be provided with an ADC calibration file that contains calibration data + /// specific to each probe. This file is mandatory for accurate recordings. + /// [FileNameFilter("ADC calibration files (*_ADCCalibration.csv)|*_ADCCalibration.csv")] [Description("Path to the Neuropixels 1.0 ADC calibration file.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] public string AdcCalibrationFile { get; set; } + /// + /// Configures a NeuropixelsV1e device. + /// + /// + /// This will schedule configuration actions to be applied by a node + /// prior to data acquisition. + /// + /// A sequence of that holds all configuration actions. + /// + /// The original sequence with the side effect of an additional configuration action to configure + /// a NeuropixelsV1e device. + /// public override IObservable Process(IObservable source) { var enable = Enable; @@ -56,7 +131,7 @@ public override IObservable Process(IObservable source return source.ConfigureDevice(context => { // configure device via the DS90UB9x deserializer device - var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID); + var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); // configure deserializer aliases and serializer power supply @@ -87,15 +162,14 @@ public override IObservable Process(IObservable source } var deviceInfo = new NeuropixelsV1eDeviceInfo(context, DeviceType, deviceAddress, probeControl); - var disposable = DeviceManager.RegisterDevice(deviceName, deviceInfo); var shutdown = Disposable.Create(() => { serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, NeuropixelsV1e.DefaultGPO10Config); serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, NeuropixelsV1e.DefaultGPO32Config); }); return new CompositeDisposable( - shutdown, - disposable); + DeviceManager.RegisterDevice(deviceName, deviceInfo), + shutdown); }); } @@ -165,7 +239,7 @@ static class NeuropixelsV1e public const int ChannelCount = 384; public const int FrameWords = 40; - // unmanaged regiseters + // unmanaged registers public const uint OP_MODE = 0X00; public const uint REC_MOD = 0X01; public const uint CAL_MOD = 0X02; @@ -235,21 +309,57 @@ enum NeuropixelsV1OperationRegisterValues : uint RECORD_AND_CALIBRATE = RECORD | CALIBRATE, }; + /// + /// Specifies the reference source for all electrodes. + /// public enum NeuropixelsV1ReferenceSource : byte { + /// + /// Specifies that the reference should be External. + /// External = 0b001, + /// + /// Specifies that the reference should be the Tip. + /// Tip = 0b010 } + /// + /// Specifies the gain for all electrodes + /// public enum NeuropixelsV1Gain : byte { + /// + /// Specifies that the gain should be 50x. + /// Gain50 = 0b000, + /// + /// Specifies that the gain should be 125x. + /// Gain125 = 0b001, + /// + /// Specifies that the gain should be 250x. + /// Gain250 = 0b010, + /// + /// Specifies that the gain should be 500x. + /// Gain500 = 0b011, + /// + /// Specifies that the gain should be 1000x. + /// Gain1000 = 0b100, + /// + /// Specifies that the gain should be 1500x. + /// Gain1500 = 0b101, + /// + /// Specifies that the gain should be 2000x. + /// Gain2000 = 0b110, + /// + /// Specifies that the gain should be 3000x. + /// Gain3000 = 0b111 } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV1eBno055.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1eBno055.cs similarity index 66% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV1eBno055.cs rename to OpenEphys.Onix1/ConfigureNeuropixelsV1eBno055.cs index 55b6ebe3..b5cbb9e9 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV1eBno055.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1eBno055.cs @@ -1,19 +1,45 @@ using System; using System.ComponentModel; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { + /// + /// A class that configures a NeuropixelsV1eBno055 device. + /// + [Description("Configures a NeuropixelsV1eBno055 device.")] public class ConfigureNeuropixelsV1eBno055 : SingleDeviceFactory { + /// + /// Initialize a new instance of a class. + /// public ConfigureNeuropixelsV1eBno055() : base(typeof(NeuropixelsV1eBno055)) { } + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, will produce data. If set to false, + /// will not produce data. + /// [Category(ConfigurationCategory)] [Description("Specifies whether the BNO055 device is enabled.")] public bool Enable { get; set; } = true; + /// + /// Configures a NeuropixelsV1eBno055 device. + /// + /// + /// This will schedule configuration actions to be applied by a node + /// prior to data acquisition. + /// + /// A sequence of that holds all configuration actions. + /// + /// The original sequence with the side effect of an additional configuration action to configure + /// a NeuropixelsV1eBno055 device. + /// public override IObservable Process(IObservable source) { var enable = Enable; @@ -22,7 +48,7 @@ public override IObservable Process(IObservable source return source.ConfigureDevice(context => { // configure device via the DS90UB9x deserializer device - var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID); + var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); ConfigureDeserializer(device); ConfigureBno055(device); var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV1eHeadstage.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs similarity index 54% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV1eHeadstage.cs rename to OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs index bb75b6e4..df88662e 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV1eHeadstage.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs @@ -2,27 +2,49 @@ using System.ComponentModel; using System.Threading; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - public class ConfigureNeuropixelsV1eHeadstage : HubDeviceFactory + /// + /// A class that configures a NeuropixelsV1e headstage. + /// + [Description("Configures a NeuropixelsV1e headstage.")] + public class ConfigureNeuropixelsV1eHeadstage : MultiDeviceFactory { PortName port; - readonly ConfigureNeuropixelsV1LinkController LinkController = new(); + readonly ConfigureNeuropixelsV1eLinkController LinkController = new(); + /// + /// Initialize a new instance of a class. + /// public ConfigureNeuropixelsV1eHeadstage() { Port = PortName.PortA; LinkController.HubConfiguration = HubConfiguration.Passthrough; } + /// + /// Gets or sets the NeuropixelsV1e configuration. + /// [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureNeuropixelsV1e NeuropixelsV1 { get; set; } = new(); + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the NeuropixelsV1e device.")] + public ConfigureNeuropixelsV1e NeuropixelsV1e { get; set; } = new(); + /// + /// Gets or sets the Bno055 9-axis inertial measurement unit configuration. + /// [Category(ConfigurationCategory)] - [TypeConverter(typeof(HubDeviceConverter))] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the Bno055 device.")] public ConfigureNeuropixelsV1eBno055 Bno055 { get; set; } = new(); + /// + /// Gets or sets the port. + /// + /// + /// The port is the physical connection to the ONIX breakout board and must be specified prior to operation. + /// + [Description("Specifies the physical connection of the headstage to the ONIX breakout board.")] public PortName Port { get { return port; } @@ -31,11 +53,20 @@ public PortName Port port = value; var offset = (uint)port << 8; LinkController.DeviceAddress = (uint)port; - NeuropixelsV1.DeviceAddress = offset + 0; + NeuropixelsV1e.DeviceAddress = offset + 0; Bno055.DeviceAddress = offset + 1; } } + /// + /// Gets or sets the port voltage. + /// + /// + /// If a port voltage is defined this will override the automated voltage discovery and applies + /// the specified voltage to the headstage. To enable automated voltage discovery, leave this field + /// empty. Warning: This device requires 3.8V to 5.0V for proper operation. Voltages higher than 5.0V can + /// damage the headstage + /// [Description("If defined, overrides automated voltage discovery and applies " + "the specified voltage to the headstage. Warning: this device requires 3.8V to 5.0V " + "for proper operation. Higher voltages can damage the headstage.")] @@ -48,11 +79,11 @@ public double? PortVoltage internal override IEnumerable GetDevices() { yield return LinkController; - yield return NeuropixelsV1; + yield return NeuropixelsV1e; yield return Bno055; } - class ConfigureNeuropixelsV1LinkController : ConfigureFmcLinkController + class ConfigureNeuropixelsV1eLinkController : ConfigureFmcLinkController { protected override bool ConfigurePortVoltage(DeviceContext device) { diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs similarity index 82% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs rename to OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index 3b6360e0..0c4a23b4 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -3,11 +3,18 @@ using System.Reactive.Disposables; using Bonsai; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - [Editor("OpenEphys.Onix.Design.NeuropixelsV2eEditor, OpenEphys.Onix.Design", typeof(ComponentEditor))] + /// + /// A class that configures a NeuropixelsV2e device. + /// + [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eEditor, OpenEphys.Onix1.Design", typeof(ComponentEditor))] + [Description("Configures a NeuropixelsV2e device.")] public class ConfigureNeuropixelsV2e : SingleDeviceFactory { + /// + /// Initialize a new instance of a class. + /// public ConfigureNeuropixelsV2e() : base(typeof(NeuropixelsV2e)) { @@ -25,28 +32,67 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) DeviceAddress = configureNode.DeviceAddress; } + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, will produce data. If set to false, + /// will not produce data. + /// [Category(ConfigurationCategory)] [Description("Specifies whether the NeuropixelsV2 device is enabled.")] public bool Enable { get; set; } = true; + /// + /// Gets or sets the electrode configuration for Probe A. + /// [Category(ConfigurationCategory)] [Description("Probe A electrode configuration.")] public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new(); + /// + /// Gets or sets the path to the gain calibration file for Probe A. + /// + /// + /// Each probe must be provided with a gain calibration file that contains calibration data + /// specific to each probe. This file is mandatory for accurate recordings. + /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe A.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] public string GainCalibrationFileA { get; set; } + /// + /// Gets or sets the electrode configuration for Probe B. + /// [Category(ConfigurationCategory)] [Description("Probe B electrode configuration.")] public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new(); + /// + /// Gets or sets the path to the gain calibration file for Probe B. + /// + /// + /// Each probe must be provided with a gain calibration file that contains calibration data + /// specific to each probe. This file is mandatory for accurate recordings. + /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe B.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] public string GainCalibrationFileB { get; set; } + /// + /// Configures a NeuropixelsV2e device. + /// + /// + /// This will schedule configuration actions to be applied by a node + /// prior to data acquisition. + /// + /// A sequence of that holds all configuration actions. + /// + /// The original sequence with the side effect of an additional configuration action to configure + /// a NeuropixelsV2e device. + /// public override IObservable Process(IObservable source) { var enable = Enable; @@ -55,7 +101,7 @@ public override IObservable Process(IObservable source return source.ConfigureDevice(context => { // configure device via the DS90UB9x deserializer device - var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID); + var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); // configure deserializer aliases and serializer power supply @@ -100,15 +146,14 @@ public override IObservable Process(IObservable source } var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB); - var disposable = DeviceManager.RegisterDevice(deviceName, deviceInfo); var shutdown = Disposable.Create(() => { serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, NeuropixelsV2e.DefaultGPO10Config); SelectProbe(serializer, NeuropixelsV2e.NoProbeSelected); }); return new CompositeDisposable( - shutdown, - disposable); + DeviceManager.RegisterDevice(deviceName, deviceInfo), + shutdown); }); } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs similarity index 82% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs rename to OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index 1e3e82af..bcbcf602 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -3,41 +3,90 @@ using System.Reactive.Disposables; using Bonsai; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { + /// + /// A class that configures a NeuropixelsV2eBeta device. + /// + [Description("Configures a NeuropixelsV2eBeta device.")] public class ConfigureNeuropixelsV2eBeta : SingleDeviceFactory { + /// + /// Initialize a new instance of a class. + /// public ConfigureNeuropixelsV2eBeta() : base(typeof(NeuropixelsV2eBeta)) { } + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, will produce data. If set to false, + /// will not produce data. + /// [Category(ConfigurationCategory)] [Description("Specifies whether the NeuropixelsV2Beta device is enabled.")] public bool Enable { get; set; } = true; + /// + /// Gets or sets the LED enable state. + /// + /// + /// If true, the headstage LED will turn on during data acquisition. If false, the LED will not turn on. + /// [Category(ConfigurationCategory)] - [Description("Enable headstage LED when acquiring data.")] + [Description("If true, the headstage LED will turn on during data acquisition. If false, the LED will not turn on.")] public bool EnableLed { get; set; } = true; + /// + /// Gets or sets the electrode configuration for Probe A. + /// [Category(ConfigurationCategory)] [Description("Probe A electrode configuration.")] public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(); + /// + /// Gets or sets the path to the gain calibration file for Probe A. + /// + /// + /// Each probe must be provided with a gain calibration file that contains calibration data + /// specific to each probe. This file is mandatory for accurate recordings. + /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe A.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] public string GainCalibrationFileA { get; set; } + /// + /// Gets or sets the electrode configuration for Probe B. + /// [Category(ConfigurationCategory)] [Description("Probe B electrode configuration.")] public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(); + /// + /// Gets or sets the path to the gain calibration file for Probe B. + /// + /// + /// Each probe must be provided with a gain calibration file that contains calibration data + /// specific to each probe. This file is mandatory for accurate recordings. + /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe B.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] public string GainCalibrationFileB { get; set; } + /// + /// Configures a NeuropixelsV2eBeta device. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that holds configuration actions. + /// The original sequence modified by adding additional configuration actions required to configure a NeuropixelsV2eBeta device./> public override IObservable Process(IObservable source) { var enable = Enable; @@ -46,7 +95,7 @@ public override IObservable Process(IObservable source return source.ConfigureDevice(context => { // configure device via the DS90UB9x deserializer device - var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID); + var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); // configure deserializer aliases and serializer power supply @@ -115,15 +164,14 @@ public override IObservable Process(IObservable source SyncProbes(serializer, gpo10Config); var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB); - var disposable = DeviceManager.RegisterDevice(deviceName, deviceInfo); var shutdown = Disposable.Create(() => { serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, NeuropixelsV2eBeta.DefaultGPO10Config); serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, NeuropixelsV2eBeta.DefaultGPO32Config); }); return new CompositeDisposable( - shutdown, - disposable); + DeviceManager.RegisterDevice(deviceName, deviceInfo), + shutdown); }); } diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs new file mode 100644 index 00000000..9c3c3b22 --- /dev/null +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that configures a NeuropixelsV2eBeta headstage. + /// + [Description("Configures a NeuropixelsV2eBeta headstage.")] + public class ConfigureNeuropixelsV2eBetaHeadstage : MultiDeviceFactory + { + PortName port; + readonly ConfigureNeuropixelsV2eLinkController LinkController = new(); + + /// + /// Initialize a new instance of a class. + /// + public ConfigureNeuropixelsV2eBetaHeadstage() + { + Port = PortName.PortA; + LinkController.HubConfiguration = HubConfiguration.Passthrough; + } + + /// + /// Gets or sets the NeuropixelsV2eBeta configuration. + /// + [Category(ConfigurationCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the NeuropixelsV2eBeta device.")] + public ConfigureNeuropixelsV2eBeta NeuropixelsV2eBeta { get; set; } = new(); + + /// + /// Gets or sets the Bno055 9-axis inertial measurement unit configuration. + /// + [Category(ConfigurationCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the Bno055 device.")] + public ConfigureNeuropixelsV2eBno055 Bno055 { get; set; } = new(); + + /// + /// Gets or sets the port. + /// + /// + /// The port is the physical connection to the ONIX breakout board and must be specified prior to operation. + /// + [Description("Specifies the physical connection of the headstage to the ONIX breakout board.")] + public PortName Port + { + get { return port; } + set + { + port = value; + var offset = (uint)port << 8; + LinkController.DeviceAddress = (uint)port; + NeuropixelsV2eBeta.DeviceAddress = offset + 0; + Bno055.DeviceAddress = offset + 1; + } + } + + /// + /// Gets or sets the port voltage. + /// + /// + /// If a port voltage is defined this will override the automated voltage discovery and applies + /// the specified voltage to the headstage. To enable automated voltage discovery, leave this field + /// empty. Warning: This device requires 3.0V to 5.0V for proper operation. Voltages higher than 5.0V can + /// damage the headstage + /// + [Description("If defined, overrides automated voltage discovery and applies " + + "the specified voltage to the headstage. Warning: this device requires 3.0V to 5.0V " + + "for proper operation. Higher voltages can damage the headstage.")] + public double? PortVoltage + { + get => LinkController.PortVoltage; + set => LinkController.PortVoltage = value; + } + + internal override IEnumerable GetDevices() + { + yield return LinkController; + yield return NeuropixelsV2eBeta; + yield return Bno055; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs similarity index 67% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs rename to OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs index a82fd7f6..94125359 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBno055.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs @@ -1,11 +1,18 @@ using System; using System.ComponentModel; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - [Editor("OpenEphys.Onix.Design.NeuropixelsV2eBno055Editor, OpenEphys.Onix.Design", typeof(ComponentEditor))] + /// + /// A class that configures a NeuropixelsV2eBno055 device. + /// + [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eBno055Editor, OpenEphys.Onix1.Design", typeof(ComponentEditor))] + [Description("Configures a NeuropixelsV2eBno055 device.")] public class ConfigureNeuropixelsV2eBno055 : SingleDeviceFactory { + /// + /// Initialize a new instance of a class. + /// public ConfigureNeuropixelsV2eBno055() : base(typeof(NeuropixelsV2eBno055)) { @@ -19,10 +26,29 @@ public ConfigureNeuropixelsV2eBno055(ConfigureNeuropixelsV2eBno055 configureNeur DeviceAddress = configureNeuropixelsV2eBno055.DeviceAddress; } + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, will produce data. If set to false, + /// will not produce data. + /// [Category(ConfigurationCategory)] [Description("Specifies whether the BNO055 device is enabled.")] public bool Enable { get; set; } = true; + /// + /// Configures a NeuropixelsV2eBno055 device. + /// + /// + /// This will schedule configuration actions to be applied by a node + /// prior to data acquisition. + /// + /// A sequence of that holds all configuration actions. + /// + /// The original sequence with the side effect of an additional configuration action to configure + /// a NeuropixelsV2eBno055 device. + /// public override IObservable Process(IObservable source) { var enable = Enable; @@ -31,7 +57,7 @@ public override IObservable Process(IObservable source return source.ConfigureDevice(context => { // configure device via the DS90UB9x deserializer device - var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID); + var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); ConfigureDeserializer(device); ConfigureBno055(device); var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs new file mode 100644 index 00000000..f77b1734 --- /dev/null +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that configures a NeuropixelsV2e headstage. + /// + [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eHeadstageEditor, OpenEphys.Onix1.Design", typeof(ComponentEditor))] + [Description("configures a NeuropixelsV2e headstage.")] + public class ConfigureNeuropixelsV2eHeadstage : MultiDeviceFactory + { + PortName port; + readonly ConfigureNeuropixelsV2eLinkController LinkController = new(); + + /// + /// Initialize a new instance of a class. + /// + public ConfigureNeuropixelsV2eHeadstage() + { + Port = PortName.PortA; + LinkController.HubConfiguration = HubConfiguration.Passthrough; + } + + /// + /// Gets or sets the NeuropixelsV2e configuration. + /// + [Category(ConfigurationCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the NeuropixelsV2e device.")] + public ConfigureNeuropixelsV2e NeuropixelsV2e { get; set; } = new(); + + /// + /// Gets or sets the Bno055 9-axis inertial measurement unit configuration. + /// + [Category(ConfigurationCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the Bno055 device.")] + public ConfigureNeuropixelsV2eBno055 Bno055 { get; set; } = new(); + + /// + /// Gets or sets the port. + /// + /// + /// The port is the physical connection to the ONIX breakout board and must be specified prior to operation. + /// + [Description("Specifies the physical connection of the headstage to the ONIX breakout board.")] + public PortName Port + { + get { return port; } + set + { + port = value; + var offset = (uint)port << 8; + LinkController.DeviceAddress = (uint)port; + NeuropixelsV2e.DeviceAddress = offset + 0; + Bno055.DeviceAddress = offset + 1; + } + } + + /// + /// Gets or sets the port voltage. + /// + /// + /// If a port voltage is defined this will override the automated voltage discovery and applies + /// the specified voltage to the headstage. To enable automated voltage discovery, leave this field + /// empty. Warning: This device requires 3.0V to 5.5V for proper operation. Voltages higher than 5.5V can + /// damage the headstage + /// + [Description("If defined, overrides automated voltage discovery and applies " + + "the specified voltage to the headstage. Warning: this device requires 3.0V to 5.5V " + + "for proper operation. Higher voltages can damage the headstage.")] + public double? PortVoltage + { + get => LinkController.PortVoltage; + set => LinkController.PortVoltage = value; + } + + internal override IEnumerable GetDevices() + { + yield return LinkController; + yield return NeuropixelsV2e; + yield return Bno055; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eLinkController.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eLinkController.cs similarity index 97% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eLinkController.cs rename to OpenEphys.Onix1/ConfigureNeuropixelsV2eLinkController.cs index 5f55c800..86cce964 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eLinkController.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eLinkController.cs @@ -1,6 +1,6 @@ using System.Threading; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class ConfigureNeuropixelsV2eLinkController : ConfigureFmcLinkController { diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureRhd2164.cs b/OpenEphys.Onix1/ConfigureRhd2164.cs similarity index 69% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureRhd2164.cs rename to OpenEphys.Onix1/ConfigureRhd2164.cs index 76929557..3e106e4b 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureRhd2164.cs +++ b/OpenEphys.Onix1/ConfigureRhd2164.cs @@ -1,35 +1,67 @@ using System; using System.ComponentModel; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { + /// + /// A class for configuring an Intan RHD2164 bioamplifier chip. + /// + /// + /// This configuration class can be linked to a instance to stream + /// electrophysiology data from the chip. + /// + [Description("Configures a RHD2164 device.")] public class ConfigureRhd2164 : SingleDeviceFactory { + /// + /// Initializes a new instance of the class. + /// public ConfigureRhd2164() : base(typeof(Rhd2164)) { } + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, a instance that is linked to this configuration will produce data. + /// If set to false, it will not produce data. + /// [Category(ConfigurationCategory)] [Description("Specifies whether the RHD2164 device is enabled.")] public bool Enable { get; set; } = true; + /// + /// Gets or sets the cutoff frequency for the digital (post-ADC) high-pass filter used for amplifier offset removal. + /// [Category(ConfigurationCategory)] - [Description("Specifies the raw ADC output format used for amplifier conversions.")] - public Rhd2164AmplifierDataFormat AmplifierDataFormat { get; set; } - - [Category(ConfigurationCategory)] - [Description("Specifies the cutoff frequency for the DSP high-pass filter used for amplifier offset removal.")] + [Description("Specifies the cutoff frequency for the digital (post-ADC) high-pass filter used for amplifier offset removal.")] public Rhd2164DspCutoff DspCutoff { get; set; } = Rhd2164DspCutoff.Dsp146mHz; + /// + /// Gets or sets the low cutoff frequency of the analog (pre-ADC) bandpass filter. + /// [Category(ConfigurationCategory)] - [Description("Specifies the lower cutoff frequency of the pre-ADC amplifiers.")] + [Description("Specifies the low cutoff frequency of the analog (pre-ADC) bandpass filter.")] public Rhd2164AnalogLowCutoff AnalogLowCutoff { get; set; } = Rhd2164AnalogLowCutoff.Low100mHz; + /// + /// Gets or sets the high cutoff frequency of the analog (pre-ADC) bandpass filter. + /// [Category(ConfigurationCategory)] - [Description("Specifies the upper cutoff frequency of the pre-ADC amplifiers.")] + [Description("Specifies the high cutoff frequency of the analog (pre-ADC) bandpass filter.")] public Rhd2164AnalogHighCutoff AnalogHighCutoff { get; set; } = Rhd2164AnalogHighCutoff.High10000Hz; + /// + /// Configures a RHD2164 device. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that holds configuration actions. + /// The original sequence modified by adding additional configuration actions required to configure a RHD2164 device. public override IObservable Process(IObservable source) { var enable = Enable; @@ -39,12 +71,10 @@ public override IObservable Process(IObservable source { // config register format following RHD2164 datasheet // https://intantech.com/files/Intan_RHD2000_series_datasheet.pdf - var device = context.GetDeviceContext(deviceAddress, Rhd2164.ID); + var device = context.GetDeviceContext(deviceAddress, DeviceType); var format = device.ReadRegister(Rhd2164.FORMAT); - var amplifierDataFormat = AmplifierDataFormat; - format &= ~(1u << 6); - format |= (uint)amplifierDataFormat << 6; + format &= ~(1u << 6); // hard-code amplifier data format to offset binary var dspCutoff = DspCutoff; if (dspCutoff == Rhd2164DspCutoff.Off) @@ -58,8 +88,8 @@ public override IObservable Process(IObservable source format |= (uint)dspCutoff; } - var highCutoff = Rhd2164Config.AnalogHighCutoffToRegisters[AnalogHighCutoff]; - var lowCutoff = Rhd2164Config.AnalogLowCutoffToRegisters[AnalogLowCutoff]; + var highCutoff = Rhd2164Config.ToHighCutoffToRegisters(AnalogHighCutoff); + var lowCutoff = Rhd2164Config.ToLowCutoffToRegisters(AnalogLowCutoff); var bw0 = device.ReadRegister(Rhd2164.BW0); var bw1 = device.ReadRegister(Rhd2164.BW1); var bw2 = device.ReadRegister(Rhd2164.BW2); diff --git a/OpenEphys.Onix1/ConfigureTS4231V1.cs b/OpenEphys.Onix1/ConfigureTS4231V1.cs new file mode 100644 index 00000000..46603e90 --- /dev/null +++ b/OpenEphys.Onix1/ConfigureTS4231V1.cs @@ -0,0 +1,73 @@ +using System; +using System.ComponentModel; + +namespace OpenEphys.Onix1 +{ + /// + /// A class for configuring an array of Triad Semiconductor TS4231 lighthouse receivers for 3D position tracking using + /// a pair of SteamVR V1 base stations. + /// + /// + /// This configuration class can be linked to a instance to stream 3D position data from + /// light-house receivers when SteamVR V1 base stations have been installed above the arena. + /// + [Description("Configures a TS4231 receiver array.")] + public class ConfigureTS4231V1 : SingleDeviceFactory + { + /// + /// Initializes a new instance of the class. + /// + public ConfigureTS4231V1() + : base(typeof(TS4231V1)) + { + } + + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, a instance that is linked to this configuration will produce data. If set to false, + /// it will not produce data. + /// + [Category(ConfigurationCategory)] + [Description("Specifies whether the TS4231 device is enabled.")] + public bool Enable { get; set; } = true; + + /// + /// Configures a TS4231 receiver array. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that holds configuration actions. + /// The original sequence modified by adding additional configuration actions required to configure a TS4231 array. + public override IObservable Process(IObservable source) + { + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + var device = context.GetDeviceContext(deviceAddress, DeviceType); + device.WriteRegister(TS4231V1.ENABLE, Enable ? 1u : 0); + return DeviceManager.RegisterDevice(deviceName, device, DeviceType); + }); + } + } + + static class TS4231V1 + { + public const int ID = 25; + + // managed registers + public const uint ENABLE = 0x0; // Enable or disable the data output stream + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(TS4231V1)) + { + } + } + } +} diff --git a/OpenEphys.Onix1/ContextHelper.cs b/OpenEphys.Onix1/ContextHelper.cs new file mode 100644 index 00000000..eb394b03 --- /dev/null +++ b/OpenEphys.Onix1/ContextHelper.cs @@ -0,0 +1,72 @@ +using System; +using System.Reflection; +using oni; + +namespace OpenEphys.Onix1 +{ + static class ContextHelper + { + public static DeviceContext GetDeviceContext(this ContextTask context, uint address, Type expectedType) + { + if (!context.DeviceTable.TryGetValue(address, out Device device)) + { + ThrowDeviceNotFoundException(expectedType, address); + } + + if (device.ID != GetDeviceID(expectedType)) + { + ThrowInvalidDeviceException(expectedType, address); + } + + return new DeviceContext(context, device); + } + + public static DeviceContext GetDeviceContext(this DeviceInfo deviceInfo, Type expectedType) + { + deviceInfo.AssertType(expectedType); + if (!deviceInfo.Context.DeviceTable.TryGetValue(deviceInfo.DeviceAddress, out Device device)) + { + ThrowDeviceNotFoundException(expectedType, deviceInfo.DeviceAddress); + } + + return new DeviceContext(deviceInfo.Context, device); + } + + public static DeviceContext GetPassthroughDeviceContext(this ContextTask context, uint address, Type expectedType) + { + var passthroughDeviceAddress = context.GetPassthroughDeviceAddress(address); + return GetDeviceContext(context, passthroughDeviceAddress, expectedType); + } + + public static DeviceContext GetPassthroughDeviceContext(this DeviceContext device, Type expectedType) + { + return GetPassthroughDeviceContext(device.Context, device.Address, expectedType); + } + + static int GetDeviceID(Type deviceType) + { + var fieldInfo = deviceType.GetField( + "ID", + BindingFlags.Static | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.IgnoreCase); + if (fieldInfo == null || !fieldInfo.IsLiteral) + { + throw new ArgumentException($"The specified device type {deviceType} does not have a const ID field.", nameof(deviceType)); + } + + return (int)fieldInfo.GetRawConstantValue(); + } + + static void ThrowDeviceNotFoundException(Type expectedType, uint address) + { + throw new InvalidOperationException($"Device '{expectedType.Name}' was not found in the device table at address {address}."); + } + + static void ThrowInvalidDeviceException(Type expectedType, uint address) + { + throw new InvalidOperationException($"Invalid device ID. The device found at address {address} is not a '{expectedType.Name}' device."); + } + } +} diff --git a/OpenEphys.Onix1/ContextTask.cs b/OpenEphys.Onix1/ContextTask.cs new file mode 100644 index 00000000..1cdc27c6 --- /dev/null +++ b/OpenEphys.Onix1/ContextTask.cs @@ -0,0 +1,524 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenEphys.Onix1 +{ + /// + /// Encapsulates an and orchestrates interaction with ONI hardware. + /// + /// + /// This class forms the basis for ONI hardware interaction within the library. It manages an . It + /// reads and distributes s using a dedicated acquisition thread. It allows s to + /// be written to devices that accept them. Finally, it exposes information about the underlying ONI hardware such as the device + /// table, clock rates, and block read and write sizes. + /// + public class ContextTask : IDisposable + { + oni.Context ctx; + + /// + /// Maximum amount of frames the reading queue will hold. If the queue fills or the read + /// thread is not performant enough to fill it faster than data is produced, frame reading + /// will throttle, filling host memory instead of user space memory. + /// + const int MaxQueuedFrames = 2_000_000; + + /// + /// Timeout in ms for queue reads. This should not be critical as the read operation will + /// cancel if the task is stopped + /// + const int QueueTimeoutMilliseconds = 200; + + /// + /// In this package most operators are tied in to the RIFFA PCIe backend used by the FMC host. + /// + internal const string DefaultDriver = "riffa"; + internal const int DefaultIndex = 0; + + // NB: Decouple OnNext() from hardware reads + bool disposed; + Task readFrames; + Task distributeFrames; + Task acquisition = Task.CompletedTask; + CancellationTokenSource collectFramesCancellation; + event Func ConfigureHostEvent; + event Func ConfigureLinkEvent; + event Func ConfigureDeviceEvent; + + // FrameReceived observable sequence + readonly Subject frameReceived = new(); + readonly IConnectableObservable> groupedFrames; + + // TODO: These work for RIFFA implementation, but potentially not others!! + readonly object readLock = new(); + readonly object writeLock = new(); + readonly object regLock = new(); + readonly object disposeLock = new(); + + readonly string contextDriver = DefaultDriver; + readonly int contextIndex = DefaultIndex; + + /// + /// Initializes a new instance of the class. + /// + /// A string specifying the device driver used to control hardware. + /// The index of the host interconnect between the ONI controller and host computer. For instance, 0 could + /// correspond to a particular PCIe slot or USB port as enumerated by the operating system and translated by an + /// ONI device driver translator. + /// A value of -1 will attempt to open the default hardware index and is useful if there is only a single ONI controller + /// managed by the specified in the host computer. + internal ContextTask(string driver, int index) + { + groupedFrames = frameReceived.GroupBy(frame => frame.DeviceAddress).Replay(); + groupedFrames.Connect(); + contextDriver = driver; + contextIndex = index; + Initialize(); + } + + private void Initialize() + { + ctx = new oni.Context(contextDriver, contextIndex); + SystemClockHz = ctx.SystemClockHz; + AcquisitionClockHz = ctx.AcquisitionClockHz; + MaxReadFrameSize = ctx.MaxReadFrameSize; + MaxWriteFrameSize = ctx.MaxWriteFrameSize; + DeviceTable = ctx.DeviceTable; + } + + private void Reset() + { + lock (disposeLock) + lock (regLock) + { + AssertConfigurationContext(); + lock (readLock) + lock (writeLock) + { + ctx?.Dispose(); + Initialize(); + } + } + } + + /// + /// Gets the system clock rate in Hz. + /// + /// + /// This describes the frequency of the clock governing the ONI controller. + /// + public uint SystemClockHz { get; private set; } + + /// + /// Gets the acquisition clock rate in Hz. + /// + /// + /// This describes the frequency of the clock used to drive the ONI controller's acquisition clock which is used + /// to generate the clock counter values in and its derivative types (e.g. , + /// , etc.) + /// + public uint AcquisitionClockHz { get; private set; } + + /// + /// Gets the maximal size of a frame produced by a call to in bytes. + /// + /// + /// This number is the maximum sized frame that can be produced across every device within the device table + /// that generates data. + /// + public uint MaxReadFrameSize { get; private set; } + + /// + /// Gets the maximal size consumed by a call to in bytes. + /// + /// + /// This number is the maximum sized frame that can be consumed across every device within the device table + /// that accepts write data. + /// + public uint MaxWriteFrameSize { get; private set; } + + /// + /// Gets the device table containing the device hierarchy governed by the internal . + /// + /// + /// This dictionary maps a fully-qualified to an instance. + /// + public Dictionary DeviceTable { get; private set; } + + internal IObservable> GroupedFrames => groupedFrames; + + /// + /// Gets the sequence of s produced by a particular device. + /// + /// The fully qualified that will produce the frame sequence. + /// The frame sequence produced by the device at address . + public IObservable GetDeviceFrames(uint deviceAddress) + { + return groupedFrames.Where(deviceFrames => deviceFrames.Key == deviceAddress).Merge(); + } + + void AssertConfigurationContext() + { + if (disposed) + { + throw new ObjectDisposedException(GetType().FullName); + } + + if (!acquisition.IsCompleted) + { + throw new InvalidOperationException("Configuration cannot be changed while acquisition context is running."); + } + } + + // NB: This is where actions that reconfigure the hub state, or otherwise + // change the device table should be executed + internal void ConfigureHost(Func configure) + { + lock (regLock) + { + AssertConfigurationContext(); + ConfigureHostEvent += configure; + } + } + + // NB: This is where actions that calibrate port voltage or otherwise + // check link lock state should be executed + internal void ConfigureLink(Func configure) + { + lock (regLock) + { + AssertConfigurationContext(); + ConfigureLinkEvent += configure; + } + } + + // NB: Actions queued using this method should assume that the device table + // is finalized and cannot be changed + internal void ConfigureDevice(Func configure) + { + lock (regLock) + { + AssertConfigurationContext(); + ConfigureDeviceEvent += configure; + } + } + + private IDisposable ConfigureContext() + { + var hostAction = Interlocked.Exchange(ref ConfigureHostEvent, null); + var linkAction = Interlocked.Exchange(ref ConfigureLinkEvent, null); + var deviceAction = Interlocked.Exchange(ref ConfigureDeviceEvent, null); + var disposable = new StackDisposable(); + ConfigureResources(disposable, hostAction); + ConfigureResources(disposable, linkAction); + ConfigureResources(disposable, deviceAction); + return disposable; + } + + void ConfigureResources(StackDisposable disposable, Func action) + { + if (action != null) + { + var invocationList = action.GetInvocationList(); + try + { + foreach (var selector in invocationList.Cast>()) + { + disposable.Push(selector(this)); + } + } + catch + { + disposable.Dispose(); + throw; + } + finally { Reset(); } + } + } + + internal Task StartAsync(int blockReadSize, int blockWriteSize, CancellationToken cancellationToken = default) + { + lock (disposeLock) + lock (regLock) + { + if (disposed) + { + throw new ObjectDisposedException(GetType().FullName); + } + + if (!acquisition.IsCompleted) + throw new InvalidOperationException("Acquisition already running in the current context."); + + // NB: Configure context before starting acquisition + var contextConfiguration = ConfigureContext(); + ctx.BlockReadSize = blockReadSize; + ctx.BlockWriteSize = blockWriteSize; + + // TODO: Stuff related to sync mode is 100% ONIX, not ONI. Therefore, in the long term, + // another place to do this separation might be needed + int address = ctx.HardwareAddress; + int mode = (address & 0x00FF0000) >> 16; + if (mode == 0) // Standalone mode + { + ctx.Start(true); + } + else // If synchronized mode, reset counter independently + { + ctx.ResetFrameClock(); + ctx.Start(false); + } + + collectFramesCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var collectFramesToken = collectFramesCancellation.Token; + var frameQueue = new BlockingCollection(MaxQueuedFrames); + + readFrames = Task.Factory.StartNew(() => + { + try + { + while (!collectFramesToken.IsCancellationRequested) + { + // NB: This is a blocking call and there is no safe way to terminate it + // other than ending the process. For this reason, it is the job of the + // hardware to provide enough data (e.g. through a HeartbeatDevice") for + // this call to return. + oni.Frame frame; + try { frame = ReadFrame(); } + catch (Exception) + { + collectFramesCancellation.Cancel(); + throw; + } + frameQueue.Add(frame, collectFramesToken); + + } + } + catch (OperationCanceledException) + { +#if DEBUG + // NB: If FrameQueue.Add has not been called, frame has ref count 0 when it exits + // while loop context and will be disposed. + Console.WriteLine("Frame collection task has been cancelled by " + this.GetType()); +#endif + }; + }, + collectFramesToken, + TaskCreationOptions.LongRunning, + TaskScheduler.Default); + + distributeFrames = Task.Factory.StartNew(() => + { + try + { + while (!collectFramesToken.IsCancellationRequested) + { + if (frameQueue.TryTake(out oni.Frame frame, QueueTimeoutMilliseconds, collectFramesToken)) + { + frameReceived.OnNext(frame); + frame.Dispose(); + } + } + } + catch (OperationCanceledException) + { +#if DEBUG + // NB: If the thread stops no frame has been collected + Console.WriteLine("Frame distribution task has been cancelled by " + this.GetType()); +#endif + } + }, + collectFramesToken, + TaskCreationOptions.LongRunning, + TaskScheduler.Default); + + return acquisition = Task.WhenAll(distributeFrames, readFrames).ContinueWith(task => + { + if (readFrames.IsFaulted && readFrames.Exception is AggregateException ex) + { + var error = ex.InnerExceptions.Count == 1 ? ex.InnerExceptions[0] : ex; + frameReceived.OnError(error); + } + + lock (regLock) + { + collectFramesCancellation?.Dispose(); + collectFramesCancellation = null; + + // Clear queue and free memory + while (frameQueue?.Count > 0) + { + var frame = frameQueue.Take(); + frame.Dispose(); + } + frameQueue?.Dispose(); + frameQueue = null; + ctx.Stop(); + + contextConfiguration.Dispose(); + acquisition = Task.CompletedTask; + } + }); + } + } + + #region oni.Context Properties + + /// + /// Gets the data acquisition state. + /// + /// + /// A value of true indicates that data is being acquired by the host computer from the host controller. + /// False indicates that the host computer is not collecting data from the controller and that the controller + /// memory remains cleared. + /// + internal bool Running => ctx.Running; + + internal int HardwareAddress + { + get => ctx.HardwareAddress; + set => ctx.HardwareAddress = value; + } + + /// + /// Gets the number of bytes read by the device driver access to the read channel. + /// + /// + /// This option allows control over a fundamental trade-off between closed-loop response time and overall bandwidth. + /// A minimal value, which is determined by , will provide the lowest response latency, + /// so long as data can be cleared from hardware memory fast enough to prevent buffering. Larger values will reduce system + /// call frequency, increase overall bandwidth, and may improve processing performance for high-bandwidth data sources. + /// The optimal value depends on the host computer and hardware configuration and must be determined via testing (e.g. + /// using ). + /// + public int BlockReadSize => ctx.BlockReadSize; + + /// + /// Gets the number of bytes that are pre-allocated for writing data to hardware. + /// + /// + /// This value determines the amount of memory pre-allocated for calls to , + /// , and . A larger size will reduce + /// the average amount of dynamic memory allocation system calls but increase the cost of each of those calls. The minimum + /// size of this option is determined by . The effect on real-timer performance is not as + /// large as that of . + /// + public int BlockWriteSize => ctx.BlockWriteSize; + + // Port A and Port B each have a bit in PORTFUNC + internal PassthroughState HubState + { + get => (PassthroughState)ctx.GetCustomOption((int)oni.ONIXOption.PORTFUNC); + set => ctx.SetCustomOption((int)oni.ONIXOption.PORTFUNC, (int)value); + } + + // NB: This is for actions that require synchronized register access and might + // be called asynchronously with context dispose + internal void EnsureContext(Action action) + { + lock (disposeLock) + { + if (!disposed) + action(); + } + } + + internal uint ReadRegister(uint deviceAddress, uint registerAddress) + { + lock (regLock) + { + return ctx.ReadRegister(deviceAddress, registerAddress); + } + } + + internal void WriteRegister(uint deviceAddress, uint registerAddress, uint value) + { + lock (regLock) + { + ctx.WriteRegister(deviceAddress, registerAddress, value); + } + } + + private oni.Frame ReadFrame() + { + lock (readLock) + { + return ctx.ReadFrame(); + } + } + + internal void Write(uint deviceAddress, T data) where T : unmanaged + { + lock (writeLock) + { + ctx.Write(deviceAddress, data); + } + } + + internal void Write(uint deviceAddress, T[] data) where T : unmanaged + { + lock (writeLock) + { + ctx.Write(deviceAddress, data); + } + } + + internal void Write(uint deviceAddress, IntPtr data, int dataSize) + { + lock (writeLock) + { + ctx.Write(deviceAddress, data, dataSize); + } + } + + internal oni.Hub GetHub(uint deviceAddress) => ctx.GetHub(deviceAddress); + + internal uint GetPassthroughDeviceAddress(uint deviceAddress) + { + var hubAddress = (deviceAddress & 0xFF00u) >> 8; + if (hubAddress == 0) + { + throw new ArgumentException( + "Device addresses on hub zero cannot be used to create passthrough devices.", + nameof(deviceAddress)); + } + + return hubAddress + 7; + } + + #endregion + + private void DisposeContext() + { + lock (disposeLock) + lock (regLock) + lock (readLock) + lock (writeLock) + { + ctx?.Dispose(); + ctx = null; + } + } + + /// + /// Dispose the and free all resources. + /// + public void Dispose() + { + lock (disposeLock) + lock (regLock) + { + disposed = true; + acquisition.ContinueWith(_ => DisposeContext()); + collectFramesCancellation?.Cancel(); + } + + GC.SuppressFinalize(this); + } + } +} diff --git a/OpenEphys.Onix1/CreateContext.cs b/OpenEphys.Onix1/CreateContext.cs new file mode 100644 index 00000000..87b2ff84 --- /dev/null +++ b/OpenEphys.Onix1/CreateContext.cs @@ -0,0 +1,62 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Reactive.Linq; + +namespace OpenEphys.Onix1 +{ + /// + /// Creates a to orchestrate a single ONI-compliant controller + /// using the specified device driver and host interconnect. + /// + [Description("Creates a ContextTask to orchestrate a single ONI-compliant controller using the specified device driver and host interconnect.")] + [Combinator(MethodName = nameof(Generate))] + [WorkflowElementCategory(ElementCategory.Source)] + public class CreateContext + { + /// + /// Gets or sets a string specifying the device driver used to communicate with hardware. + /// + [Description("Specifies the device driver used to communicate with hardware.")] + public string Driver { get; set; } = ContextTask.DefaultDriver; + + /// + /// Gets or sets the index of the host interconnect between the ONI controller and host computer. + /// + /// + /// For instance, 0 could correspond to a particular PCIe slot or USB port as enumerated by the operating system and translated by an + /// ONI device driver translator. + /// A value of -1 will attempt to open the default index and is useful if there is only a single ONI controller + /// managed by the specified selected in the host computer. + /// + [Description("The index of the host interconnect between the ONI controller and host computer.")] + public int Index { get; set; } = ContextTask.DefaultIndex; + + /// + /// Generates a sequence that creates a new object. + /// + /// + /// A sequence containing a single instance of the class. Cancelling the sequence + /// will dispose of the created context. + /// + public IObservable Generate() + { + return Observable.Create(observer => + { + var driver = Driver; + var index = Index; + var context = new ContextTask(driver, index); + try + { + observer.OnNext(context); + return context; + } + catch + { + context.Dispose(); + throw; + } + }); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureDS90UB9x.cs b/OpenEphys.Onix1/DS90UB9x.cs similarity index 73% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureDS90UB9x.cs rename to OpenEphys.Onix1/DS90UB9x.cs index 852fc1e7..56a2eca0 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureDS90UB9x.cs +++ b/OpenEphys.Onix1/DS90UB9x.cs @@ -1,33 +1,5 @@ -using System; -using System.ComponentModel; - -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - public class ConfigureDS90UB9x : SingleDeviceFactory - { - public ConfigureDS90UB9x() - : base(typeof(DS90UB9x)) - { - } - - [Category(ConfigurationCategory)] - [Description("Specifies whether the DS90UB9x raw device is enabled.")] - public bool Enable { get; set; } = true; - - public override IObservable Process(IObservable source) - { - var enable = Enable; - var deviceName = DeviceName; - var deviceAddress = DeviceAddress; - return source.ConfigureDevice(context => - { - var device = context.GetDeviceContext(deviceAddress, DS90UB9x.ID); - device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); - return DeviceManager.RegisterDevice(deviceName, device, DeviceType); - }); - } - } - static class DS90UB9x { public const int ID = 24; diff --git a/OpenEphys.Onix1/DataFrame.cs b/OpenEphys.Onix1/DataFrame.cs new file mode 100644 index 00000000..536e5210 --- /dev/null +++ b/OpenEphys.Onix1/DataFrame.cs @@ -0,0 +1,68 @@ +namespace OpenEphys.Onix1 +{ + /// + /// An abstract class for representing objects in way that suits their use in this library. + /// + public abstract class DataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// Acquisition clock count. Generally provided by the underlying value. + internal DataFrame(ulong clock) + { + Clock = clock; + } + + internal DataFrame(ulong clock, ulong hubClock) + : this(clock) + { + HubClock = hubClock; + } + + /// + /// Gets the acquisition clock count. + /// + /// + /// Acquisition clock count that is synchronous for all frames collected within an ONI context created using . + /// The acquisition clock rate is given by . This clock value provides a common, synchronized + /// time base for all data collected with an single ONI context. + /// + public ulong Clock { get; } + + /// + /// Gets the hub clock count. + /// + /// + /// Local, potentially asynchronous, clock count. Aside from the synchronous value, data frames also contain a local clock + /// count produced within the that the data was actually produced within. For instance, a headstage may contain an onboard controller + /// for controlling devices and arbitrating data stream that runs asynchronously from the . This value + /// is therefore the most precise way to compare the sample time of data collected within a given . However, the delay between time of + /// data collection and synchronous time stamping by is very small (sub-microsecond) and this value can therefore + /// be disregarded in most scenarios in favor of . + /// + public ulong HubClock { get; internal set; } + } + + /// + /// An abstract class for representing buffered groups objects in way that suits their use in this library. + /// + public abstract class BufferedDataFrame + { + internal BufferedDataFrame(ulong[] clock, ulong[] hubClock) + { + Clock = clock; + HubClock = hubClock; + } + + /// + /// Gets the buffered array of values. + /// + public ulong[] Clock { get; } + + /// + /// Gets the buffered array of values. + /// + public ulong[] HubClock { get; } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/DeviceContext.cs b/OpenEphys.Onix1/DeviceContext.cs similarity index 90% rename from OpenEphys.Onix/OpenEphys.Onix/DeviceContext.cs rename to OpenEphys.Onix1/DeviceContext.cs index 53f24b4a..5c059926 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/DeviceContext.cs +++ b/OpenEphys.Onix1/DeviceContext.cs @@ -1,8 +1,8 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - public class DeviceContext + internal class DeviceContext { readonly ContextTask _context; readonly oni.Device _device; @@ -19,6 +19,8 @@ public DeviceContext(ContextTask context, oni.Device device) public oni.Device DeviceMetadata => _device; + public oni.Hub Hub => _context.GetHub(_device.Address); + public uint ReadRegister(uint registerAddress) { return _context.ReadRegister(_device.Address, registerAddress); diff --git a/OpenEphys.Onix1/DeviceFactory.cs b/OpenEphys.Onix1/DeviceFactory.cs new file mode 100644 index 00000000..1181c1b8 --- /dev/null +++ b/OpenEphys.Onix1/DeviceFactory.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// Provides an abstract base class for all device configuration operators. + /// + /// + /// ONI devices usually require a specific sequence of configuration and parameterization + /// steps before they can be interacted with. The provides + /// a modular abstraction for flexible assembly and sequencing of both single and multi- + /// device configuration. + /// + public abstract class DeviceFactory : Sink + { + internal const string ConfigurationCategory = "Configuration"; + internal const string AcquisitionCategory = "Acquisition"; + + internal abstract IEnumerable GetDevices(); + } + + /// + /// Provides an abstract base class for configuration operators responsible for + /// registering a single device in the context device table. + /// + /// + /// ONI devices usually require a specific sequence of configuration and parameterization + /// steps before they can be interacted with. The + /// provides a modular abstraction allowing flexible assembly and sequencing of + /// of all device-specific configuration code. + /// + public abstract class SingleDeviceFactory : DeviceFactory, IDeviceConfiguration + { + internal const string DeviceNameDescription = "The unique device name."; + internal const string DeviceAddressDescription = "The device address."; + + internal SingleDeviceFactory(Type deviceType) + { + DeviceType = deviceType ?? throw new ArgumentNullException(nameof(deviceType)); + } + + /// + /// Gets or sets a unique device name. + /// + /// + /// The device name provides a unique, human-readable identifier that is used to link software + /// elements for configuration, control, and data streaming to hardware. This is often a one-to-one + /// representation of a single , but can also represent abstract ONI device + /// aggregates or virtual devices. + /// + [Description(DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Gets or sets the device address. + /// + /// + /// This address provides a fully-qualified location of a device within the device table. This is often a one-to-one + /// representation of a , but can also represent abstract device addresses. + /// + [Description(DeviceAddressDescription)] + public uint DeviceAddress { get; set; } + + /// + /// Gets or sets the device identity. + /// + /// + /// This type provides a device identity to each device within the device table. This is often a one-to-one + /// representation of a a , but can also represent abstract device identities. + /// + [Browsable(false)] + public Type DeviceType { get; } + + internal override IEnumerable GetDevices() + { + yield return this; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/DeviceInfo.cs b/OpenEphys.Onix1/DeviceInfo.cs similarity index 89% rename from OpenEphys.Onix/OpenEphys.Onix/DeviceInfo.cs rename to OpenEphys.Onix1/DeviceInfo.cs index 53059dcf..3879942d 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/DeviceInfo.cs +++ b/OpenEphys.Onix1/DeviceInfo.cs @@ -1,6 +1,6 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { internal class DeviceInfo { @@ -27,7 +27,7 @@ public void AssertType(Type expectedType) if (DeviceType != expectedType) { throw new InvalidOperationException( - $"Expected device with register type {expectedType}. Actual type is {DeviceType}." + $"Expected device of type {expectedType.Name}. Actual type is {DeviceType.Name}." ); } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/DeviceManager.cs b/OpenEphys.Onix1/DeviceManager.cs similarity index 63% rename from OpenEphys.Onix/OpenEphys.Onix/DeviceManager.cs rename to OpenEphys.Onix1/DeviceManager.cs index 52274ddd..fa478cd8 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/DeviceManager.cs +++ b/OpenEphys.Onix1/DeviceManager.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; using System.Reactive.Disposables; +using System.Reactive.Linq; using System.Reactive.Subjects; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class DeviceManager { - static readonly Dictionary deviceMap = new(); + static readonly Dictionary deviceMap = new(); static readonly object managerLock = new(); internal static IDisposable RegisterDevice(string name, DeviceContext device, Type deviceType) @@ -18,17 +19,15 @@ internal static IDisposable RegisterDevice(string name, DeviceContext device, Ty internal static IDisposable RegisterDevice(string name, DeviceInfo deviceInfo) { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("A valid device name must be specified.", nameof(name)); + } + lock (managerLock) { - var disposable = ReserveDevice(name); + var disposable = RegisterDevice(name); var subject = disposable.Subject; - if (subject.IsCompleted) - { - throw new ArgumentException( - $"A device with the same name '{name}' has already been configured.", - nameof(name) - ); - } foreach (var entry in deviceMap) { @@ -55,35 +54,53 @@ internal static IDisposable RegisterDevice(string name, DeviceInfo deviceInfo) } } - internal static DeviceDisposable ReserveDevice(string name) + static DeviceDisposable RegisterDevice(string name) { lock (managerLock) { - if (!deviceMap.TryGetValue(name, out var resourceHandle)) + if (deviceMap.ContainsKey(name)) { - var subject = new AsyncSubject(); - var dispose = Disposable.Create(() => - { - subject.Dispose(); - deviceMap.Remove(name); - }); - - resourceHandle.Subject = subject; - resourceHandle.RefCount = new RefCountDisposable(dispose); - deviceMap.Add(name, resourceHandle); - return new DeviceDisposable(subject, resourceHandle.RefCount); + throw new ArgumentException( + $"A device with the same name '{name}' has already been configured.", + nameof(name) + ); } - return new DeviceDisposable( - resourceHandle.Subject, - resourceHandle.RefCount.GetDisposable()); + var subject = new AsyncSubject(); + var dispose = Disposable.Create(() => + { + subject.Dispose(); + deviceMap.Remove(name); + }); + + var deviceDisposable = new DeviceDisposable(subject, dispose); + deviceMap.Add(name, deviceDisposable); + return deviceDisposable; } } - struct ResourceHandle + internal static IObservable GetDevice(string name) { - public AsyncSubject Subject; - public RefCountDisposable RefCount; + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("A valid device name must be specified.", nameof(name)); + } + + return Observable.Create(observer => + { + lock (managerLock) + { + if (!deviceMap.TryGetValue(name, out var deviceDisposable)) + { + throw new ArgumentException( + $"No device with the specified name '{name}' has been configured.", + nameof(name) + ); + } + + return deviceDisposable.Subject.SubscribeSafe(observer); + } + }); } internal sealed class DeviceDisposable : IDisposable diff --git a/OpenEphys.Onix/OpenEphys.Onix/DeviceNameConverter.cs b/OpenEphys.Onix1/DeviceNameConverter.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix/DeviceNameConverter.cs rename to OpenEphys.Onix1/DeviceNameConverter.cs index 08ec5715..7150f475 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/DeviceNameConverter.cs +++ b/OpenEphys.Onix1/DeviceNameConverter.cs @@ -5,7 +5,7 @@ using Bonsai.Expressions; using Bonsai; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { /// /// Provides a type converter to convert a device name to and from other representations. diff --git a/OpenEphys.Onix/OpenEphys.Onix/Electrode.cs b/OpenEphys.Onix1/Electrode.cs similarity index 97% rename from OpenEphys.Onix/OpenEphys.Onix/Electrode.cs rename to OpenEphys.Onix1/Electrode.cs index c415fdd2..a1010319 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/Electrode.cs +++ b/OpenEphys.Onix1/Electrode.cs @@ -1,7 +1,7 @@ using System.Drawing; using System.Xml.Serialization; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { public abstract class Electrode { diff --git a/OpenEphys.Onix1/HarpSyncInputData.cs b/OpenEphys.Onix1/HarpSyncInputData.cs new file mode 100644 index 00000000..6428420d --- /dev/null +++ b/OpenEphys.Onix1/HarpSyncInputData.cs @@ -0,0 +1,38 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that generates a sequence of Harp clock synchronization events produced by + /// the Harp sync input device in the ONIX breakout board. + /// + /// + [Description("Generates a sequence of Harp clock synchronization events produced by the Harp sync input device in the ONIX breakout board.")] + public class HarpSyncInputData : Source + { + /// + [TypeConverter(typeof(HarpSyncInput.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence of objects, each of which contains + /// information about a single Harp clock synchronization event. + /// + /// A sequence of objects. + public override IObservable Generate() + { + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(HarpSyncInput)); + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .Select(frame => new HarpSyncInputDataFrame(frame)); + }); + } + } +} diff --git a/OpenEphys.Onix1/HarpSyncInputDataFrame.cs b/OpenEphys.Onix1/HarpSyncInputDataFrame.cs new file mode 100644 index 00000000..4940d5cc --- /dev/null +++ b/OpenEphys.Onix1/HarpSyncInputDataFrame.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that contains information about a Harp clock synchronization event. + /// + public class HarpSyncInputDataFrame : DataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A frame produced by the Harp sync input device of an ONIX breakout board. + /// + public unsafe HarpSyncInputDataFrame(oni.Frame frame) + : base(frame.Clock) + { + var payload = (HarpSyncInputPayload*)frame.Data.ToPointer(); + HubClock = payload->HubClock; + HarpTime = payload->HarpTime; + } + + /// + /// Gets the Harp clock time corresponding to the local acquisition ONIX clock count. + /// + public uint HarpTime { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct HarpSyncInputPayload + { + public ulong HubClock; + public uint HarpTime; + } +} diff --git a/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs b/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs new file mode 100644 index 00000000..e0cd6894 --- /dev/null +++ b/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs @@ -0,0 +1,240 @@ +using System; +using System.ComponentModel; +using System.Drawing.Design; +using System.Linq; +using System.Reactive; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that controls a headstage-64 onboard electrical stimulus sequencer. + /// + /// + /// This class must be linked to an appropriate configuration, such as a , + /// in order to define and deliver electrical stimulation sequences. + /// + [Description("Controls a headstage-64 onboard electrical stimulus sequencer.")] + public class Headstage64ElectricalStimulatorTrigger: Sink + { + readonly BehaviorSubject enable = new(true); + readonly BehaviorSubject phaseOneCurrent = new(0); + readonly BehaviorSubject interPhaseCurrent = new(0); + readonly BehaviorSubject phaseTwoCurrent = new(0); + readonly BehaviorSubject phaseOneDuration = new(0); + readonly BehaviorSubject interPhaseInterval = new(0); + readonly BehaviorSubject phaseTwoDuration = new(0); + readonly BehaviorSubject interPulseInterval = new(0); + readonly BehaviorSubject burstPulseCount = new(0); + readonly BehaviorSubject interBurstInterval = new(0); + readonly BehaviorSubject trainBurstCount = new(0); + readonly BehaviorSubject triggerDelay = new(0); + readonly BehaviorSubject powerEnable = new(false); + + /// + [TypeConverter(typeof(Headstage64ElectricalStimulator.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, then the electrical stimulator circuit will respect triggers. If set to false, triggers will be ignored. + /// + [Description("Specifies whether the electrical stimulator will respect triggers.")] + public bool Enable + { + get => enable.Value; + set => enable.OnNext(value); + } + + /// + /// Gets or sets the electrical stimulator's power state. + /// + /// + /// If set to true, then the electrical stimulator's ±15V power supplies will be turned on. If set to false, + /// they will be turned off. It may be desirable to power down the electrical stimulator's power supplies outside + /// of stimulation windows to reduce power consumption and electrical noise. This property must be set to true + /// in order for electrical stimuli to be delivered properly. It takes ~10 milliseconds for these supplies to stabilize. + /// + [Description("Stimulator power on/off.")] + public bool PowerEnable + { + get => powerEnable.Value; + set => powerEnable.OnNext(value); + } + + /// + /// Gets or sets a delay from receiving a trigger to the start of stimulus sequence application in μsec + /// + [Description("A delay from receiving a trigger to the start of stimulus sequence application (uSec).")] + [Range(0, uint.MaxValue)] + public uint TriggerDelay + { + get => triggerDelay.Value; + set => triggerDelay.OnNext(value); + } + + + /// + /// Gets or sets the amplitude of the first phase of each pulse in μA. + /// + [Description("Amplitude of the first phase of each pulse (uA).")] + [Range(-Headstage64ElectricalStimulator.AbsMaxMicroAmps, Headstage64ElectricalStimulator.AbsMaxMicroAmps)] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + [Precision(3, 1)] + public double PhaseOneCurrent + { + get => phaseOneCurrent.Value; + set => phaseOneCurrent.OnNext(value); + } + + /// + /// Gets or sets the amplitude of the interphase current of each pulse in μA. + /// + [Description("The amplitude of the inter-phase current of each pulse (uA).")] + [Range(-Headstage64ElectricalStimulator.AbsMaxMicroAmps, Headstage64ElectricalStimulator.AbsMaxMicroAmps)] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + [Precision(3, 1)] + public double InterPhaseCurrent + { + get => interPhaseCurrent.Value; + set => interPhaseCurrent.OnNext(value); + } + + /// + /// Gets or sets the amplitude of the second phase of each pulse in μA. + /// + [Description("The amplitude of the second phase of each pulse (uA).")] + [Range(-Headstage64ElectricalStimulator.AbsMaxMicroAmps, Headstage64ElectricalStimulator.AbsMaxMicroAmps)] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + [Precision(3, 1)] + public double PhaseTwoCurrent + { + get => phaseTwoCurrent.Value; + set => phaseTwoCurrent.OnNext(value); + } + + /// + /// Gets or sets the duration of the first phase of each pulse in μsec. + /// + [Description("The duration of the first phase of each pulse in μsec.")] + [Range(0, uint.MaxValue)] + public uint PhaseOneDuration + { + get => phaseOneDuration.Value; + set => phaseOneDuration.OnNext(value); + } + + /// + /// Gets or sets the duration of the interphase interval of each pulse in μsec. + /// + [Description("The duration of the interphase interval of each pulse (uSec).")] + [Range(0, uint.MaxValue)] + public uint InterPhaseInterval + { + get => interPhaseInterval.Value; + set => interPhaseInterval.OnNext(value); + } + + /// + /// Gets or sets the duration of the second phase of each pulse in μsec. + /// + [Description("The duration of the second phase of each pulse (uSec).")] + [Range(0, uint.MaxValue)] + public uint PhaseTwoDuration + { + get => phaseTwoDuration.Value; + set => phaseTwoDuration.OnNext(value); + } + + /// + /// Gets or sets the duration of the inter-pulse interval within a single burst in μsec. + /// + [Description("The duration of the inter-pulse interval within a single burst (uSec).")] + [Range(0, uint.MaxValue)] + public uint InterPulseInterval + { + get => interPulseInterval.Value; + set => interPulseInterval.OnNext(value); + } + + /// + /// Gets or sets the duration of the inter-burst interval within a stimulus train in μsec. + /// + [Description("The duration of the inter-burst interval within a stimulus train (uSec).")] + [Range(0, uint.MaxValue)] + public uint InterBurstInterval + { + get => interBurstInterval.Value; + set => interBurstInterval.OnNext(value); + } + + /// + /// Gets or sets the number of pulses per burst. + /// + [Description("The number of pulses per burst.")] + [Range(0, uint.MaxValue)] + public uint BurstPulseCount + { + get => burstPulseCount.Value; + set => burstPulseCount.OnNext(value); + } + + /// + /// Gets or sets the number of bursts in a stimulus train. + /// + [Description("The number of bursts in each train.")] + [Range(0, uint.MaxValue)] + public uint TrainBurstCount + { + get => trainBurstCount.Value; + set => trainBurstCount.OnNext(value); + } + + /// + /// Start an electrical stimulus sequence. + /// + /// A sequence of boolean values indicating the start of a stimulus sequence when true. + /// A sequence of boolean values that is identical to + public override IObservable Process(IObservable source) + { + return DeviceManager.GetDevice(DeviceName).SelectMany( + deviceInfo => Observable.Create(observer => + { + var device = deviceInfo.GetDeviceContext(typeof(Headstage64ElectricalStimulator)); + var triggerObserver = Observer.Create( + value => device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, value ? 1u : 0u), + observer.OnError, + observer.OnCompleted); + + static uint uAToCode(double currentuA) + { + var k = 1 / (2 * Headstage64ElectricalStimulator.AbsMaxMicroAmps / (Math.Pow(2, Headstage64ElectricalStimulator.DacBitDepth) - 1)); // static + return (uint)(k * (currentuA + Headstage64ElectricalStimulator.AbsMaxMicroAmps)); + } + + return new CompositeDisposable( + enable.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.ENABLE, value ? 1u : 0u)), + phaseOneCurrent.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT1, uAToCode(value))), + interPhaseCurrent.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.RESTCURR, uAToCode(value))), + phaseTwoCurrent.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT2, uAToCode(value))), + triggerDelay.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.TRAINDELAY, value)), + phaseOneDuration.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR1, value)), + interPhaseInterval.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.INTERPHASEINTERVAL, value)), + phaseTwoDuration.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR2, value)), + interPulseInterval.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.INTERPULSEINTERVAL, value)), + interBurstInterval.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.INTERBURSTINTERVAL, value)), + burstPulseCount.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.BURSTCOUNT, value)), + trainBurstCount.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.TRAINCOUNT, value)), + powerEnable.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.POWERON, value ? 1u : 0u)), + source.SubscribeSafe(triggerObserver) + ); + })); + } + } +} diff --git a/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs b/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs new file mode 100644 index 00000000..34f6f060 --- /dev/null +++ b/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs @@ -0,0 +1,270 @@ +using System; +using System.ComponentModel; +using System.Drawing.Design; +using System.Linq; +using System.Reactive; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that controls a headstage-64 onboard optical stimulus sequencer. + /// + /// + /// This class must be linked to an appropriate configuration, such as a , + /// in order to define and deliver optical stimulation sequences. + /// + [Description("Controls a headstage-64 onboard optical stimulus sequencer.")] + public class Headstage64OpticalStimulatorTrigger : Sink + { + readonly BehaviorSubject enable = new(true); + readonly BehaviorSubject maxCurrent = new(100); + readonly BehaviorSubject channelOneCurrent = new(100); + readonly BehaviorSubject channelTwoCurrent = new(0); + readonly BehaviorSubject pulseDuration = new(5); + readonly BehaviorSubject pulsesPerSecond = new(50); + readonly BehaviorSubject pulsesPerBurst = new(20); + readonly BehaviorSubject interBurstInterval = new(0); + readonly BehaviorSubject burstsPerTrain = new(1); + readonly BehaviorSubject delay = new(0); + + /// + [TypeConverter(typeof(Headstage64OpticalStimulator.NameConverter))] + public string DeviceName { get; set; } + + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, then the optical stimulator circuit will respect triggers. If set to false, triggers will be ignored. + /// + [Description("Specifies whether the optical stimulator will respect triggers.")] + public bool Enable + { + get => enable.Value; + set => enable.OnNext(value); + } + + /// + /// Gets or sets a delay from receiving a trigger to the start of stimulus sequence application in msec. + /// + [Description("A delay from receiving a trigger to the start of stimulus sequence application (msec).")] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Range(0.0, 1000.0)] + [Precision(3, 1)] + public double Delay + { + get => delay.Value; + set => delay.OnNext(value); + } + + /// + /// Gets or sets the Maximum current per channel per pulse in mA. + /// + /// + /// This value defines the maximal possible current that can be delivered to each channel. + /// To get different amplitudes for each channel use the and + /// properties. + /// + [Description("Maximum current per channel per pulse (mA). " + + "This value is used by both channels. To get different amplitudes " + + "for each channel use the ChannelOneCurrent and ChannelTwoCurrent properties.")] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + [Range(0, 300)] + [Precision(3, 0)] + public double MaxCurrent + { + get => maxCurrent.Value; + set => maxCurrent.OnNext(value); + } + + /// + /// Gets or sets the percent of that will delivered to channel 1 in each pulse. + /// + [Description("Channel 1 percent of MaxCurrent. If greater than 0, channel 1 will respond to triggers.")] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Range(0, 100)] + [Precision(1, 12.5)] + public double ChannelOneCurrent + { + get => channelOneCurrent.Value; + set => channelOneCurrent.OnNext(value); + } + + /// + /// Gets or sets the percent of that will delivered to channel 2 in each pulse. + /// + [Description("Channel 2 percent of MaxCurrent. If greater than 0, channel 2 will respond to triggers.")] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Range(0, 100)] + [Precision(1, 12.5)] + public double ChannelTwoCurrent + { + get => channelTwoCurrent.Value; + set => channelTwoCurrent.OnNext(value); + } + + /// + /// Gets or sets the duration of each pulse in msec. + /// + [Description("The duration of each pulse (msec).")] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Range(0.001, 1000.0)] + [Precision(3, 1)] + public double PulseDuration + { + get => pulseDuration.Value; + set => pulseDuration.OnNext(value); + } + + /// + /// Gets or sets the pulse period within a burst in msec. + /// + [Description("The pulse period within a burst (msec).")] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Range(0.01, 10000.0)] + [Precision(3, 1)] + public double PulsesPerSecond + { + get => pulsesPerSecond.Value; + set => pulsesPerSecond.OnNext(value); + } + + /// + /// Gets or sets the number of pulses per burst. + /// + [Description("Number of pulses to deliver in a burst.")] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Range(1, int.MaxValue)] + [Precision(0, 1)] + public uint PulsesPerBurst + { + get => pulsesPerBurst.Value; + set => pulsesPerBurst.OnNext(value); + } + + /// + /// Gets or sets the duration of the inter-burst interval within a stimulus train in msec. + /// + [Description("The duration of the inter-burst interval within a stimulus train (msec).")] + [Editor(DesignTypes.SliderEditor, DesignTypes.UITypeEditor)] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Range(0.0, 10000.0)] + [Precision(3, 1)] + public double InterBurstInterval + { + get => interBurstInterval.Value; + set => interBurstInterval.OnNext(value); + } + + /// + /// Gets or sets the number of bursts in a stimulus train. + /// + [Description("Number of bursts to deliver in a train.")] + [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] + [Range(1, int.MaxValue)] + [Precision(0, 1)] + public uint BurstsPerTrain + { + get => burstsPerTrain.Value; + set => burstsPerTrain.OnNext(value); + } + + // TODO: Should this be checked before TRIGGER is written to below and an error thrown if + // DC current is too high? Or, should settings be forced too keep DC current under some value? + /// + /// Gets total direct current required during the application of a burst. + /// + /// + /// This value should be kept below 50 mA to prevent excess head accumulation on the headstage. + /// + [Description("The total direct current required during the application of a burst (mA). Should be less than 50 mA.")] + public double BurstCurrent + { + get + { + return PulsesPerSecond * 0.001 * PulseDuration * MaxCurrent * 0.01 * (ChannelOneCurrent + ChannelTwoCurrent); + } + } + + /// + /// Start an optical stimulus sequence. + /// + /// A sequence of boolean values indicating the start of a stimulus sequence when true. + /// A sequence of boolean values that is identical to + public override IObservable Process(IObservable source) + { + return DeviceManager.GetDevice(DeviceName).SelectMany( + deviceInfo => Observable.Create(observer => + { + var device = deviceInfo.GetDeviceContext(typeof(Headstage64OpticalStimulator)); + var triggerObserver = Observer.Create( + value => device.WriteRegister(Headstage64OpticalStimulator.TRIGGER, value ? 1u : 0u), + observer.OnError, + observer.OnCompleted); + + // NB: fit from Fig. 10 of CAT4016 datasheet + // x = (y/a)^(1/b) + // a = 3.833e+05 + // b = -0.9632 + static uint mAToPotSetting(double currentMa) + { + double R = Math.Pow(currentMa / 3.833e+05, 1 / -0.9632); + double s = 256 * (R - Headstage64OpticalStimulator.MinRheostatResistanceOhms) / Headstage64OpticalStimulator.PotResistanceOhms; + return s > 255 ? 255 : s < 0 ? 0 : (uint)s; + } + + uint currentSourceMask = 0; + static uint percentToPulseMask(int channel, double percent, uint oldMask) + { + uint mask = 0x00000000; + var p = 0.0; + while (p < percent) + { + mask = (mask << 1) | 1; + p += 12.5; + } + + return channel == 0 ? (oldMask & 0x0000FF00) | mask : (mask << 8) | (oldMask & 0x000000FF); + } + + static uint pulseDurationToRegister(double pulseDuration, double pulseHz) + { + var pulsePeriod = 1000.0 / pulseHz; + return pulseDuration > pulsePeriod ? (uint)(1000 * pulsePeriod - 1): (uint)(1000 * pulseDuration); + } + + static uint pulseFrequencyToRegister(double pulseHz, double pulseDuration) + { + var pulsePeriod = 1000.0 / pulseHz; + return pulsePeriod > pulseDuration ? (uint)(1000 * pulsePeriod) : (uint)(1000 * pulseDuration + 1); + } + + return new CompositeDisposable( + enable.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.ENABLE, value ? 1u : 0u)), + maxCurrent.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.MAXCURRENT, mAToPotSetting(value))), + channelOneCurrent.SubscribeSafe(observer, value => + { + currentSourceMask = percentToPulseMask(0, value, currentSourceMask); + device.WriteRegister(Headstage64OpticalStimulator.PULSEMASK, currentSourceMask); + }), + channelTwoCurrent.SubscribeSafe(observer, value => + { + currentSourceMask = percentToPulseMask(1, value, currentSourceMask); + device.WriteRegister(Headstage64OpticalStimulator.PULSEMASK, currentSourceMask); + }), + pulseDuration.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.PULSEDUR, pulseDurationToRegister(value, PulsesPerSecond))), + pulsesPerSecond.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.PULSEPERIOD, pulseFrequencyToRegister(value, PulseDuration))), + pulsesPerBurst.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.BURSTCOUNT, value)), + interBurstInterval.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.IBI, (uint)(1000 * value))), + burstsPerTrain.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.TRAINCOUNT, value)), + delay.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.TRAINDELAY, (uint)(1000 * value))), + source.SubscribeSafe(triggerObserver) + ); + })); + } + } +} diff --git a/OpenEphys.Onix1/HeartbeatData.cs b/OpenEphys.Onix1/HeartbeatData.cs new file mode 100644 index 00000000..f4c55227 --- /dev/null +++ b/OpenEphys.Onix1/HeartbeatData.cs @@ -0,0 +1,40 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that produces a sequence of heartbeat data frames. + /// + /// + /// This data stream class must be linked to an appropriate configuration, such as a , + /// in order to stream heartbeat data. + /// + [Description("Produces a sequence of heartbeat data frames.")] + public class HeartbeatData : Source + { + /// + [TypeConverter(typeof(Heartbeat.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence of objects, each of which contains period signal from the + /// acquisition system indicating that it is active. + /// + /// A sequence of objects. + public override IObservable Generate() + { + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(Heartbeat)); + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .Select(frame => new HeartbeatDataFrame(frame)); + }); + } + } +} diff --git a/OpenEphys.Onix1/HeartbeatDataFrame.cs b/OpenEphys.Onix1/HeartbeatDataFrame.cs new file mode 100644 index 00000000..9c22a27a --- /dev/null +++ b/OpenEphys.Onix1/HeartbeatDataFrame.cs @@ -0,0 +1,27 @@ +using System.Runtime.InteropServices; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that contains the time of a single heartbeat. + /// + public class HeartbeatDataFrame : DataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// A data frame produced by a heartbeat device. + public unsafe HeartbeatDataFrame(oni.Frame frame) + : base(frame.Clock) + { + var payload = (HeartbeatPayload*)frame.Data.ToPointer(); + HubClock = payload->HubClock; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct HeartbeatPayload + { + public ulong HubClock; + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/I2CRegisterContext.cs b/OpenEphys.Onix1/I2CRegisterContext.cs similarity index 98% rename from OpenEphys.Onix/OpenEphys.Onix/I2CRegisterContext.cs rename to OpenEphys.Onix1/I2CRegisterContext.cs index d87f82f7..03075e87 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/I2CRegisterContext.cs +++ b/OpenEphys.Onix1/I2CRegisterContext.cs @@ -1,7 +1,7 @@ using System; using System.Text; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class I2CRegisterContext { diff --git a/OpenEphys.Onix/OpenEphys.Onix/IDeviceConfiguration.cs b/OpenEphys.Onix1/IDeviceConfiguration.cs similarity index 89% rename from OpenEphys.Onix/OpenEphys.Onix/IDeviceConfiguration.cs rename to OpenEphys.Onix1/IDeviceConfiguration.cs index 1e0bf4bf..73082f61 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/IDeviceConfiguration.cs +++ b/OpenEphys.Onix1/IDeviceConfiguration.cs @@ -1,6 +1,6 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { internal interface IDeviceConfiguration { diff --git a/OpenEphys.Onix/OpenEphys.Onix/MatHelper.cs b/OpenEphys.Onix1/MatHelper.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix/MatHelper.cs rename to OpenEphys.Onix1/MatHelper.cs index 5c6f3d2c..60fe8754 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/MatHelper.cs +++ b/OpenEphys.Onix1/MatHelper.cs @@ -3,7 +3,7 @@ using System.Reactive.Linq; using OpenCV.Net; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { static class MatHelper { diff --git a/OpenEphys.Onix1/MemoryMonitorData.cs b/OpenEphys.Onix1/MemoryMonitorData.cs new file mode 100644 index 00000000..ed674142 --- /dev/null +++ b/OpenEphys.Onix1/MemoryMonitorData.cs @@ -0,0 +1,42 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that produces a sequence of memory usage data frames. + /// + /// + /// This data stream class must be linked to an appropriate configuration, such as a , + /// in order to stream data. + /// + [Description("Produces a sequence of memory usage data frames.")] + public class MemoryMonitorData : Source + { + /// + [TypeConverter(typeof(MemoryMonitor.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence of objects, which contains information + /// about the system's low-level first-in, first-out (FIFO) data buffer. + /// + /// A sequence of objects. + public override IObservable Generate() + { + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(MemoryMonitor)); + var totalMemory = device.ReadRegister(MemoryMonitor.TOTAL_MEM); + + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .Select(frame => new MemoryMonitorDataFrame(frame, totalMemory)); + }); + } + } +} diff --git a/OpenEphys.Onix1/MemoryMonitorDataFrame.cs b/OpenEphys.Onix1/MemoryMonitorDataFrame.cs new file mode 100644 index 00000000..72ae2500 --- /dev/null +++ b/OpenEphys.Onix1/MemoryMonitorDataFrame.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that contains hardware memory use information. + /// + public class MemoryMonitorDataFrame : DataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// A data frame produced by a memory monitor device. + /// + /// The total amount of memory, in 32-bit words, on the hardware that is available for data buffering. + /// + public unsafe MemoryMonitorDataFrame(oni.Frame frame, uint totalMemory) + : base(frame.Clock) + { + var payload = (MemoryUsagePayload*)frame.Data.ToPointer(); + HubClock = payload->HubClock; + PercentUsed = 100.0 * payload->Usage / totalMemory; + BytesUsed = payload->Usage * 4; + } + + /// + /// Gets the percent of available memory that is currently used. + /// + public double PercentUsed { get; } + + /// + /// Gets the number of bytes that are currently used. + /// + public uint BytesUsed { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MemoryUsagePayload + { + public ulong HubClock; + public uint Usage; + } +} diff --git a/OpenEphys.Onix1/MultiDeviceFactory.cs b/OpenEphys.Onix1/MultiDeviceFactory.cs new file mode 100644 index 00000000..a6e0f4fe --- /dev/null +++ b/OpenEphys.Onix1/MultiDeviceFactory.cs @@ -0,0 +1,92 @@ +using System; +using System.ComponentModel; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// Provides an abstract base class for configuration operators responsible for + /// registering all devices in an ONI device aggregate in the context device table. + /// + /// + /// + /// ONI devices are often grouped into multi-device aggregates connected to hubs or + /// headstages. These aggregates provide access to multiple devices through hub-specific + /// addresses and usually require a specific sequence of configuration steps to determine + /// operational port voltages and other link-specific settings. + /// + /// + /// These multi-device aggregates are the most common starting point for configuration + /// of an ONI system, and the provides a modular abstraction + /// for flexible assembly and sequencing of multiple such aggregates. + /// + /// + public abstract class MultiDeviceFactory : DeviceFactory, INamedElement + { + const string BaseTypePrefix = "Configure"; + string _name; + + internal MultiDeviceFactory() + { + var baseName = GetType().Name; + var prefixIndex = baseName.IndexOf(BaseTypePrefix); + Name = prefixIndex >= 0 ? baseName.Substring(prefixIndex + BaseTypePrefix.Length) : baseName; + } + + /// + /// Gets or sets a unique hub device name. + /// + /// + [Description("The unique hub device name.")] + public string Name + { + get { return _name; } + set + { + _name = value; + UpdateDeviceNames(); + } + } + + internal string GetFullDeviceName(string deviceName) + { + return !string.IsNullOrEmpty(_name) ? $"{_name}/{deviceName}" : string.Empty; + } + + internal virtual void UpdateDeviceNames() + { + foreach (var device in GetDevices()) + { + device.DeviceName = GetFullDeviceName(device.DeviceType.Name); + } + } + + /// + /// Configure all the ONI devices in the multi-device aggregate. + /// + /// + /// This will schedule configuration actions to be applied by a instance + /// prior to data acquisition. + /// + /// A sequence of instances that hold configuration actions. + /// + /// The original sequence modified by adding additional configuration actions required to configure + /// all the ONI devices in the multi-device aggregate. + /// + public override IObservable Process(IObservable source) + { + if (string.IsNullOrEmpty(_name)) + { + throw new InvalidOperationException("A valid hub device name must be specified."); + } + + var output = source; + foreach (var device in GetDevices()) + { + output = device.Process(output); + } + + return output; + } + } +} diff --git a/OpenEphys.Onix1/NeuropixelsV1eAdc.cs b/OpenEphys.Onix1/NeuropixelsV1eAdc.cs new file mode 100644 index 00000000..f0ea1156 --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV1eAdc.cs @@ -0,0 +1,41 @@ +namespace OpenEphys.Onix1 +{ + /// + /// A class that contains ADC calibration values for a NeuropixelsV1e device. + /// + public class NeuropixelsV1eAdc + { + /// + /// Neuropixels 1.0 CompP calibration setting + /// + public int CompP { get; set; } = 16; + /// + /// Neuropixels 1.0 CompN calibration setting + /// + public int CompN { get; set; } = 16; + /// + /// Neuropixels 1.0 Slope calibration setting + /// + public int Slope { get; set; } = 0; + /// + /// Neuropixels 1.0 Coarse calibration setting + /// + public int Coarse { get; set; } = 0; + /// + /// Neuropixels 1.0 Fine calibration setting + /// + public int Fine { get; set; } = 0; + /// + /// Neuropixels 1.0 Cfix calibration setting + /// + public int Cfix { get; set; } = 0; + /// + /// Neuropixels 1.0 Offset calibration setting + /// + public int Offset { get; set; } = 0; + /// + /// Neuropixels 1.0 Threshold calibration setting + /// + public int Threshold { get; set; } = 512; + } +} diff --git a/OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs b/OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs new file mode 100644 index 00000000..5f9b3917 --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs @@ -0,0 +1,70 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// Produces a sequence of objects from a NeuropixelsV1e headstage. + /// + [Description("Produces a sequence of Bno055DataFrame objects from a NeuropixelsV1e headstage.")] + public class NeuropixelsV1eBno055Data : Source + { + /// + [TypeConverter(typeof(NeuropixelsV1eBno055.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence of objects at approximately 100 Hz. + /// + /// A sequence of objects. + /// + /// This will generate a sequence of objects at approximately 100 Hz. This rate + /// may be limited by the I2C bus. + /// + public override IObservable Generate() + { + // Max of 100 Hz, but limited by I2C bus + var source = Observable.Interval(TimeSpan.FromSeconds(0.01)); + return Generate(source); + } + + /// + /// Generates a sequence of objects. + /// + /// A sequence of objects. + public unsafe IObservable Generate(IObservable source) + { + return DeviceManager.GetDevice(DeviceName).SelectMany( + deviceInfo => Observable.Create(observer => + { + var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV1eBno055)); + var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); + var i2c = new I2CRegisterContext(passthrough, NeuropixelsV1eBno055.BNO055Address); + + return source.SubscribeSafe(observer, _ => + { + Bno055DataFrame frame = default; + device.Context.EnsureContext(() => + { + var data = i2c.ReadBytes(NeuropixelsV1eBno055.DataAddress, sizeof(Bno055DataPayload)); + ulong clock = passthrough.ReadRegister(DS90UB9x.LASTI2CL); + clock += (ulong)passthrough.ReadRegister(DS90UB9x.LASTI2CH) << 32; + fixed (byte* dataPtr = data) + { + frame = new Bno055DataFrame(clock, (Bno055DataPayload*)dataPtr); + } + }); + + if (frame != null) + { + observer.OnNext(frame); + } + }); + })); + } + } +} diff --git a/OpenEphys.Onix1/NeuropixelsV1eData.cs b/OpenEphys.Onix1/NeuropixelsV1eData.cs new file mode 100644 index 00000000..b7513a11 --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV1eData.cs @@ -0,0 +1,90 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// Produces a sequence of objects from a NeuropixelsV1e headstage. + /// + [Description("Produces a sequence of NeuropixelsV1eDataFrame objects from a NeuropixelsV1e headstage.")] + public class NeuropixelsV1eData : Source + { + /// + [TypeConverter(typeof(NeuropixelsV1e.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + int bufferSize = 36; + + /// + /// Gets or sets the buffer size. + /// + /// + /// Buffer size sets the number of super frames that are buffered before propagating data. + /// A super frame consists of 384 channels from the spike-band and 32 channels from the LFP band. + /// The buffer size must be a multiple of 12. + /// + [Description("Number of super-frames (384 channels from spike band and 32 channels from " + + "LFP band) to buffer before propagating data. Must be a multiple of 12.")] + public int BufferSize + { + get => bufferSize; + set => bufferSize = (int)(Math.Ceiling((double)value / NeuropixelsV1e.FramesPerRoundRobin) * NeuropixelsV1e.FramesPerRoundRobin); + } + + /// + /// Generates a sequence of objects. + /// + /// A sequence of objects. + public unsafe override IObservable Generate() + { + var spikeBufferSize = BufferSize; + var lfpBufferSize = spikeBufferSize / NeuropixelsV1e.FramesPerRoundRobin; + + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var info = (NeuropixelsV1eDeviceInfo)deviceInfo; + var device = info.GetDeviceContext(typeof(NeuropixelsV1e)); + var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); + var probeData = device.Context.GetDeviceFrames(passthrough.Address); + + return Observable.Create(observer => + { + var sampleIndex = 0; + var spikeBuffer = new ushort[NeuropixelsV1e.ChannelCount, spikeBufferSize]; + var lfpBuffer = new ushort[NeuropixelsV1e.ChannelCount, lfpBufferSize]; + var frameCountBuffer = new int[spikeBufferSize * NeuropixelsV1e.FramesPerSuperFrame]; + var hubClockBuffer = new ulong[spikeBufferSize]; + var clockBuffer = new ulong[spikeBufferSize]; + + var frameObserver = Observer.Create( + frame => + { + var payload = (NeuropixelsV1ePayload*)frame.Data.ToPointer(); + NeuropixelsV1eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, frameCountBuffer, spikeBuffer, lfpBuffer, sampleIndex, info.ApGainCorrection, info.LfpGainCorrection, info.AdcThresholds, info.AdcOffsets); + hubClockBuffer[sampleIndex] = payload->HubClock; + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= spikeBufferSize) + { + var spikeData = Mat.FromArray(spikeBuffer); + var lfpData = Mat.FromArray(lfpBuffer); + observer.OnNext(new NeuropixelsV1eDataFrame(clockBuffer, hubClockBuffer, frameCountBuffer, spikeData, lfpData)); + frameCountBuffer = new int[spikeBufferSize * NeuropixelsV1e.FramesPerSuperFrame]; + hubClockBuffer = new ulong[spikeBufferSize]; + clockBuffer = new ulong[spikeBufferSize]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + return probeData.SubscribeSafe(frameObserver); + }); + }); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eDataFrame.cs b/OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs similarity index 73% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eDataFrame.cs rename to OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs index 9ca2e1cb..b81105fe 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eDataFrame.cs +++ b/OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs @@ -1,27 +1,55 @@ using System.Runtime.InteropServices; using OpenCV.Net; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - public class NeuropixelsV1eDataFrame + /// + /// Buffered data from a NeuropixelsV1e device. + /// + public class NeuropixelsV1eDataFrame : BufferedDataFrame { + /// + /// Initializes a new instance of the class. + /// + /// An array of values. + /// An array of hub clock counter values. + /// An array of frame count values. + /// An array of multi-channel spike data as a object. + /// An array of multi-channel LFP data as a object. public NeuropixelsV1eDataFrame(ulong[] clock, ulong[] hubClock, int[] frameCount, Mat spikeData, Mat lfpData) + : base(clock, hubClock) { - Clock = clock; - HubClock = hubClock; FrameCount = frameCount; SpikeData = spikeData; LfpData = lfpData; } - public ulong[] Clock { get; } - - public ulong[] HubClock { get; } - + /// + /// Gets the frame count value array. + /// + /// + /// Frame count is a 20-bit counter on the probe that increments its value for every frame produced. + /// The value ranges from 0 to 1048575 (2^20-1), and should always increment by 1 until it wraps around back to 0. + /// This can be used to detect dropped frames. + /// public int[] FrameCount { get; } + /// + /// Gets the spike-band data as a object. + /// + /// + /// Spike-band data has 384 rows (channels) with columns representing the samples acquired at 30 kHz. Each sample is a + /// 10-bit offset binary encoded as an unsigned short value. + /// public Mat SpikeData { get; } + /// + /// Gets the LFP band data as a object. + /// + /// + /// LFP data has 32 rows (channels) with columns representing the samples acquired at 2.5 kHz. Each sample is a + /// 10-bit offset binary encoded as an unsigned short value. + /// public Mat LfpData { get; } internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] frameCountBuffer, ushort[,] spikeBuffer, ushort[,] lfpBuffer, int index, double apGainCorrection, double lfpGainCorrection, ushort[] thresholds, ushort[] offsets) diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eDeviceInfo.cs b/OpenEphys.Onix1/NeuropixelsV1eDeviceInfo.cs similarity index 96% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eDeviceInfo.cs rename to OpenEphys.Onix1/NeuropixelsV1eDeviceInfo.cs index afb00494..d9303449 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eDeviceInfo.cs +++ b/OpenEphys.Onix1/NeuropixelsV1eDeviceInfo.cs @@ -1,6 +1,6 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class NeuropixelsV1eDeviceInfo : DeviceInfo { diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eMetadata.cs b/OpenEphys.Onix1/NeuropixelsV1eMetadata.cs similarity index 98% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eMetadata.cs rename to OpenEphys.Onix1/NeuropixelsV1eMetadata.cs index d55f1a4d..92aa7fc7 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eMetadata.cs +++ b/OpenEphys.Onix1/NeuropixelsV1eMetadata.cs @@ -1,6 +1,6 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class NeuropixelsV1eMetadata { diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eRegisterContext.cs b/OpenEphys.Onix1/NeuropixelsV1eRegisterContext.cs similarity index 93% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eRegisterContext.cs rename to OpenEphys.Onix1/NeuropixelsV1eRegisterContext.cs index 1ed024ad..86dfb067 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV1eRegisterContext.cs +++ b/OpenEphys.Onix1/NeuropixelsV1eRegisterContext.cs @@ -1,9 +1,8 @@ using System; using System.Collections; using System.Linq; -using Bonsai; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class NeuropixelsV1eRegisterContext : I2CRegisterContext { @@ -55,7 +54,7 @@ public NeuropixelsV1eRegisterContext(DeviceContext deviceContext, uint i2cAddres var gainCorrections = gainFile.ReadLine().Split(',').Skip(1); if (gainCorrections.Count() != 2 * NumberOfGains) - throw new ArgumentException("Incorrectly formmatted gain correction calibration file."); + throw new ArgumentException("Incorrectly formatted gain correction calibration file."); ApGainCorrection = double.Parse(gainCorrections.ElementAt(Array.IndexOf(Enum.GetValues(typeof(NeuropixelsV1Gain)), apGain))); LfpGainCorrection = double.Parse(gainCorrections.ElementAt(Array.IndexOf(Enum.GetValues(typeof(NeuropixelsV1Gain)), lfpGain) + 8)); @@ -66,7 +65,7 @@ public NeuropixelsV1eRegisterContext(DeviceContext deviceContext, uint i2cAddres var adcCal = adcFile.ReadLine().Split(',').Skip(1); if (adcCal.Count() != NumberOfGains) { - throw new ArgumentException("Incorrectly formmatted ADC calibration file."); + throw new ArgumentException("Incorrectly formatted ADC calibration file."); } Adcs[i] = new NeuropixelsV1eAdc @@ -134,7 +133,7 @@ public NeuropixelsV1eRegisterContext(DeviceContext deviceContext, uint i2cAddres var chanOptsIdx = BaseConfigurationConfigOffset + ((i - configIdx) * 4); - // MSB [Full, standby, LFPGain(3 downto 0), APGain(3 downto0)] LSB + // MSB [Full, standby, LFPGain(3 downto 0), APGain(3 downto 0)] LSB BaseConfigs[configIdx][chanOptsIdx + 0] = ((byte)apGain >> 0 & 0x1) == 1; BaseConfigs[configIdx][chanOptsIdx + 1] = ((byte)apGain >> 1 & 0x1) == 1; @@ -239,14 +238,10 @@ public void InitializeProbe() WriteByte(NeuropixelsV1e.OP_MODE, (uint)NeuropixelsV1OperationRegisterValues.RECORD); } - // TODO: There is an issue getting these SR write sequences to complete correctly. - // We have a suspicion it is due to the nature of the MCLK signal and that this - // headstage needs either a different oscillator with even more drive strength or - // a clock buffer (second might be easiest). public void WriteConfiguration() { - // shank - // NB: no read check because of ASIC bug + // shank configuration + // NB: no read check because of ASIC bug that is documented in IMEC-API comments var shankBytes = BitArrayToBytes(ShankConfig); WriteByte(NeuropixelsV1e.SR_LENGTH1, (uint)shankBytes.Length % 0x100); @@ -257,16 +252,15 @@ public void WriteConfiguration() WriteByte(NeuropixelsV1e.SR_CHAIN1, b); } - // base + // base configuration for (int i = 0; i < BaseConfigs.Length; i++) { var srAddress = i == 0 ? NeuropixelsV1e.SR_CHAIN2 : NeuropixelsV1e.SR_CHAIN3; for (int j = 0; j < 2; j++) { - // TODO: HACK HACK HACK - // If we do not do this, the ShiftRegisterSuccess check below will always fail - // on whatever the second shift register write sequnece regardless of order or + // WONTFIX: Without this reset, the ShiftRegisterSuccess check below will always fail + // on whatever the second shift register write sequence regardless of order or // contents. Could be increased current draw during internal process causes MCLK // to droop and mess up internal state. Or that MCLK is just not good enough to // prevent metastability in some logic in the ASIC that is only entered in between @@ -294,8 +288,8 @@ public void WriteConfiguration() public void StartAcquisition() { - // TODO: Hack inside settings.WriteShiftRegisters() above puts probe in reset set that needs to be - // undone here + // WONTFIX: Soft reset inside settings.WriteShiftRegisters() above puts probe in reset set that + // needs to be undone here WriteByte(NeuropixelsV1e.OP_MODE, (uint)NeuropixelsV1OperationRegisterValues.RECORD); WriteByte(NeuropixelsV1e.REC_MOD, (uint)NeuropixelsV1RecordRegisterValues.ACTIVE); } diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2.cs b/OpenEphys.Onix1/NeuropixelsV2.cs similarity index 81% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2.cs rename to OpenEphys.Onix1/NeuropixelsV2.cs index 27e9f9a1..5cb1c8e5 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2.cs +++ b/OpenEphys.Onix1/NeuropixelsV2.cs @@ -1,10 +1,19 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { + /// + /// Specifies the probe as A or B. + /// public enum NeuropixelsV2Probe { + /// + /// Specifies that this is Probe A. + /// ProbeA = 0, + /// + /// Specifies that this is Probe B. + /// ProbeB = 1 } diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs rename to OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs index 534ac095..ff30227b 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2QuadShankElectrode.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; using OpenEphys.ProbeInterface; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { public class NeuropixelsV2QuadShankElectrode : Electrode { diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs new file mode 100644 index 00000000..63326222 --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Bonsai; +using Newtonsoft.Json; +using System.Text; +using System.Xml.Serialization; +using System.Linq; + +namespace OpenEphys.Onix1 +{ + /// + /// Specifies the reference for a quad-shank probe. + /// + public enum NeuropixelsV2QuadShankReference : uint + { + /// + /// Specifies that the External reference will be used. + /// + External, + /// + /// Specifies that the tip reference of shank 1 will be used. + /// + Tip1, + /// + /// Specifies that the tip reference of shank 2 will be used. + /// + Tip2, + /// + /// Specifies that the tip reference of shank 3 will be used. + /// + Tip3, + /// + /// Specifies that the tip reference of shank 4 will be used. + /// + Tip4 + } + + /// + /// Specifies the bank of electrodes within each shank. + /// + public enum NeuropixelsV2QuadShankBank + { + /// + /// Specifies that Bank A is the current bank. + /// + /// Bank A is defined as shank index 0 to 383 along each shank. + A, + /// + /// Specifies that Bank B is the current bank. + /// + /// Bank B is defined as shank index 384 to 767 along each shank. + B, + /// + /// Specifies that Bank C is the current bank. + /// + /// Bank C is defined as shank index 768 to 1151 along each shank. + C, + /// + /// Specifies that Bank D is the current bank. + /// + /// + /// Bank D is defined as shank index 1152 to 1279 along each shank. Note that Bank D is not a full contingent + /// of 384 channels; to compensate for this, electrodes from Bank C (starting at shank index 896) are used to + /// generate a full 384 channel map. + /// + D, + } + + /// + /// Defines a configuration for quad-shank probes. + /// + public class NeuropixelsV2QuadShankProbeConfiguration + { + /// + /// Creates a model of the probe with all electrodes instantiated. + /// + [XmlIgnore] + public static readonly IReadOnlyList ProbeModel = CreateProbeModel(); + + /// + /// Initializes a new instance of the class. + /// + public NeuropixelsV2QuadShankProbeConfiguration() + { + ChannelMap = new List(NeuropixelsV2.ChannelCount); + for (int i = 0; i < NeuropixelsV2.ChannelCount; i++) + { + ChannelMap.Add(ProbeModel.FirstOrDefault(e => e.Channel == i)); + } + } + + private static List CreateProbeModel() + { + var electrodes = new List(NeuropixelsV2.ElectrodePerShank * 4); + for (int i = 0; i < NeuropixelsV2.ElectrodePerShank * 4; i++) + { + electrodes.Add(new NeuropixelsV2QuadShankElectrode(i)); + } + return electrodes; + } + + /// + /// Gets or sets the reference for all electrodes. + /// + /// + /// All electrodes are set to the same reference, which can be + /// or any of the tip references + /// (, , etc.). + /// Setting to will use the external reference, while + /// sets the reference to the electrode at the tip of the first shank. + /// + public NeuropixelsV2QuadShankReference Reference { get; set; } = NeuropixelsV2QuadShankReference.External; + + /// + /// Gets the existing channel map listing all currently enabled electrodes. + /// + /// + /// The channel map will always be 384 channels, and will return the 384 enabled electrodes. + /// + [XmlIgnore] + public List ChannelMap { get; } + + /// + /// Update the with the selected electrodes. + /// + /// List of selected electrodes that are being added to the + public void SelectElectrodes(List electrodes) + { + foreach (var e in electrodes) + { + ChannelMap[e.Channel] = e; + } + } + + [XmlIgnore] + [Category("Configuration")] + [Description("Defines the shape of the probe, and which contacts are currently selected for streaming")] + public NeuropixelsV2eProbeGroup ChannelConfiguration { get; private set; } = new(); + + [Browsable(false)] + [Externalizable(false)] + [XmlElement(nameof(ChannelConfiguration))] + public string ChannelConfigurationString + { + get + { + var jsonString = JsonConvert.SerializeObject(ChannelConfiguration); + return Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString)); + } + set + { + var jsonString = Encoding.UTF8.GetString(Convert.FromBase64String(value)); + ChannelConfiguration = JsonConvert.DeserializeObject(jsonString); + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2RegisterContext.cs b/OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2RegisterContext.cs rename to OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs index 30c86d36..e308d3ed 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2RegisterContext.cs +++ b/OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Linq; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class NeuropixelsV2RegisterContext : I2CRegisterContext { diff --git a/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs b/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs new file mode 100644 index 00000000..89b84bb2 --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs @@ -0,0 +1,97 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// Produces a sequence of objects from a NeuropixelsV2eBeta headstage. + /// + [Description("Produces a sequence of NeuropixelsV2eDataFrame objects from a NeuropixelsV2e headstage.")] + public class NeuropixelsV2eBetaData : Source + { + /// + [TypeConverter(typeof(NeuropixelsV2eBeta.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Gets or sets the buffer size. + /// + /// + /// Buffer size sets the number of frames that are buffered before propagating data. + /// + [Description("The number of samples collected for each channel that are used to create a single NeuropixelsV2eBetaDataFrame.")] + public int BufferSize { get; set; } = 30; + + /// + /// Gets or sets the probe index. + /// + [Description("The index of the probe from which to collect sample data")] + public NeuropixelsV2Probe ProbeIndex { get; set; } + + /// + /// Generates a sequence of objects. + /// + /// A sequence of objects. + public unsafe override IObservable Generate() + { + var bufferSize = BufferSize; + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var info = (NeuropixelsV2eDeviceInfo)deviceInfo; + var device = info.GetDeviceContext(typeof(NeuropixelsV2eBeta)); + var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); + var probeData = device.Context + .GetDeviceFrames(passthrough.Address) + .Where(frame => NeuropixelsV2eBetaDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); + + var gainCorrection = ProbeIndex switch + { + NeuropixelsV2Probe.ProbeA => (ushort)info.GainCorrectionA, + NeuropixelsV2Probe.ProbeB => (ushort)info.GainCorrectionB, + _ => throw new ArgumentOutOfRangeException(nameof(ProbeIndex), $"Unexpected ProbeIndex value: {ProbeIndex}"), + }; + + return Observable.Create(observer => + { + var sampleIndex = 0; + var amplifierBuffer = new ushort[NeuropixelsV2.ChannelCount, bufferSize]; + var frameCounter = new int[NeuropixelsV2eBeta.FramesPerSuperFrame * bufferSize]; + var hubClockBuffer = new ulong[bufferSize]; + var clockBuffer = new ulong[bufferSize]; + + var frameObserver = Observer.Create( + frame => + { + var payload = (NeuropixelsV2BetaPayload*)frame.Data.ToPointer(); + NeuropixelsV2eBetaDataFrame.CopyAmplifierBuffer(payload->SuperFrame, amplifierBuffer, frameCounter, sampleIndex, gainCorrection); + hubClockBuffer[sampleIndex] = payload->HubClock; + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= bufferSize) + { + var amplifierData = Mat.FromArray(amplifierBuffer); + var dataFrame = new NeuropixelsV2eBetaDataFrame( + clockBuffer, + hubClockBuffer, + amplifierData, + frameCounter); + observer.OnNext(dataFrame); + frameCounter = new int[NeuropixelsV2eBeta.FramesPerSuperFrame * bufferSize]; + hubClockBuffer = new ulong[bufferSize]; + clockBuffer = new ulong[bufferSize]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + return probeData.SubscribeSafe(frameObserver); + }); + }); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaDataFrame.cs b/OpenEphys.Onix1/NeuropixelsV2eBetaDataFrame.cs similarity index 79% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaDataFrame.cs rename to OpenEphys.Onix1/NeuropixelsV2eBetaDataFrame.cs index c87b240d..4e6fd790 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaDataFrame.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eBetaDataFrame.cs @@ -1,25 +1,41 @@ using System.Runtime.InteropServices; using OpenCV.Net; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - public class NeuropixelsV2eBetaDataFrame + /// + /// Buffered data from a NeuropixelsV2e device. + /// + public class NeuropixelsV2eBetaDataFrame : BufferedDataFrame { - public NeuropixelsV2eBetaDataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData, int[] frameCounter) + /// + /// Initializes a new instance of the class. + /// + /// An array of values. + /// An array of hub clock counter values. + /// An array of multi-channel amplifier data. + /// An array of frame count values. + public NeuropixelsV2eBetaDataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData, int[] frameCount) + : base(clock, hubClock) { - Clock = clock; - HubClock = hubClock; AmplifierData = amplifierData; - FrameCounter = frameCounter; + FrameCount = frameCount; } - public ulong[] Clock { get; } - - public ulong[] HubClock { get; } - + /// + /// Gets the amplifier data array. + /// public Mat AmplifierData { get; } - public int[] FrameCounter { get; } + /// + /// Gets the frame count array. + /// + /// + /// Frame count is a 20-bit counter on the probe that increments its value for every frame produced. + /// The value ranges from 0 to 1048575 (2^20-1), and should always increment by 1 until it wraps around back to 0. + /// This can be used to detect dropped frames. + /// + public int[] FrameCount { get; } internal static unsafe ushort GetProbeIndex(oni.Frame frame) { diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaMetadata.cs b/OpenEphys.Onix1/NeuropixelsV2eBetaMetadata.cs similarity index 98% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaMetadata.cs rename to OpenEphys.Onix1/NeuropixelsV2eBetaMetadata.cs index dc6f2381..a85b044e 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaMetadata.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eBetaMetadata.cs @@ -1,6 +1,6 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class NeuropixelsV2eBetaMetadata { diff --git a/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs b/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs new file mode 100644 index 00000000..9028faf1 --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs @@ -0,0 +1,70 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// Produces a sequence of objects from a NeuropixelsV2e headstage. + /// + [Description("Produces a sequence of Bno055DataFrame objects from a NeuropixelsV2e headstage.")] + public class NeuropixelsV2eBno055Data : Source + { + /// + [TypeConverter(typeof(NeuropixelsV2eBno055.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence of objects at approximately 100 Hz. + /// + /// A sequence of objects. + /// + /// This will generate a sequence of objects at approximately 100 Hz. This rate + /// may be limited by the I2C bus. + /// + public override IObservable Generate() + { + // Max of 100 Hz, but limited by I2C bus + var source = Observable.Interval(TimeSpan.FromSeconds(0.01)); + return Generate(source); + } + + /// + /// Generates a sequence of objects. + /// + /// A sequence of objects. + public unsafe IObservable Generate(IObservable source) + { + return DeviceManager.GetDevice(DeviceName).SelectMany( + deviceInfo => Observable.Create(observer => + { + var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2eBno055)); + var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); + var i2c = new I2CRegisterContext(passthrough, NeuropixelsV2eBno055.BNO055Address); + + return source.SubscribeSafe(observer, _ => + { + Bno055DataFrame frame = default; + device.Context.EnsureContext(() => + { + var data = i2c.ReadBytes(NeuropixelsV2eBno055.DataAddress, sizeof(Bno055DataPayload)); + ulong clock = passthrough.ReadRegister(DS90UB9x.LASTI2CL); + clock += (ulong)passthrough.ReadRegister(DS90UB9x.LASTI2CH) << 32; + fixed (byte* dataPtr = data) + { + frame = new Bno055DataFrame(clock, (Bno055DataPayload*)dataPtr); + } + }); + + if (frame != null) + { + observer.OnNext(frame); + } + }); + })); + } + } +} diff --git a/OpenEphys.Onix1/NeuropixelsV2eData.cs b/OpenEphys.Onix1/NeuropixelsV2eData.cs new file mode 100644 index 00000000..3c161813 --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV2eData.cs @@ -0,0 +1,93 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// Produces a sequence of objects from a NeuropixelsV2e headstage. + /// + [Description("Produces a sequence of NeuropixelsV2eDataFrame objects from a NeuropixelsV2e headstage.")] + public class NeuropixelsV2eData : Source + { + /// + [TypeConverter(typeof(NeuropixelsV2e.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Gets or sets the buffer size. + /// + /// + /// This property determines the number of super-frames that are buffered before data is propagated. A super-frame consists of 384 + /// channels from the spike-band and 32 channels from the LFP band. If this value is set to 30, then 30 super-frames, along with + /// corresponding clock values, will be collected and packed into each . Because channels are + /// sampled at 30 kHz, this is equivalent to 1 millisecond of data from each channel. + /// + [Description("The number of samples collected for each channel that are used to create a single NeuropixelsV2eDataFrame.")] + public int BufferSize { get; set; } = 30; + + /// + /// Gets or sets the probe index. + /// + [Description("The index of the probe from which to collect sample data")] + public NeuropixelsV2Probe ProbeIndex { get; set; } + + /// + /// Generates a sequence of objects. + /// + /// A sequence of objects. + public unsafe override IObservable Generate() + { + var bufferSize = BufferSize; + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var info = (NeuropixelsV2eDeviceInfo)deviceInfo; + var device = info.GetDeviceContext(typeof(NeuropixelsV2e)); + var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); + var probeData = device.Context + .GetDeviceFrames(passthrough.Address) + .Where(frame => NeuropixelsV2eDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); + + var gainCorrection = ProbeIndex switch + { + NeuropixelsV2Probe.ProbeA => (ushort)info.GainCorrectionA, + NeuropixelsV2Probe.ProbeB => (ushort)info.GainCorrectionB, + _ => throw new ArgumentOutOfRangeException(nameof(ProbeIndex), $"Unexpected ProbeIndex value: {ProbeIndex}"), + }; + + return Observable.Create(observer => + { + var sampleIndex = 0; + var amplifierBuffer = new ushort[NeuropixelsV2e.ChannelCount, bufferSize]; + var hubClockBuffer = new ulong[bufferSize]; + var clockBuffer = new ulong[bufferSize]; + + var frameObserver = Observer.Create( + frame => + { + var payload = (NeuropixelsV2Payload*)frame.Data.ToPointer(); + NeuropixelsV2eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, amplifierBuffer, sampleIndex, gainCorrection); + hubClockBuffer[sampleIndex] = payload->HubClock; + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= bufferSize) + { + var amplifierData = Mat.FromArray(amplifierBuffer); + observer.OnNext(new NeuropixelsV2eDataFrame(clockBuffer, hubClockBuffer, amplifierData)); + hubClockBuffer = new ulong[bufferSize]; + clockBuffer = new ulong[bufferSize]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + return probeData.SubscribeSafe(frameObserver); + }); + }); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eDataFrame.cs b/OpenEphys.Onix1/NeuropixelsV2eDataFrame.cs similarity index 87% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eDataFrame.cs rename to OpenEphys.Onix1/NeuropixelsV2eDataFrame.cs index 0a62749a..698f7616 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eDataFrame.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eDataFrame.cs @@ -1,21 +1,28 @@ using System.Runtime.InteropServices; using OpenCV.Net; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - public class NeuropixelsV2eDataFrame + /// + /// Buffered data from a NeuropixelsV2e device. + /// + public class NeuropixelsV2eDataFrame : BufferedDataFrame { + /// + /// Initializes a new instance of the class. + /// + /// An array of values. + /// An array of hub clock counter values. + /// An array of multi-channel amplifier data. public NeuropixelsV2eDataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData) + : base(clock, hubClock) { - Clock = clock; - HubClock = hubClock; AmplifierData = amplifierData; } - public ulong[] Clock { get; } - - public ulong[] HubClock { get; } - + /// + /// Gets the amplifier data array. + /// public Mat AmplifierData { get; } internal static unsafe ushort GetProbeIndex(oni.Frame frame) diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eDeviceInfo.cs b/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs similarity index 95% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eDeviceInfo.cs rename to OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs index d83997c8..5558ef79 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eDeviceInfo.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs @@ -1,6 +1,6 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class NeuropixelsV2eDeviceInfo : DeviceInfo { diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eMetadata.cs b/OpenEphys.Onix1/NeuropixelsV2eMetadata.cs similarity index 98% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eMetadata.cs rename to OpenEphys.Onix1/NeuropixelsV2eMetadata.cs index 31588446..d33204ca 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eMetadata.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eMetadata.cs @@ -1,6 +1,6 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { class NeuropixelsV2eMetadata { diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs similarity index 99% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs rename to OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs index bb7fe9c1..032a8d06 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eProbeGroup.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; using OpenEphys.ProbeInterface; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { public class NeuropixelsV2eProbeGroup : ProbeGroup { diff --git a/OpenEphys.Onix/OpenEphys.Onix/ObservableExtensions.cs b/OpenEphys.Onix1/ObservableExtensions.cs similarity index 70% rename from OpenEphys.Onix/OpenEphys.Onix/ObservableExtensions.cs rename to OpenEphys.Onix1/ObservableExtensions.cs index 15a87ba9..440ec082 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ObservableExtensions.cs +++ b/OpenEphys.Onix1/ObservableExtensions.cs @@ -3,7 +3,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { internal static class ObservableExtensions { @@ -22,6 +22,13 @@ public static IObservable ConfigureDevice(this IObservable context.ConfigureDevice(action), configure); } + public static IObservable ConfigureDevice(this IObservable source, Func, IDisposable> configure) + { + return Observable.Create(observer => source + .ConfigureDevice(context => configure(context, observer)) + .SubscribeSafe(observer)); + } + static IObservable ConfigureContext( this IObservable source, Action> configureContext, @@ -59,5 +66,24 @@ static IObservable ConfigureContext( return source.SubscribeSafe(contextObserver); }); } + + public static IDisposable SubscribeSafe( + this IObservable source, + IObserver observer, + Action onNext) + { + var sourceObserver = Observer.Create( + value => + { + try { onNext(value); } + catch (Exception ex) + { + observer.OnError(ex); + } + }, + observer.OnError, + observer.OnCompleted); + return source.SubscribeSafe(sourceObserver); + } } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/OpenEphys.Onix.csproj b/OpenEphys.Onix1/OpenEphys.Onix1.csproj similarity index 67% rename from OpenEphys.Onix/OpenEphys.Onix/OpenEphys.Onix.csproj rename to OpenEphys.Onix1/OpenEphys.Onix1.csproj index f08fc5d7..6a5a0e95 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/OpenEphys.Onix.csproj +++ b/OpenEphys.Onix1/OpenEphys.Onix1.csproj @@ -1,23 +1,22 @@  - OpenEphys.Onix + OpenEphys.Onix1 Bonsai library containing interfaces for data acquisition and control of ONIX devices. Bonsai Rx Open Ephys Onix net472 true - 0.1.0 x64 - - + + - + diff --git a/OpenEphys.Onix/OpenEphys.Onix/PassthroughState.cs b/OpenEphys.Onix1/PassthroughState.cs similarity index 60% rename from OpenEphys.Onix/OpenEphys.Onix/PassthroughState.cs rename to OpenEphys.Onix1/PassthroughState.cs index 3b31222f..e3448ded 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/PassthroughState.cs +++ b/OpenEphys.Onix1/PassthroughState.cs @@ -1,9 +1,9 @@ using System; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { [Flags] - public enum PassthroughState + internal enum PassthroughState { PortA = 1 << 0, PortB = 1 << 2 diff --git a/OpenEphys.Onix/OpenEphys.Onix/Properties/AssemblyInfo.cs b/OpenEphys.Onix1/Properties/AssemblyInfo.cs similarity index 76% rename from OpenEphys.Onix/OpenEphys.Onix/Properties/AssemblyInfo.cs rename to OpenEphys.Onix1/Properties/AssemblyInfo.cs index 24c8aefd..e8cc65f2 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/Properties/AssemblyInfo.cs +++ b/OpenEphys.Onix1/Properties/AssemblyInfo.cs @@ -3,5 +3,5 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: XmlNamespacePrefix("clr-namespace:OpenEphys.Onix", "onix")] +[assembly: XmlNamespacePrefix("clr-namespace:OpenEphys.Onix1", "onix1")] [assembly: WorkflowNamespaceIcon("")] diff --git a/OpenEphys.Onix/OpenEphys.Onix/Properties/launchSettings.json b/OpenEphys.Onix1/Properties/launchSettings.json similarity index 71% rename from OpenEphys.Onix/OpenEphys.Onix/Properties/launchSettings.json rename to OpenEphys.Onix1/Properties/launchSettings.json index 8e6d143e..e8640d60 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/Properties/launchSettings.json +++ b/OpenEphys.Onix1/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Bonsai": { "commandName": "Executable", - "executablePath": "$(SolutionDir)..\\Bonsai\\Bonsai.exe", + "executablePath": "$(SolutionDir).bonsai/Bonsai.exe", "commandLineArgs": "--lib:$(TargetDir).", "nativeDebugging": true } diff --git a/OpenEphys.Onix1/Rhd2164Config.cs b/OpenEphys.Onix1/Rhd2164Config.cs new file mode 100644 index 00000000..c684d20f --- /dev/null +++ b/OpenEphys.Onix1/Rhd2164Config.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; + +namespace OpenEphys.Onix1 +{ + internal static class Rhd2164Config + { + // Page 26 of RHD2000 datasheet + internal static IReadOnlyList ToLowCutoffToRegisters(Rhd2164AnalogLowCutoff lowCut) => lowCut switch + { + Rhd2164AnalogLowCutoff.Low500Hz => new[] { 13, 0, 0 }, + Rhd2164AnalogLowCutoff.Low300Hz => new[] { 15, 0, 0 }, + Rhd2164AnalogLowCutoff.Low250Hz => new[] { 17, 0, 0 }, + Rhd2164AnalogLowCutoff.Low200Hz => new[] { 18, 0, 0 }, + Rhd2164AnalogLowCutoff.Low150Hz => new[] { 21, 0, 0 }, + Rhd2164AnalogLowCutoff.Low100Hz => new[] { 25, 0, 0 }, + Rhd2164AnalogLowCutoff.Low75Hz => new[] { 28, 0, 0 }, + Rhd2164AnalogLowCutoff.Low50Hz => new[] { 34, 0, 0 }, + Rhd2164AnalogLowCutoff.Low30Hz => new[] { 44, 0, 0 }, + Rhd2164AnalogLowCutoff.Low25Hz => new[] { 48, 0, 0 }, + Rhd2164AnalogLowCutoff.Low20Hz => new[] { 54, 0, 0 }, + Rhd2164AnalogLowCutoff.Low15Hz => new[] { 62, 0, 0 }, + Rhd2164AnalogLowCutoff.Low10Hz => new[] { 5, 1, 0 }, + Rhd2164AnalogLowCutoff.Low7500mHz => new[] { 18, 1, 0 }, + Rhd2164AnalogLowCutoff.Low5000mHz => new[] { 40, 1, 0 }, + Rhd2164AnalogLowCutoff.Low3000mHz => new[] { 20, 2, 0 }, + Rhd2164AnalogLowCutoff.Low2500mHz => new[] { 42, 2, 0 }, + Rhd2164AnalogLowCutoff.Low2000mHz => new[] { 8, 3, 0 }, + Rhd2164AnalogLowCutoff.Low1500mHz => new[] { 9, 4, 0 }, + Rhd2164AnalogLowCutoff.Low1000mHz => new[] { 44, 6, 0 }, + Rhd2164AnalogLowCutoff.Low750mHz => new[] { 49, 9, 0 }, + Rhd2164AnalogLowCutoff.Low500mHz => new[] { 35, 17, 0 }, + Rhd2164AnalogLowCutoff.Low300mHz => new[] { 1, 40, 0 }, + Rhd2164AnalogLowCutoff.Low250mHz => new[] { 56, 54, 0 }, + Rhd2164AnalogLowCutoff.Low100mHz => new[] { 16, 60, 1 }, + _ => throw new ArgumentOutOfRangeException(nameof(lowCut), $"Unsupported low cutoff value : {lowCut}"), + }; + + // Page 25 of RHD2000 datasheet + internal static IReadOnlyList ToHighCutoffToRegisters(Rhd2164AnalogHighCutoff highCut) => highCut switch + { + Rhd2164AnalogHighCutoff.High20000Hz => new[] { 8, 0, 4, 0 }, + Rhd2164AnalogHighCutoff.High15000Hz => new[] { 11, 0, 8, 0 }, + Rhd2164AnalogHighCutoff.High10000Hz => new[] { 17, 0, 16, 0 }, + Rhd2164AnalogHighCutoff.High7500Hz => new[] { 22, 0, 23, 0 }, + Rhd2164AnalogHighCutoff.High5000Hz => new[] { 33, 0, 37, 0 }, + Rhd2164AnalogHighCutoff.High3000Hz => new[] { 3, 1, 13, 1 }, + Rhd2164AnalogHighCutoff.High2500Hz => new[] { 13, 1, 25, 1 }, + Rhd2164AnalogHighCutoff.High2000Hz => new[] { 27, 1, 44, 1 }, + Rhd2164AnalogHighCutoff.High1500Hz => new[] { 1, 2, 23, 2 }, + Rhd2164AnalogHighCutoff.High1000Hz => new[] { 46, 2, 30, 3 }, + Rhd2164AnalogHighCutoff.High750Hz => new[] { 41, 3, 36, 4 }, + Rhd2164AnalogHighCutoff.High500Hz => new[] { 30, 5, 43, 6 }, + Rhd2164AnalogHighCutoff.High300Hz => new[] { 6, 9, 2, 11 }, + Rhd2164AnalogHighCutoff.High250Hz => new[] { 42, 10, 5, 13 }, + Rhd2164AnalogHighCutoff.High200Hz => new[] { 24, 13, 7, 16 }, + Rhd2164AnalogHighCutoff.High150Hz => new[] { 44, 17, 8, 21 }, + Rhd2164AnalogHighCutoff.High100Hz => new[] { 38, 26, 5, 31 }, + _ => throw new ArgumentOutOfRangeException(nameof(highCut), $"Unsupported high cutoff value : {highCut}"), + }; + } + + /// + /// Specifies the lower cutoff frequency of the RHD2164 analog (pre-ADC) bandpass filter. + /// + public enum Rhd2164AnalogLowCutoff + { + /// + /// Specifies 500 Hz. + /// + Low500Hz, + /// + /// Specifies 300 Hz. + /// + Low300Hz, + /// + /// Specifies 250 Hz. + /// + Low250Hz, + /// + /// Specifies 200 Hz. + /// + Low200Hz, + /// + /// Specifies 150 Hz. + /// + Low150Hz, + /// + /// Specifies 100 Hz. + /// + Low100Hz, + /// + /// Specifies 75 Hz. + /// + Low75Hz, + /// + /// Specifies 50 Hz. + /// + Low50Hz, + /// + /// Specifies 30 Hz. + /// + Low30Hz, + /// + /// Specifies 25 Hz. + /// + Low25Hz, + /// + /// Specifies 20 Hz. + /// + Low20Hz, + /// + /// Specifies 15 Hz. + /// + Low15Hz, + /// + /// Specifies 10 Hz. + /// + Low10Hz, + /// + /// Specifies 7.5 Hz. + /// + Low7500mHz, + /// + /// Specifies 5 Hz. + /// + Low5000mHz, + /// + /// Specifies 3 Hz. + /// + Low3000mHz, + /// + /// Specifies 2.5 Hz. + /// + Low2500mHz, + /// + /// Specifies 2 Hz. + /// + Low2000mHz, + /// + /// Specifies 1.5 Hz. + /// + Low1500mHz, + /// + /// Specifies 1 Hz. + /// + Low1000mHz, + /// + /// Specifies 0.75 Hz. + /// + Low750mHz, + /// + /// Specifies 0.5 Hz. + /// + Low500mHz, + /// + /// Specifies 0.3 Hz. + /// + Low300mHz, + /// + /// Specifies 0.25 Hz. + /// + Low250mHz, + /// + /// Specifies 0.1 Hz. + /// + Low100mHz, + } + + /// + /// Specifies the upper cutoff frequency of the RHD2164 analog (pre-ADC) bandpass filter. + /// + public enum Rhd2164AnalogHighCutoff + { + /// + /// Specifies 20 kHz. + /// + High20000Hz, + /// + /// Specifies 15 kHz. + /// + High15000Hz, + /// + /// Specifies 10 kHz. + /// + High10000Hz, + /// + /// Specifies 7.5 kHz. + /// + High7500Hz, + /// + /// Specifies 5 kHz. + /// + High5000Hz, + /// + /// Specifies 3 kHz. + /// + High3000Hz, + /// + /// Specifies 2.5 kHz. + /// + High2500Hz, + /// + /// Specifies 2 kHz. + /// + High2000Hz, + /// + /// Specifies 1.5 kHz. + /// + High1500Hz, + /// + /// Specifies 1 kHz. + /// + High1000Hz, + /// + /// Specifies 750 Hz. + /// + High750Hz, + /// + /// Specifies 500 Hz. + /// + High500Hz, + /// + /// Specifies 300 Hz. + /// + High300Hz, + /// + /// Specifies 250 Hz. + /// + High250Hz, + /// + /// Specifies 200 Hz. + /// + High200Hz, + /// + /// Specifies 150 Hz. + /// + High150Hz, + /// + /// Specifies 100 Hz. + /// + High100Hz, + } + + /// + /// Specifies the cutoff frequency of the RHD2164 digital (post-ADC) high-pass filter. + /// + public enum Rhd2164DspCutoff + { + /// + /// Specifies differences between adjacent samples of each channel (approximate first-order derivative). + /// + Differential = 0, + /// + /// Specifies 3310 Hz. + /// + Dsp3309Hz, + /// + /// Specifies 1370 Hz. + /// + Dsp1374Hz, + /// + /// Specifies 638 Hz. + /// + Dsp638Hz, + /// + /// Specifies 308 Hz. + /// + Dsp308Hz, + /// + /// Specifies 152 Hz. + /// + Dsp152Hz, + /// + /// Specifies 75.2 Hz. + /// + Dsp75Hz, + /// + /// Specifies 37.4 Hz. + /// + Dsp37Hz, + /// + /// Specifies 18.7 Hz. + /// + Dsp19Hz, + /// + /// Specifies 9.34 Hz. + /// + Dsp9336mHz, + /// + /// Specifies 4.67 Hz. + /// + Dsp4665mHz, + /// + /// Specifies 2.33 Hz. + /// + Dsp2332mHz, + /// + /// Specifies 1.17 Hz. + /// + Dsp1166mHz, + /// + /// Specifies 0.583 Hz. + /// + Dsp583mHz, + /// + /// Specifies 0.291 Hz. + /// + Dsp291mHz, + /// + /// Specifies 0.146 Hz. + /// + Dsp146mHz, + /// + /// Specifies that no digital high-pass filtering should be applied. + /// + Off, + } +} diff --git a/OpenEphys.Onix1/Rhd2164Data.cs b/OpenEphys.Onix1/Rhd2164Data.cs new file mode 100644 index 00000000..de6c1686 --- /dev/null +++ b/OpenEphys.Onix1/Rhd2164Data.cs @@ -0,0 +1,82 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Runtime.InteropServices; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that produces a sequence of objects. + /// + /// + /// This data stream class must be linked to an appropriate configuration, such as a , + /// in order to stream electrophysiology data. + /// + [Description("produces a sequence of Rhd2164DataFrame objects.")] + public class Rhd2164Data : Source + { + /// + [TypeConverter(typeof(Rhd2164.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Gets or sets the number of samples collected for each channel that are used to create a single . + /// + /// + /// This property determines the number of samples that are buffered for each electrophysiology and auxiliary channel produced by the RHD2164 chip + /// before data is propagated. For instance, if this value is set to 30, then 30 samples, along with corresponding clock values, will be collected + /// from each of the electrophysiology and auxiliary channels and packed into each . Because channels are sampled at + /// 30 kHz, this is equivalent to 1 millisecond of data from each channel. + /// + [Description("The number of samples collected for each channel that are used to create a single Rhd2164DataFrame.")] + public int BufferSize { get; set; } = 30; + + /// + /// Generates a sequence of objects, each of which are a buffered set of multichannel samples an RHD2164 device. + /// + /// A sequence of objects. + public unsafe override IObservable Generate() + { + var bufferSize = BufferSize; + return DeviceManager.GetDevice(DeviceName).SelectMany( + deviceInfo => Observable.Create(observer => + { + var sampleIndex = 0; + var device = deviceInfo.GetDeviceContext(typeof(Rhd2164)); + var amplifierBuffer = new short[Rhd2164.AmplifierChannelCount * bufferSize]; + var auxBuffer = new short[Rhd2164.AuxChannelCount * bufferSize]; + var hubClockBuffer = new ulong[bufferSize]; + var clockBuffer = new ulong[bufferSize]; + + var frameObserver = Observer.Create( + frame => + { + var payload = (Rhd2164Payload*)frame.Data.ToPointer(); + Marshal.Copy(new IntPtr(payload->AmplifierData), amplifierBuffer, sampleIndex * Rhd2164.AmplifierChannelCount, Rhd2164.AmplifierChannelCount); + Marshal.Copy(new IntPtr(payload->AuxData), auxBuffer, sampleIndex * Rhd2164.AuxChannelCount, Rhd2164.AuxChannelCount); + hubClockBuffer[sampleIndex] = payload->HubClock; + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= bufferSize) + { + var amplifierData = BufferHelper.CopyTranspose(amplifierBuffer, bufferSize, Rhd2164.AmplifierChannelCount, Depth.U16); + var auxData = BufferHelper.CopyTranspose(auxBuffer, bufferSize, Rhd2164.AuxChannelCount, Depth.U16); + observer.OnNext(new Rhd2164DataFrame(clockBuffer, hubClockBuffer, amplifierData, auxData)); + hubClockBuffer = new ulong[bufferSize]; + clockBuffer = new ulong[bufferSize]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .SubscribeSafe(frameObserver); + })); + } + } +} diff --git a/OpenEphys.Onix1/Rhd2164DataFrame.cs b/OpenEphys.Onix1/Rhd2164DataFrame.cs new file mode 100644 index 00000000..7df74d84 --- /dev/null +++ b/OpenEphys.Onix1/Rhd2164DataFrame.cs @@ -0,0 +1,51 @@ +using System.Runtime.InteropServices; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that contains electrophysiology data produced by an RHD2164 bioamplifier chip. + /// + public class Rhd2164DataFrame : BufferedDataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// An array of values. + /// An array of hub clock counter values. + /// An array of RHD2164 multi-channel electrophysiology data. + /// An array of RHD2164 auxiliary channel data. + public Rhd2164DataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData, Mat auxData) + : base(clock, hubClock) + { + AmplifierData = amplifierData; + AuxData = auxData; + } + + /// + /// Gets the buffered electrophysiology data array. + /// + /// + /// Each row corresponds to a channel. Each column corresponds to a sample whose time is indicated by + /// the corresponding element and . + /// + public Mat AmplifierData { get; } + + /// + /// Gets the buffered auxiliary data array. + /// + /// + /// Each row corresponds to a channel. Each column corresponds to a sample whose time is indicated by + /// the corresponding element and . + /// + public Mat AuxData { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + unsafe struct Rhd2164Payload + { + public ulong HubClock; + public fixed ushort AmplifierData[Rhd2164.AmplifierChannelCount]; + public fixed ushort AuxData[Rhd2164.AuxChannelCount]; + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/HubDeviceConverter.cs b/OpenEphys.Onix1/SingleDeviceFactoryConverter.cs similarity index 91% rename from OpenEphys.Onix/OpenEphys.Onix/HubDeviceConverter.cs rename to OpenEphys.Onix1/SingleDeviceFactoryConverter.cs index e83622b6..da34aaa7 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/HubDeviceConverter.cs +++ b/OpenEphys.Onix1/SingleDeviceFactoryConverter.cs @@ -3,9 +3,9 @@ using System.Globalization; using System.Linq; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { - internal class HubDeviceConverter : ExpandableObjectConverter + internal class SingleDeviceFactoryConverter : ExpandableObjectConverter { public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { diff --git a/OpenEphys.Onix/OpenEphys.Onix/StackDisposable.cs b/OpenEphys.Onix1/StackDisposable.cs similarity index 97% rename from OpenEphys.Onix/OpenEphys.Onix/StackDisposable.cs rename to OpenEphys.Onix1/StackDisposable.cs index e2a73353..f32b69ce 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/StackDisposable.cs +++ b/OpenEphys.Onix1/StackDisposable.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Reactive.Disposables; -namespace OpenEphys.Onix +namespace OpenEphys.Onix1 { internal class StackDisposable : IDisposable { diff --git a/OpenEphys.Onix1/StartAcquisition.cs b/OpenEphys.Onix1/StartAcquisition.cs new file mode 100644 index 00000000..6474acd8 --- /dev/null +++ b/OpenEphys.Onix1/StartAcquisition.cs @@ -0,0 +1,75 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// Starts data acquisition and frame distribution on a . + /// + [Description("Starts data acquisition and frame distribution on a ContextTask.")] + public class StartAcquisition : Combinator> + { + /// + /// Gets or sets the number of bytes read by the device driver access to the read channel. + /// + /// + /// This option allows control over a fundamental trade-off between closed-loop response time and overall bandwidth. + /// A minimal value, which is determined by , will provide the lowest response latency, + /// so long as data can be cleared from hardware memory fast enough to prevent buffering. Larger values will reduce system + /// call frequency, increase overall bandwidth, and may improve processing performance for high-bandwidth data sources. + /// The optimal value depends on the host computer and hardware configuration and must be determined via testing (e.g. + /// using ). + /// + [Description("The number of bytes read by the device driver access to the read channel.")] + public int ReadSize { get; set; } = 2048; + + /// + /// Gets or sets the number of bytes that are pre-allocated for writing data to hardware. + /// + /// + /// This value determines the amount of memory pre-allocated for calls to , + /// , and . A larger size will reduce + /// the average amount of dynamic memory allocation system calls but increase the cost of each of those calls. The minimum + /// size of this option is determined by . The effect on real-timer performance is not as + /// large as that of . + /// + [Description("The number of bytes that are pre-allocated for writing data to hardware.")] + public int WriteSize { get; set; } = 2048; + + /// + /// Starts data acquisition and frame distribution on a and returns + /// the sequence of all received objects, grouped by device address. + /// + /// + /// The sequence of objects on which to start data acquisition + /// and frame distribution. + /// + /// + /// A sequence of objects for each , + /// grouped by device address. + /// + public override IObservable> Process(IObservable source) + { + return source.SelectMany(context => + { + return Observable.Create>((observer, cancellationToken) => + { + var frameSubscription = context.GroupedFrames.SubscribeSafe(observer); + try + { + return context.StartAsync(ReadSize, WriteSize, cancellationToken) + .ContinueWith(_ => frameSubscription.Dispose()); + } + catch + { + frameSubscription.Dispose(); + throw; + } + }); + }); + } + } +} diff --git a/OpenEphys.Onix1/TS4231V1Data.cs b/OpenEphys.Onix1/TS4231V1Data.cs new file mode 100644 index 00000000..e742ec8b --- /dev/null +++ b/OpenEphys.Onix1/TS4231V1Data.cs @@ -0,0 +1,49 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that produces a sequence of decoded optical signals produced by a pair of SteamVR V1 base stations. + /// + /// + /// + /// This data stream class must be linked to an appropriate configuration, such as a , + /// in order to stream 3D position data. + /// + /// + /// The data produced by this class contains individual base station pulse/sweep codes and timing information. These data provide + /// rapid updates that constrain the possible position of a sensor and therefore can be combined with orientation information + /// in a downstream predictive model (e.g. Kalman filter) for high-accuracy and robust position tracking. To produce naïve + /// position estimates, use the operator instead of this one. + /// + /// + [Description("Produces a sequence of decoded optical signals produced by a pair of SteamVR V1 base stations.")] + public class TS4231V1Data : Source + { + /// + [TypeConverter(typeof(TS4231V1.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence of objects, each of which contains information on a single + /// lighthouse optical sweep or pulse. + /// + /// A sequence of objects. + public override IObservable Generate() + { + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(TS4231V1)); + var hubClockPeriod = 1e6 / device.Hub.ClockHz; + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .Select(frame => new TS4231V1DataFrame(frame, hubClockPeriod)); + }); + } + } +} diff --git a/OpenEphys.Onix1/TS4231V1DataFrame.cs b/OpenEphys.Onix1/TS4231V1DataFrame.cs new file mode 100644 index 00000000..3a7c8580 --- /dev/null +++ b/OpenEphys.Onix1/TS4231V1DataFrame.cs @@ -0,0 +1,97 @@ +using System.Runtime.InteropServices; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that contains information about a single synchronization pulse or light sweep from a SteamVR V1 base station. + /// + public class TS4231V1DataFrame : DataFrame + { + + /// + /// Initializes a new instance of the class. + /// + /// An produced by a TS4231 device + /// The period of the TS4231 devices local clock in Hz + public unsafe TS4231V1DataFrame(oni.Frame frame, double hubClockPeriod) + : base(frame.Clock) + { + var payload = (TS4231V1Payload*)frame.Data.ToPointer(); + HubClock = payload->HubClock; + SensorIndex = payload->SensorIndex; + EnvelopeWidth = 1e6 * hubClockPeriod * payload->EnvelopeWidth; + EnvelopeType = payload->EnvelopeType; + } + + /// + /// Gets the index of the TS4231 sensor that produced this data. + /// + public int SensorIndex { get; } + + /// + /// Gets the width of the envelope of the modulated optical pulse or sweep in microseconds. + /// + public double EnvelopeWidth { get; } + + /// + /// Gets the pulse or sweep classification. + /// + public TS4231V1Envelope EnvelopeType { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct TS4231V1Payload + { + public ulong HubClock; + public ushort SensorIndex; + public uint EnvelopeWidth; + public TS4231V1Envelope EnvelopeType; + } + + /// + /// Specifies the SteamVR V1 base station optical signal classification. + /// + public enum TS4231V1Envelope : short + { + /// + /// Specifies and invalid optical signal. + /// + Bad = -1, + /// + /// Specifies a synchronization pulse with 50.0 μS < width ≤ 62.5 μS + /// + J0, + /// + /// Specifies a synchronization pulse with 62.5 μS < width ≤ 72.9 μS + /// + K0, + /// + /// Specifies a synchronization pulse with 72.9 μS < width ≤ 83.3 μS + /// + J1, + /// + /// Specifies a synchronization pulse with 83.3 μS < width ≤ 93.8 μS + /// + K1, + /// + /// Specifies a synchronization pulse with 93.8 μS < width ≤ 104 μS + /// + J2, + /// + /// Specifies a synchronization pulse with 104 μS < width ≤ 115 μS + /// + K2, + /// + /// Specifies a synchronization pulse with 115 μS < width ≤ 125 μS + /// + J3, + /// + /// Specifies a synchronization pulse with 125 μS < width ≤ 135 μS + /// + K3, + /// + /// Specifies a light sheet sweep (width ≤ 50 μS) + /// + Sweep, + } +} diff --git a/OpenEphys.Onix1/TS4231V1PositionConverter.cs b/OpenEphys.Onix1/TS4231V1PositionConverter.cs new file mode 100644 index 00000000..c4b0e123 --- /dev/null +++ b/OpenEphys.Onix1/TS4231V1PositionConverter.cs @@ -0,0 +1,164 @@ +using OpenCV.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Reactive.Linq; + +namespace OpenEphys.Onix1 +{ + class TS4231V1PulseQueue + { + public Queue PulseFrameClock { get; } = new(new ulong[TS4231V1PositionConverter.ValidPulseSequenceTemplate.Length / 4]); + public Queue PulseHubClock { get; } = new(new ulong[TS4231V1PositionConverter.ValidPulseSequenceTemplate.Length / 4]); + public Queue PulseWidths { get; } = new(new double[TS4231V1PositionConverter.ValidPulseSequenceTemplate.Length / 4]); + public Queue PulseParse { get; } = new(new bool[TS4231V1PositionConverter.ValidPulseSequenceTemplate.Length]); + } + + class TS4231V1PositionConverter + { + const double SweepFrequencyHz = 60; + readonly double HubClockFrequencyPeriod; + readonly Mat p; + readonly Mat q; + + // Template pattern + internal static readonly bool[] ValidPulseSequenceTemplate = { + // bad skip axis sweep + false, false, false, false, + false, true, false, false, + false, false, false, true, // axis 0, station 0 + false, false, true, false, + false, true, true, false, + false, false, false, true, // axis 1, station 0 + false, true, false, false, + false, false, false, false, + false, false, false, true, // axis 0, station 1 + false, true, true, false, + false, false, true, false, + false, false, false, true // axis 1, station 1 + }; + + Dictionary PulseQueues = new(); + + public TS4231V1PositionConverter(uint hubClockFrequencyHz, Point3d baseStation1Origin, Point3d baseStation2Origin) + { + HubClockFrequencyPeriod = 1d / hubClockFrequencyHz; + + p = new Mat(3, 1, Depth.F64, 1); + p[0] = new Scalar(baseStation1Origin.X); + p[1] = new Scalar(baseStation1Origin.Y); + p[2] = new Scalar(baseStation1Origin.Z); + + q = new Mat(3, 1, Depth.F64, 1); + q[0] = new Scalar(baseStation2Origin.X); + q[1] = new Scalar(baseStation2Origin.Y); + q[2] = new Scalar(baseStation2Origin.Z); + } + + public unsafe TS4231V1PositionDataFrame Convert(oni.Frame frame) + { + var payload = (TS4231V1Payload*)frame.Data.ToPointer(); + + if (!PulseQueues.ContainsKey(payload->SensorIndex)) + PulseQueues.Add(payload->SensorIndex, new TS4231V1PulseQueue()); + + var queues = PulseQueues[payload->SensorIndex]; + + // Push pulse time into buffer and pop oldest + queues.PulseFrameClock.Dequeue(); + queues.PulseFrameClock.Enqueue(frame.Clock); + + queues.PulseHubClock.Dequeue(); + queues.PulseHubClock.Enqueue(payload->HubClock); + + // Push pulse width into buffer and pop oldest + queues.PulseWidths.Dequeue(); + queues.PulseWidths.Enqueue(HubClockFrequencyPeriod * payload->EnvelopeWidth); + + // push pulse code categorization into buffer and pop oldest 4x + queues.PulseParse.Dequeue(); + queues.PulseParse.Dequeue(); + queues.PulseParse.Dequeue(); + queues.PulseParse.Dequeue(); + queues.PulseParse.Enqueue(payload->EnvelopeType == TS4231V1Envelope.Bad); + queues.PulseParse.Enqueue(payload->EnvelopeType >= TS4231V1Envelope.J2 & payload->EnvelopeType != TS4231V1Envelope.Sweep); // skip + queues.PulseParse.Enqueue((int)payload->EnvelopeType % 2 == 1 & payload->EnvelopeType != TS4231V1Envelope.Sweep); // axis + queues.PulseParse.Enqueue(payload->EnvelopeType == TS4231V1Envelope.Sweep); // sweep + + // convert to arrays + var times = queues.PulseHubClock.Select(x => HubClockFrequencyPeriod * x).ToArray(); + var widths = queues.PulseWidths.ToArray(); + + // test template match and make sure time between pulses does not integrate to more than two periods + if (!queues.PulseParse.SequenceEqual(ValidPulseSequenceTemplate) || + times.Last() - times.First() > 2 / SweepFrequencyHz) + { + return null; + } + + var t11 = times[2] + widths[2] / 2 - times[0]; + var t21 = times[5] + widths[5] / 2 - times[3]; + var theta0 = 2 * Math.PI * SweepFrequencyHz * t11 - Math.PI / 2; + var gamma0 = 2 * Math.PI * SweepFrequencyHz * t21 - Math.PI / 2; + + var u = new Mat(3, 1, Depth.F64, 1); + u[0] = new Scalar(Math.Tan(theta0)); + u[1] = new Scalar(Math.Tan(gamma0)); + u[2] = new Scalar(1); + CV.Normalize(u, u); + + var t12 = times[8] + widths[8] / 2 - times[7]; + var t22 = times[11] + widths[11] / 2 - times[10]; + var theta1 = 2 * Math.PI * SweepFrequencyHz * t12 - Math.PI / 2; + var gamma1 = 2 * Math.PI * SweepFrequencyHz * t22 - Math.PI / 2; + + var v = new Mat(3, 1, Depth.F64, 1); + v[0] = new Scalar(Math.Tan(theta1)); + v[1] = new Scalar(Math.Tan(gamma1)); + v[2] = new Scalar(1); + CV.Normalize(v, v); + + // Base station origin vector + var d = q - p; + + // Linear transform + // A = [a11 a12] + // [a21 a22] + var a11 = 1.0; + var a12 = -CV.DotProduct(u, v); + var a21 = CV.DotProduct(u, v); + var a22 = -1.0; + + // Result + // B = [b1] + // [b2] + var b1 = CV.DotProduct(u, d); + var b2 = CV.DotProduct(v, d); + + // Solve Ax = B + var x2 = (b2 - (b1 * a21) / a11) / (a22 - (a12 * a21) / a11); + var x1 = (b1 - a12 * x2) / a11; + + // If singular, return null + if (double.IsNaN(x1) || + double.IsNaN(x2) || + double.IsInfinity(x1) || + double.IsInfinity(x2)) + { + return null; + } + + // calculate position + var p1 = p + x1 * u; + var q1 = q + x2 * v; + var position = 0.5 * (p1 + q1); + + return new TS4231V1PositionDataFrame( + queues.PulseHubClock.ElementAt(ValidPulseSequenceTemplate.Length / 8), + queues.PulseFrameClock.ElementAt(ValidPulseSequenceTemplate.Length / 8), + payload->SensorIndex, + new Vector3((float)position[0].Val0, (float)position[1].Val0, (float)position[2].Val0)); + } + } +} diff --git a/OpenEphys.Onix1/TS4231V1PositionData.cs b/OpenEphys.Onix1/TS4231V1PositionData.cs new file mode 100644 index 00000000..a7d27c72 --- /dev/null +++ b/OpenEphys.Onix1/TS4231V1PositionData.cs @@ -0,0 +1,95 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that produces a sequence of 3D positions from an array of Triad Semiconductor TS4231 receivers beneath + /// a pair of SteamVR V1 base stations. + /// + /// + /// + /// This data stream class must be linked to an appropriate configuration, such as a , + /// in order to stream data. + /// + /// + /// The data produced by this class contains naïve geometric estimates of positions of photodiodes attached to each TS4231 chip. + /// This operator makes the following assumptions about the setup: + /// + /// Two SteamVR V1 base stations are used. + /// The base stations have been synchronized with a patch cable and their modes set to ‘A’ and ‘b’, respectively. + /// The base stations are pointed in the same direction. + /// The Z-axis extends away the emitting face of lighthouses, X along the direction of the text on the back label, + /// and Y from bottom to top text on the back label. + /// + /// This operator collects a sequence of objects from each TS3231 receiver that are used to determine the ray from each + /// base station to the TS3231's photodiode. A simple geometric inversion is performed to determine the photodiodes 3D position from the values + /// and . It does not use a predictive model or integrate data from an IMU and is therefore quite sensitive to + /// obstructions in and will require post-hoc processing to correct systematic errors due to optical aberrations and nonlinearities. The the + /// operator provides access to individual lighthouse signals that is useful for a creating more robust position + /// estimates using downstream processing. + /// + /// + [Description("Produces a sequence of 3D positions from an array of Triad Semiconductor TS4231 receivers beneath a pair of SteamVR V1 base stations.")] + public class TS4231V1PositionData : Source + { + /// + [TypeConverter(typeof(TS4231V1.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Gets or sets the position of the first base station in arbitrary units. + /// + /// + /// The units used will determine the units of and must match those used in . + /// Typically this value is used to define the origin and remains at (0, 0, 0). + /// + [Description("The position of the first base station in arbitrary units.")] + public Point3d P { get; set; } = new(0, 0, 0); + + /// + /// Gets or sets the position of the second base station in arbitrary units. + /// + /// + /// The units used will determine the units of and must match those used in . + /// + [Description("The position of the second base station in arbitrary units.")] + public Point3d Q { get; set; } = new(1, 0, 0); + + /// + /// Generates a sequence of objects, each of which contains the 3D position of single photodiode. + /// + /// A sequence of objects. + public unsafe override IObservable Generate() + { + return DeviceManager.GetDevice(DeviceName).SelectMany( + deviceInfo => Observable.Create(observer => + { + var device = deviceInfo.GetDeviceContext(typeof(TS4231V1)); + var pulseConverter = new TS4231V1PositionConverter(device.Hub.ClockHz, P, Q); + + var frameObserver = Observer.Create( + frame => + { + var position = pulseConverter.Convert(frame); + if (position != null) + { + observer.OnNext(position); + } + }, + observer.OnError, + observer.OnCompleted); + + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .SubscribeSafe(frameObserver); + })); + } + } +} diff --git a/OpenEphys.Onix1/TS4231V1PositionDataFrame.cs b/OpenEphys.Onix1/TS4231V1PositionDataFrame.cs new file mode 100644 index 00000000..2bddb468 --- /dev/null +++ b/OpenEphys.Onix1/TS4231V1PositionDataFrame.cs @@ -0,0 +1,48 @@ +using System.Numerics; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that contains the 3D position of a photodiode in a TS4231 sensor array relative + /// to a given SteamVR V1 base station origin. + /// + /// + /// A sequence of 12 objects produced by a single TS4231 sensor are required to + /// geometrically calculate the position of the sensor's photodiode in 3D space. + /// + public class TS4231V1PositionDataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// The median value of the 12 frames required to construct a single position. + /// The median value of the 12 frames required to construct a single position. + /// The index of the TS4231 sensor that the 3D position corresponds to. + /// The 3 dimensional position of the photodiode connected to the TS4231 sensor with units determined by + /// and . + public TS4231V1PositionDataFrame(ulong clock, ulong hubClock, int sensorIndex, Vector3 position) + { + Clock = clock; + HubClock = hubClock; + SensorIndex = sensorIndex; + Position = position; + } + + /// + public ulong Clock { get; } + + /// + public ulong HubClock { get; } + + /// + /// Gets the index of the TS4231 sensor that produced this data. + /// + public int SensorIndex { get; } + + /// + /// Gets rhe 3D position of the photodiode connected to the TS4231[] sensor with units determined by + /// and . + /// + public Vector3 Position { get; } + } +} diff --git a/OpenEphys.ProbeInterface b/OpenEphys.ProbeInterface new file mode 160000 index 00000000..411f31e5 --- /dev/null +++ b/OpenEphys.ProbeInterface @@ -0,0 +1 @@ +Subproject commit 411f31e5dad40c379c9b5f608ac00f6e15967a17 diff --git a/README.md b/README.md index eed4c535..90e7baeb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ -# onix-refactor -A project for refactoring the ONIX bonsai library +# Onix1 Bonsai Library +[Bonsai](https://bonsai-rx.org/) library for the [Open Ephys Onix +Acquisition System](https://open-ephys.github.io/onix-docs). + +- Open Ephys store: https://open-ephys.org/onix +- Library documentation: https://open-ephys.github.io/onix1-bonsai-docs +- Hardware documentation: https://open-ephys.github.io/onix-docs + diff --git a/build/Version.props b/build/Version.props new file mode 100644 index 00000000..d7d1fa24 --- /dev/null +++ b/build/Version.props @@ -0,0 +1,20 @@ + + + + 0 + + dev$(DevVersion) + <_FileVersionRevision>$([MSBuild]::Add(60000, $(DevVersion))) + + + + $(CiBuildVersionSuffix) + <_FileVersionRevision>0 + <_FileVersionRevision Condition="'$(CiBuildVersionSuffix)' != '' and '$(CiRunNumber)' != ''">$(CiRunNumber) + + + + + $(WarningsAsErrors);CS7035 + + \ No newline at end of file From 1aed2b8fde9e63047c91e720190a008606907400 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 2 Aug 2024 13:09:10 -0400 Subject: [PATCH 8/8] Add ProbeInterface project back to solution --- OpenEphys.Onix1.sln | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/OpenEphys.Onix1.sln b/OpenEphys.Onix1.sln index 77983339..e82153ce 100644 --- a/OpenEphys.Onix1.sln +++ b/OpenEphys.Onix1.sln @@ -12,20 +12,40 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenEphys.ProbeInterface", "OpenEphys.ProbeInterface\OpenEphys.ProbeInterface\OpenEphys.ProbeInterface.csproj", "{CDC8058A-48DD-49FE-BFC8-9F12F353D29D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|Any CPU.ActiveCfg = Debug|x64 + {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|Any CPU.Build.0 = Debug|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|x64.ActiveCfg = Debug|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Debug|x64.Build.0 = Debug|x64 + {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|Any CPU.ActiveCfg = Release|x64 + {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|Any CPU.Build.0 = Release|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|x64.ActiveCfg = Release|x64 {353B1EBC-F8EB-4D99-8331-9FF15EC17F38}.Release|x64.Build.0 = Release|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|Any CPU.ActiveCfg = Debug|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|Any CPU.Build.0 = Debug|x64 {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|x64.ActiveCfg = Debug|x64 {149E86EC-B865-463D-81A8-8290CA7F8871}.Debug|x64.Build.0 = Debug|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|Any CPU.ActiveCfg = Release|x64 + {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|Any CPU.Build.0 = Release|x64 {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|x64.ActiveCfg = Release|x64 {149E86EC-B865-463D-81A8-8290CA7F8871}.Release|x64.Build.0 = Release|x64 + {CDC8058A-48DD-49FE-BFC8-9F12F353D29D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDC8058A-48DD-49FE-BFC8-9F12F353D29D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDC8058A-48DD-49FE-BFC8-9F12F353D29D}.Debug|x64.ActiveCfg = Debug|Any CPU + {CDC8058A-48DD-49FE-BFC8-9F12F353D29D}.Debug|x64.Build.0 = Debug|Any CPU + {CDC8058A-48DD-49FE-BFC8-9F12F353D29D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDC8058A-48DD-49FE-BFC8-9F12F353D29D}.Release|Any CPU.Build.0 = Release|Any CPU + {CDC8058A-48DD-49FE-BFC8-9F12F353D29D}.Release|x64.ActiveCfg = Release|Any CPU + {CDC8058A-48DD-49FE-BFC8-9F12F353D29D}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE