From 8f889dfab5562ad8329b3383bd201aefdce4e6ba Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 12 Jul 2024 15:54:52 -0400 Subject: [PATCH 01/35] 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 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 + + + + 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 02/35] 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 03/35] 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 04/35] 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 05/35] 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 06/35] 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 07/35] 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 08/35] 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 From 1ae7b3aae15d03c9cba593058e19f000571d6ecb Mon Sep 17 00:00:00 2001 From: bparks13 Date: Mon, 5 Aug 2024 14:50:47 -0400 Subject: [PATCH 09/35] Address review comments - Based on feedback given from issue-186 - Update variable names to be more clear - Formatting fixes - Update XML comments to reflect convention correctly - Add all missing XML comments - Refactor to use NeuropixelsV2QuadShankProbeConfiguration directly --- .../ChannelConfigurationDialog.cs | 10 +- OpenEphys.Onix1.Design/ContactTag.cs | 89 +++--- OpenEphys.Onix1.Design/DesignHelper.cs | 15 +- ...europixelsV2eChannelConfigurationDialog.cs | 63 ++-- .../NeuropixelsV2eDialog.cs | 300 +++++++++--------- .../NeuropixelsV2eEditor.cs | 4 + .../OpenEphys.Onix1.Design.csproj | 2 +- OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs | 4 + .../ConfigureNeuropixelsV2eBeta.cs | 4 +- .../ConfigureNeuropixelsV2eBno055.cs | 4 + OpenEphys.Onix1/Electrode.cs | 26 +- .../NeuropixelsV2QuadShankElectrode.cs | 94 +++--- ...europixelsV2QuadShankProbeConfiguration.cs | 40 ++- .../NeuropixelsV2RegisterContext.cs | 6 +- OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs | 2 +- 15 files changed, 351 insertions(+), 312 deletions(-) diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index 0d968aed..67baaa50 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -19,9 +19,9 @@ public abstract partial class ChannelConfigurationDialog : Form /// /// Local variable that holds the channel configuration in memory until the user presses Okay /// - internal ProbeGroup ChannelConfiguration; + ProbeGroup ChannelConfiguration; - internal List ReferenceContacts = new(); + internal readonly List ReferenceContacts = new(); internal readonly bool[] SelectedContacts = null; @@ -324,7 +324,7 @@ internal virtual void HighlightEnabledContacts() { var tag = (ContactTag)contact.Tag; - contact.Fill.Color = ReferenceContacts.Any(x => x == tag.ContactNumber) ? ReferenceContactFill : EnabledContactFill; + contact.Fill.Color = ReferenceContacts.Any(x => x == tag.ContactIndex) ? ReferenceContactFill : EnabledContactFill; } } } @@ -844,7 +844,7 @@ private void ToggleSelectedContact(ContactTag tag) private void SetSelectedContact(ContactTag contactTag, bool status) { - SetSelectedContact(contactTag.ContactNumber, status); + SetSelectedContact(contactTag.ContactIndex, status); } private void SetSelectedContact(int index, bool status) @@ -866,7 +866,7 @@ internal void SetAllSelections(bool newStatus) private bool GetContactStatus(ContactTag tag) { - return SelectedContacts[tag.ContactNumber]; + return SelectedContacts[tag.ContactIndex]; } private static PointD TransformPixelsToCoordinates(Point pixels, GraphPane graphPane) diff --git a/OpenEphys.Onix1.Design/ContactTag.cs b/OpenEphys.Onix1.Design/ContactTag.cs index 6c899d8f..312a4db9 100644 --- a/OpenEphys.Onix1.Design/ContactTag.cs +++ b/OpenEphys.Onix1.Design/ContactTag.cs @@ -2,68 +2,51 @@ namespace OpenEphys.Onix1.Design { + /// + /// Public class used to create tags for contacts in their respective GUIs. + /// 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) + const string ContactStringFormat = "Probe_{0}-Contact_{1}"; + const string TextStringFormat = "TextProbe_{0}-Contact_{1}"; + + /// + /// Gets the probe index of this contact. + /// + public int ProbeIndex { get; } + + /// + /// Gets the contact index of this contact. + /// + public int ContactIndex; + + /// + /// Gets the string defining the probe and contact index for this contact. + /// + public string ContactString => GetContactString(ProbeIndex, ContactIndex); + + /// + /// Gets the string defining the probe and contact index of a text object for this contact. + /// + public string TextString => GetTextString(ProbeIndex, ContactIndex); + + /// + /// Initializes a new instance of the class with the given indices. + /// + /// Index of the probe for this contact. + /// Index of the contact for this contact. + public ContactTag(int probeIndex, int contactIndex) { - 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; + ProbeIndex = probeIndex; + ContactIndex = contactIndex; } - public static string GetContactString(int probeNumber, int contactNumber) + static string GetContactString(int probeNumber, int contactNumber) { return string.Format(ContactStringFormat, probeNumber, contactNumber); } - public static string GetTextString(int probeNumber, int contactNumber) + static string GetTextString(int probeNumber, int contactNumber) { return string.Format(TextStringFormat, probeNumber, contactNumber); } diff --git a/OpenEphys.Onix1.Design/DesignHelper.cs b/OpenEphys.Onix1.Design/DesignHelper.cs index f179e00c..edc41798 100644 --- a/OpenEphys.Onix1.Design/DesignHelper.cs +++ b/OpenEphys.Onix1.Design/DesignHelper.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows.Forms; @@ -7,7 +6,7 @@ namespace OpenEphys.Onix1.Design { - public static class DesignHelper + static class DesignHelper { public static T DeserializeString(string channelLayout) { @@ -140,15 +139,5 @@ public static void AddMenuItemsFromDialogToFileOption(this Form thisForm, Form f } } } - - public static List SelectElectrodes(this List channelMap, List electrodes) where TElectrode : Electrode - { - foreach (var e in electrodes) - { - channelMap[e.Channel] = e; - } - - return channelMap; - } } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index e375e419..8a204eea 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -13,19 +13,17 @@ public partial class NeuropixelsV2eChannelConfigurationDialog : ChannelConfigura internal event EventHandler OnZoom; internal event EventHandler OnFileLoad; - internal readonly List Electrodes; - internal readonly List ChannelMap; + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfiguration; - public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2eProbeGroup probeGroup) - : base(probeGroup) + public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2QuadShankProbeConfiguration probeConfiguration) + : base(probeConfiguration.ChannelConfiguration) { zedGraphChannels.ZoomButtons = MouseButtons.None; zedGraphChannels.ZoomButtons2 = MouseButtons.None; zedGraphChannels.ZoomStepFraction = 0.5; - ChannelMap = NeuropixelsV2eProbeGroup.ToChannelMap((NeuropixelsV2eProbeGroup)ChannelConfiguration); - Electrodes = NeuropixelsV2eProbeGroup.ToElectrodes((NeuropixelsV2eProbeGroup)ChannelConfiguration); + ProbeConfiguration = new(probeConfiguration); HighlightEnabledContacts(); UpdateContactLabels(); @@ -39,20 +37,34 @@ internal override ProbeGroup DefaultChannelLayout() internal override void LoadDefaultChannelLayout() { - base.LoadDefaultChannelLayout(); - - NeuropixelsV2eProbeGroup.UpdateElectrodes(Electrodes, (NeuropixelsV2eProbeGroup)ChannelConfiguration); - NeuropixelsV2eProbeGroup.UpdateChannelMap(ChannelMap, (NeuropixelsV2eProbeGroup)ChannelConfiguration); + ProbeConfiguration = new(); OnFileOpenHandler(); } internal override void OpenFile() { - base.OpenFile(); + var newConfiguration = OpenAndParseConfigurationFile(); + + if (newConfiguration == null) + { + return; + } + + if (ProbeConfiguration.ChannelConfiguration.NumberOfContacts == newConfiguration.NumberOfContacts) + { + newConfiguration.Validate(); - NeuropixelsV2eProbeGroup.UpdateElectrodes(Electrodes, (NeuropixelsV2eProbeGroup)ChannelConfiguration); - NeuropixelsV2eProbeGroup.UpdateChannelMap(ChannelMap, (NeuropixelsV2eProbeGroup)ChannelConfiguration); + ProbeConfiguration = new(newConfiguration); + + DrawProbeGroup(); + RefreshZedGraph(); + } + else + { + throw new InvalidOperationException($"Number of contacts does not match; expected {ProbeConfiguration.ChannelConfiguration.NumberOfContacts} contacts" + + $", but found {newConfiguration.NumberOfContacts} contacts"); + } OnFileOpenHandler(); } @@ -80,6 +92,9 @@ private void OnZoomHandler() internal override void DrawScale() { + if (ProbeConfiguration == null) + return; + const string ScalePointsTag = "scale_points"; const string ScaleTextTag = "scale_text"; @@ -91,7 +106,7 @@ internal override void DrawScale() const int MinorTickIncrement = 10; const int MinorTickLength = 5; - if (ChannelConfiguration.Probes.ElementAt(0).SiUnits != ProbeSiUnits.um) + if (ProbeConfiguration.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."); } @@ -164,7 +179,7 @@ private static double GetXRange(ZedGraphControl zedGraph) internal override void HighlightEnabledContacts() { - if (ChannelConfiguration == null || ChannelMap == null) + if (ProbeConfiguration == null || ProbeConfiguration.ChannelMap == null) return; var contactObjects = zedGraphChannels.GraphPane.GraphObjList.OfType() @@ -180,25 +195,25 @@ internal override void HighlightEnabledContacts() var contactsToEnable = contactObjects.Where(c => { var tag = c.Tag as ContactTag; - var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactNumber); - return ChannelMap[channel].ElectrodeNumber == tag.ContactNumber; + var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactIndex); + return ProbeConfiguration.ChannelMap[channel].Index == tag.ContactIndex; }); foreach (var contact in contactsToEnable) { var tag = (ContactTag)contact.Tag; - contact.Fill.Color = ReferenceContacts.Any(x => x == tag.ContactNumber) ? ReferenceContactFill : EnabledContactFill; + contact.Fill.Color = ReferenceContacts.Any(x => x == tag.ContactIndex) ? ReferenceContactFill : EnabledContactFill; } } internal override void UpdateContactLabels() { - if (ChannelConfiguration == null) + if (ProbeConfiguration.ChannelConfiguration == null) return; - var indices = ChannelConfiguration.GetDeviceChannelIndices() - .Select(ind => ind == -1).ToArray(); + var indices = ProbeConfiguration.ChannelConfiguration.GetDeviceChannelIndices() + .Select(ind => ind == -1).ToArray(); var textObjs = zedGraphChannels.GraphPane.GraphObjList.OfType() .Where(t => t.Tag is ContactTag); @@ -213,8 +228,8 @@ internal override void UpdateContactLabels() textObjsToUpdate = textObjs.Where(c => { var tag = c.Tag as ContactTag; - var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactNumber); - return ChannelMap[channel].ElectrodeNumber == tag.ContactNumber; + var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactIndex); + return ProbeConfiguration.ChannelMap[channel].Index == tag.ContactIndex; }); foreach (var textObj in textObjsToUpdate) @@ -230,7 +245,7 @@ internal override string ContactString(int deviceChannelIndex, int index) internal void EnableElectrodes(List electrodes) { - ChannelMap.SelectElectrodes(electrodes); + ProbeConfiguration.SelectElectrodes(electrodes); } } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs index ee8190a4..f63bd421 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs @@ -56,7 +56,7 @@ public NeuropixelsV2eDialog(ConfigureNeuropixelsV2e configureNode) textBoxProbeCalibrationFileA.Text = ConfigureNode.GainCalibrationFileA; textBoxProbeCalibrationFileB.Text = ConfigureNode.GainCalibrationFileB; - ChannelConfigurationA = new(ConfigureNode.ProbeConfigurationA.ChannelConfiguration) + ChannelConfigurationA = new(ConfigureNode.ProbeConfigurationA) { TopLevel = false, FormBorderStyle = FormBorderStyle.None, @@ -73,7 +73,7 @@ public NeuropixelsV2eDialog(ConfigureNeuropixelsV2e configureNode) panelProbeA.Visible = false; } - ChannelConfigurationB = new(ConfigureNode.ProbeConfigurationB.ChannelConfiguration) + ChannelConfigurationB = new(ConfigureNode.ProbeConfigurationB) { TopLevel = false, FormBorderStyle = FormBorderStyle.None, @@ -216,184 +216,184 @@ private void SetChannelPreset(ChannelPreset preset, NeuropixelsV2Probe probeSele { var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; - var channelMap = channelConfiguration.ChannelMap; - var electrodes = channelConfiguration.Electrodes; + var probeConfiguration = channelConfiguration.ProbeConfiguration; + var electrodes = NeuropixelsV2eProbeGroup.ToElectrodes(channelConfiguration.ProbeConfiguration.ChannelConfiguration); switch (preset) { case ChannelPreset.Shank0BankA: - channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 0).ToList()); + probeConfiguration.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()); + probeConfiguration.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()); + probeConfiguration.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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 0).ToList()); break; case ChannelPreset.Shank1BankA: - channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 1).ToList()); + probeConfiguration.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 && + probeConfiguration.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()); + probeConfiguration.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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 1).ToList()); break; case ChannelPreset.Shank2BankA: - channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 2).ToList()); + probeConfiguration.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()); + probeConfiguration.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()); + probeConfiguration.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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 2).ToList()); break; case ChannelPreset.Shank3BankA: - channelMap.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 3).ToList()); + probeConfiguration.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()); + probeConfiguration.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()); + probeConfiguration.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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 1 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 2 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 3 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || + (e.Shank == 1 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || + (e.Shank == 2 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || + (e.Shank == 3 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 1 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 2 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 3 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 1 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 2 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 3 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || + (e.Shank == 1 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || + (e.Shank == 2 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || + (e.Shank == 3 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 1 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 2 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 3 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 1 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 2 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 3 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 1 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 2 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 3 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 1 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 2 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 3 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 1 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 2 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 3 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 1 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 2 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 3 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 1 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 2 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 3 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 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()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 1 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 2 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 3 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247)).ToList()); break; } @@ -408,7 +408,7 @@ private void CheckForExistingChannelPreset(NeuropixelsV2Probe probeSelected) var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; var comboBox = probeSelected == NeuropixelsV2Probe.ProbeA ? comboBoxChannelPresetsA : comboBoxChannelPresetsB; - var channelMap = channelConfiguration.ChannelMap; + var channelMap = channelConfiguration.ProbeConfiguration.ChannelMap; if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && e.Shank == 0)) @@ -426,7 +426,7 @@ private void CheckForExistingChannelPreset(NeuropixelsV2Probe probeSelected) comboBox.SelectedItem = ChannelPreset.Shank0BankC; } else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && e.Shank == 0)) { comboBox.SelectedItem = ChannelPreset.Shank0BankD; @@ -447,7 +447,7 @@ private void CheckForExistingChannelPreset(NeuropixelsV2Probe probeSelected) comboBox.SelectedItem = ChannelPreset.Shank1BankC; } else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && e.Shank == 1)) { comboBox.SelectedItem = ChannelPreset.Shank1BankD; @@ -468,7 +468,7 @@ private void CheckForExistingChannelPreset(NeuropixelsV2Probe probeSelected) comboBox.SelectedItem = ChannelPreset.Shank2BankC; } else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && e.Shank == 2)) { comboBox.SelectedItem = ChannelPreset.Shank2BankD; @@ -489,92 +489,92 @@ private void CheckForExistingChannelPreset(NeuropixelsV2Probe probeSelected) comboBox.SelectedItem = ChannelPreset.Shank3BankC; } else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.ElectrodeNumber >= 896)) && + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 1 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 2 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 3 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 1 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 2 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 3 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 1 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 2 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 3 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || + (e.Shank == 1 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || + (e.Shank == 2 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || + (e.Shank == 3 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 1 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 2 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 3 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 1 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 2 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 3 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 1 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 2 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 3 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 1 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 2 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 3 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 1 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 2 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 3 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 1 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 2 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 3 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 1 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 2 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 3 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 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))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 1 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 2 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 3 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247))) { comboBox.SelectedItem = ChannelPreset.AllShanks1152_1247; } @@ -716,20 +716,18 @@ internal void ButtonClick(object sender, EventArgs e) internal void UpdateProbeGroups() { - NeuropixelsV2eProbeGroup.UpdateProbeGroup(ChannelConfigurationA.ChannelMap, ConfigureNode.ProbeConfigurationA.ChannelConfiguration); - NeuropixelsV2eProbeGroup.UpdateProbeGroup(ChannelConfigurationB.ChannelMap, ConfigureNode.ProbeConfigurationB.ChannelConfiguration); + ConfigureNode.ProbeConfigurationA = ChannelConfigurationA.ProbeConfiguration; + ConfigureNode.ProbeConfigurationB = ChannelConfigurationB.ProbeConfiguration; } 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 electrodes = NeuropixelsV2eProbeGroup.ToElectrodes(channelConfiguration.ProbeConfiguration.ChannelConfiguration); - var selectedElectrodes = channelConfiguration.Electrodes - .Where((e, ind) => channelConfiguration.SelectedContacts[ind]) - .ToList(); + var selectedElectrodes = electrodes.Where((e, ind) => channelConfiguration.SelectedContacts[ind]) + .ToList(); channelConfiguration.EnableElectrodes(selectedElectrodes); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs index c6851a02..9950a7de 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs @@ -5,8 +5,12 @@ namespace OpenEphys.Onix1.Design { + /// + /// Class that opens a new dialog for a . + /// public class NeuropixelsV2eEditor : WorkflowComponentEditor { + /// public override bool EditComponent(ITypeDescriptorContext context, object component, IServiceProvider provider, IWin32Window owner) { if (provider != null) diff --git a/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj index 9bd92917..82c3b81b 100644 --- a/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj +++ b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj @@ -6,7 +6,7 @@ Bonsai Rx Open Ephys Onix Design net472 true - false + true x64 diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index 0c4a23b4..bdd933bc 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -20,6 +20,10 @@ public ConfigureNeuropixelsV2e() { } + /// + /// Copy constructor for the class. + /// + /// A pre-existing object. public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) : base(typeof(NeuropixelsV2e)) { diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index bcbcf602..2fee7957 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -45,7 +45,7 @@ public ConfigureNeuropixelsV2eBeta() /// [Category(ConfigurationCategory)] [Description("Probe A electrode configuration.")] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(); + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new(); /// /// Gets or sets the path to the gain calibration file for Probe A. @@ -64,7 +64,7 @@ public ConfigureNeuropixelsV2eBeta() /// [Category(ConfigurationCategory)] [Description("Probe B electrode configuration.")] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(); + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new(); /// /// Gets or sets the path to the gain calibration file for Probe B. diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs index 94125359..c0b63b3e 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs @@ -18,6 +18,10 @@ public ConfigureNeuropixelsV2eBno055() { } + /// + /// Copy constructor for the class. + /// + /// A pre-existing object. public ConfigureNeuropixelsV2eBno055(ConfigureNeuropixelsV2eBno055 configureNeuropixelsV2eBno055) : base(typeof(NeuropixelsV2eBno055)) { diff --git a/OpenEphys.Onix1/Electrode.cs b/OpenEphys.Onix1/Electrode.cs index a1010319..37c8b38a 100644 --- a/OpenEphys.Onix1/Electrode.cs +++ b/OpenEphys.Onix1/Electrode.cs @@ -3,36 +3,40 @@ namespace OpenEphys.Onix1 { + /// + /// Abstract base class for describing a single electrode. + /// public abstract class Electrode { /// - /// Index of the electrode within the context of the probe + /// Gets the index of the electrode (the electrode "number") within + /// the context of the entire probe. /// [XmlIgnore] - public int ElectrodeNumber { get; internal set; } + public int Index { get; internal set; } + /// - /// The shank this electrode belongs to + /// Gets the shank this electrode belongs to. /// [XmlIgnore] public int Shank { get; internal set; } + /// - /// Index of the electrode within this shank + /// Gets the index of the electrode within the context of . /// [XmlIgnore] - public int ShankIndex { get; internal set; } + public int IntraShankIndex { get; internal set; } + /// - /// The bank, or logical block of channels, this electrode belongs to + /// Gets the electrical channel that this electrode is mapped to. /// [XmlIgnore] public int Channel { get; internal set; } + /// - /// Location of the electrode in two-dimensional space + /// Gets the location of the electrode in two-dimensional space in arbitrary units. /// [XmlIgnore] public PointF Position { get; internal set; } - - public Electrode() - { - } } } diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs index ff30227b..6c8d1684 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs @@ -16,28 +16,28 @@ public class NeuropixelsV2QuadShankElectrode : Electrode public int Block { get; private set; } public int BlockIndex { get; private set; } - public NeuropixelsV2QuadShankElectrode(int electrodeNumber) + public NeuropixelsV2QuadShankElectrode(int index) { - 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; + Index = index; + Shank = index / NeuropixelsV2.ElectrodePerShank; + IntraShankIndex = index % NeuropixelsV2.ElectrodePerShank; + Bank = (NeuropixelsV2QuadShankBank)(IntraShankIndex / NeuropixelsV2.ChannelCount); + Block = IntraShankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; + BlockIndex = IntraShankIndex % NeuropixelsV2.ElectrodePerBlock; Channel = GetChannelNumber(Shank, Block, BlockIndex); - Position = GetPosition(electrodeNumber); + Position = GetPosition(index); } public NeuropixelsV2QuadShankElectrode(Contact contact) { - ElectrodeNumber = contact.Index; - Shank = ElectrodeNumber / NeuropixelsV2.ElectrodePerShank; - ShankIndex = ElectrodeNumber % NeuropixelsV2.ElectrodePerShank; - Bank = (NeuropixelsV2QuadShankBank)(ShankIndex / NeuropixelsV2.ChannelCount); - Block = ShankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; - BlockIndex = ShankIndex % NeuropixelsV2.ElectrodePerBlock; + Index = contact.Index; + Shank = Index / NeuropixelsV2.ElectrodePerShank; + IntraShankIndex = Index % NeuropixelsV2.ElectrodePerShank; + Bank = (NeuropixelsV2QuadShankBank)(IntraShankIndex / NeuropixelsV2.ChannelCount); + Block = IntraShankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; + BlockIndex = IntraShankIndex % NeuropixelsV2.ElectrodePerBlock; Channel = GetChannelNumber(Shank, Block, BlockIndex); - Position = GetPosition(ElectrodeNumber); + Position = GetPosition(Index); } private PointF GetPosition(int electrodeNumber) @@ -58,41 +58,41 @@ public static int GetChannelNumber(int electrodeNumber) 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, + (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, + (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, + (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, + (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.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs index 63326222..82819ce0 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs @@ -68,7 +68,7 @@ public enum NeuropixelsV2QuadShankBank } /// - /// Defines a configuration for quad-shank probes. + /// Defines a configuration for quad-shank, Neuropixels 2.0 and 2.0-beta probes. /// public class NeuropixelsV2QuadShankProbeConfiguration { @@ -90,6 +90,30 @@ public NeuropixelsV2QuadShankProbeConfiguration() } } + /// + /// Copy constructor for the class. + /// + /// The existing object to copy. + public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2QuadShankProbeConfiguration probeConfiguration) + { + Reference = probeConfiguration.Reference; + ChannelMap = probeConfiguration.ChannelMap; + ChannelConfiguration = new(probeConfiguration.ChannelConfiguration); + } + + /// + /// Initializes a new instance of the class with the given + /// channel configuration. The is automatically + /// generated from the . + /// + /// The existing instance to use. + public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2eProbeGroup channelConfiguration) + { + ChannelConfiguration = channelConfiguration; + + SelectElectrodes(NeuropixelsV2eProbeGroup.ToChannelMap(channelConfiguration)); + } + private static List CreateProbeModel() { var electrodes = new List(NeuropixelsV2.ElectrodePerShank * 4); @@ -131,13 +155,27 @@ public void SelectElectrodes(List electrodes) { ChannelMap[e.Channel] = e; } + + if (ChannelMap.Count != NeuropixelsV2.ChannelCount) + { + throw new InvalidOperationException($"Channel map does not match the expected number of active channels " + + $"for a NeuropixelsV2 probe. Expected {NeuropixelsV2.ChannelCount}, but there are {ChannelMap.Count} values."); + } } + /// + /// Gets the channel configuration for this probe. + /// [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(); + /// + /// Gets or sets a string defining the in Base64. + /// This variable is needed to properly save a workflow in Bonsai, but it is not + /// directly accessible in the Bonsai editor. + /// [Browsable(false)] [Externalizable(false)] [XmlElement(nameof(ChannelConfiguration))] diff --git a/OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs b/OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs index e308d3ed..5aeb4d80 100644 --- a/OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs +++ b/OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs @@ -98,10 +98,10 @@ public static BitArray[] GenerateShankBits(NeuropixelsV2QuadShankProbeConfigurat const int PixelOffset = (NeuropixelsV2.ElectrodePerShank - 1) / 2; const int ReferencePixelOffset = 3; - foreach (var c in NeuropixelsV2eProbeGroup.ToChannelMap(probe.ChannelConfiguration)) + foreach (var c in probe.ChannelMap) { - var baseIndex = c.ShankIndex % 2; - var pixelIndex = c.ShankIndex / 2; + var baseIndex = c.IntraShankIndex % 2; + var pixelIndex = c.IntraShankIndex / 2; pixelIndex = baseIndex == 0 ? pixelIndex + PixelOffset + 2 * ReferencePixelOffset : PixelOffset - pixelIndex + ReferencePixelOffset; diff --git a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs index 032a8d06..80f68f8a 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs @@ -272,7 +272,7 @@ public static void UpdateProbeGroup(List channe foreach (var e in channelMap) { - deviceChannelIndices[e.ElectrodeNumber] = e.Channel; + deviceChannelIndices[e.Index] = e.Channel; } probeGroup.UpdateDeviceChannelIndices(0, deviceChannelIndices); From 33479280159c4bab378076bb58e05792b6102dc1 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Mon, 5 Aug 2024 17:18:04 -0400 Subject: [PATCH 10/35] Update zooming - During zoom, keep focus centered on cursor - Add boundary for zooming in - Update documentation - Add check for incorrect tag type when toggling contact status --- .../ChannelConfigurationDialog.cs | 98 ++++++++++++++- OpenEphys.Onix1.Design/GenericDeviceDialog.cs | 7 ++ .../NeuropixelsV2eBno055Dialog.cs | 12 ++ .../NeuropixelsV2eBno055Editor.cs | 6 +- ...europixelsV2eChannelConfigurationDialog.cs | 19 +++ .../NeuropixelsV2eDialog.cs | 16 +++ .../NeuropixelsV2eHeadstageDialog.Designer.cs | 6 +- .../NeuropixelsV2eHeadstageDialog.cs | 27 ++++- .../NeuropixelsV2eHeadstageEditor.cs | 14 ++- .../NeuropixelsV2QuadShankElectrode.cs | 41 ++++--- OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs | 114 +++++++----------- OpenEphys.ProbeInterface | 2 +- 12 files changed, 261 insertions(+), 101 deletions(-) diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index 67baaa50..18359bc1 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -86,6 +86,8 @@ internal virtual void ZoomEvent(ZedGraphControl sender, ZoomState oldState, Zoom if (newState.Type == ZoomState.StateType.Zoom || newState.Type == ZoomState.StateType.WheelZoom) { SetEqualAxisLimits(sender); + CheckZoomBoundaries(sender); + CenterAxesOnCursor(sender); } } @@ -110,6 +112,91 @@ private void SetEqualAxisLimits(ZedGraphControl zedGraphControl) } } + private void CenterAxesOnCursor(ZedGraphControl zedGraphControl) + { + var mouseClientPosition = PointToClient(Cursor.Position); + mouseClientPosition.X -= (zedGraphControl.Parent.Width - zedGraphControl.Width) / 2; + + var currentMousePosition = TransformPixelsToCoordinates(mouseClientPosition, zedGraphControl.GraphPane); + + var centerX = CalculateRange(zedGraphChannels.GraphPane.XAxis.Scale) / 2 + + zedGraphControl.GraphPane.XAxis.Scale.Min; + + var centerY = CalculateRange(zedGraphChannels.GraphPane.YAxis.Scale) / 2 + + zedGraphControl.GraphPane.YAxis.Scale.Min; + + var diffX = centerX - currentMousePosition.X; + var diffY = centerY - currentMousePosition.Y; + + zedGraphControl.GraphPane.XAxis.Scale.Min += diffX; + zedGraphControl.GraphPane.XAxis.Scale.Max += diffX; + + zedGraphControl.GraphPane.YAxis.Scale.Min += diffY; + zedGraphControl.GraphPane.YAxis.Scale.Max += diffY; + } + + private static double CalculateRange(Scale scale) + { + return scale.Max - scale.Min; + } + + /// + /// Gets the value of the zoom boundary on the x-axis. + /// + /// + /// When zooming in excessively, it is possible to lose view of the entire probe and make it + /// difficult to zoom back out. This value is the boundary, where if the current zoom would make the x-axis + /// less than , would + /// automatically zoom back out to match . + /// + public double ZoomBoundaryX { get; internal set; } = 20; + + /// + /// Gets the value of the zoom boundary on the y-axis. + /// + /// + /// When zooming in excessively, it is possible to lose view of the entire probe and make it + /// difficult to zoom back out. This value is the boundary, where if the current zoom would make the y-axis + /// less than , would + /// automatically zoom back out to match . + /// + public double ZoomBoundaryY { get; internal set; } = 20; + + /// + /// Checks if the is too zoomed in. If the graph is too zoomed in, + /// reset the boundaries to match and . + /// + /// A object. + /// True if the control is zoomed out, false if it is zoomed in too far. + private bool CheckZoomBoundaries(ZedGraphControl zedGraphControl) + { + var rangeX = CalculateRange(zedGraphControl.GraphPane.XAxis.Scale); + var rangeY = CalculateRange(zedGraphControl.GraphPane.YAxis.Scale); + + if (rangeX < ZoomBoundaryX || rangeY < ZoomBoundaryY) + { + if (rangeX < ZoomBoundaryX) + { + var diffX = (ZoomBoundaryX - rangeX) / 2; + + zedGraphControl.GraphPane.XAxis.Scale.Min -= diffX; + zedGraphControl.GraphPane.XAxis.Scale.Max += diffX; + } + + if (rangeY < ZoomBoundaryY) + { + var diffY = (ZoomBoundaryY - rangeY) / 2; + + zedGraphControl.GraphPane.YAxis.Scale.Min -= diffY; + zedGraphControl.GraphPane.YAxis.Scale.Max += diffY; + } + + return false; + } + + return true; + } + private void FormShown(object sender, EventArgs e) { if (!TopLevel) @@ -520,6 +607,8 @@ public void InitializeZedGraphChannels() { zedGraphChannels.IsZoomOnMouseCenter = true; + zedGraphChannels.IsAntiAlias = true; + zedGraphChannels.GraphPane.Title.IsVisible = false; zedGraphChannels.GraphPane.TitleGap = 0; zedGraphChannels.GraphPane.Border.IsVisible = false; @@ -654,13 +743,14 @@ 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); + zedGraphChannels.ZoomPane(zedGraphChannels.GraphPane, 1 / zoomFactor, center, false); UpdateFontSize(); } internal void ResetZoom() { + zedGraphChannels.ZoomOutAll(zedGraphChannels.GraphPane); SetEqualAspectRatio(); UpdateFontSize(); } @@ -839,6 +929,9 @@ private bool MouseUpEvent(ZedGraphControl sender, MouseEventArgs e) private void ToggleSelectedContact(ContactTag tag) { + if (tag == null) + return; + SetSelectedContact(tag, !GetContactStatus(tag)); } @@ -866,6 +959,9 @@ internal void SetAllSelections(bool newStatus) private bool GetContactStatus(ContactTag tag) { + if (tag == null) + throw new ArgumentNullException("Attempted to check contact status of an object that is not a contact."); + return SelectedContacts[tag.ContactIndex]; } diff --git a/OpenEphys.Onix1.Design/GenericDeviceDialog.cs b/OpenEphys.Onix1.Design/GenericDeviceDialog.cs index 8bdb310d..ccd72934 100644 --- a/OpenEphys.Onix1.Design/GenericDeviceDialog.cs +++ b/OpenEphys.Onix1.Design/GenericDeviceDialog.cs @@ -2,8 +2,15 @@ namespace OpenEphys.Onix1.Design { + /// + /// Abstract form that implements a very basic GUI consisting of a single property grid and + /// two buttons (OK / Cancel). + /// public abstract partial class GenericDeviceDialog : Form { + /// + /// Initializes a new instance of . + /// public GenericDeviceDialog() { InitializeComponent(); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs index 858acd67..d9366abd 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs @@ -2,14 +2,26 @@ namespace OpenEphys.Onix1.Design { + /// + /// Partial class to create a GUI for . + /// public partial class NeuropixelsV2eBno055Dialog : GenericDeviceDialog { + /// + /// Gets or sets the object attached to + /// the property grid. + /// public ConfigureNeuropixelsV2eBno055 ConfigureNode { get => (ConfigureNeuropixelsV2eBno055)propertyGrid.SelectedObject; set => propertyGrid.SelectedObject = value; } + /// + /// Initializes a new instance of with the given + /// object. + /// + /// A object that contains configuration settings public NeuropixelsV2eBno055Dialog(ConfigureNeuropixelsV2eBno055 configureNode) { InitializeComponent(); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Editor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Editor.cs index 15388e2f..3a298ecc 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Editor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Editor.cs @@ -5,8 +5,12 @@ namespace OpenEphys.Onix1.Design { - internal class NeuropixelsV2eBno055Editor : WorkflowComponentEditor + /// + /// Class that opens a new dialog for a . + /// + public class NeuropixelsV2eBno055Editor : WorkflowComponentEditor { + /// public override bool EditComponent(ITypeDescriptorContext context, object component, IServiceProvider provider, IWin32Window owner) { if (provider != null) diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 8a204eea..de9999ab 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -8,13 +8,29 @@ namespace OpenEphys.Onix1.Design { + /// + /// Partial class to create a GUI for . + /// public partial class NeuropixelsV2eChannelConfigurationDialog : ChannelConfigurationDialog { internal event EventHandler OnZoom; internal event EventHandler OnFileLoad; + /// + /// Public object that is manipulated by + /// . + /// + /// + /// When a is passed to + /// , it is copied and stored in this + /// variable so that any modifications made to configuration settings can be easily reversed. + /// public NeuropixelsV2QuadShankProbeConfiguration ProbeConfiguration; + /// + /// Initializes a new instance of . + /// + /// A object holding the current configuration settings. public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2QuadShankProbeConfiguration probeConfiguration) : base(probeConfiguration.ChannelConfiguration) { @@ -25,6 +41,9 @@ public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2QuadShankProbeConfi ProbeConfiguration = new(probeConfiguration); + ZoomBoundaryX = 400; + ZoomBoundaryY = 400; + HighlightEnabledContacts(); UpdateContactLabels(); RefreshZedGraph(); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs index f63bd421..d65c7eb9 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs @@ -5,6 +5,9 @@ namespace OpenEphys.Onix1.Design { + /// + /// Partial class to create a GUI for . + /// public partial class NeuropixelsV2eDialog : Form { readonly NeuropixelsV2eChannelConfigurationDialog ChannelConfigurationA; @@ -44,8 +47,21 @@ private enum ChannelPreset None } + /// + /// Public object that is manipulated by + /// . + /// + /// + /// When a is passed to + /// , it is copied and stored in this + /// variable so that any modifications made to configuration settings can be easily reversed. + /// public ConfigureNeuropixelsV2e ConfigureNode { get; set; } + /// + /// Initializes a new instance of . + /// + /// A object holding the current configuration settings. public NeuropixelsV2eDialog(ConfigureNeuropixelsV2e configureNode) { InitializeComponent(); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs index 1a457684..7717140f 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs @@ -84,7 +84,7 @@ private void InitializeComponent() 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.Size = new System.Drawing.Size(1287, 686); this.tabPageBno055.TabIndex = 1; this.tabPageBno055.Text = "Bno055"; this.tabPageBno055.UseVisualStyleBackColor = true; @@ -94,7 +94,7 @@ private void InitializeComponent() 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.Size = new System.Drawing.Size(1281, 680); this.panelBno055.TabIndex = 0; // // splitContainer1 @@ -154,7 +154,7 @@ private void InitializeComponent() // fileToolStripMenuItem // this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 32); + this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 30); this.fileToolStripMenuItem.Text = "File"; // // NeuropixelsV2eHeadstageDialog diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs index 0e4d9cb9..83b8e8d7 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs @@ -2,16 +2,31 @@ namespace OpenEphys.Onix1.Design { + /// + /// Partial class to create a GUI for . + /// public partial class NeuropixelsV2eHeadstageDialog : Form { - public readonly NeuropixelsV2eDialog ConfigureNeuropixelsV2e; + /// + /// A that configures a . + /// + public readonly NeuropixelsV2eDialog DialogNeuropixelsV2e; + + /// + /// A that configures a . + /// public readonly NeuropixelsV2eBno055Dialog ConfigureBno055; + /// + /// Initializes a new instance of a . + /// + /// Configuration settings for a . + /// Configuration settings for a . public NeuropixelsV2eHeadstageDialog(ConfigureNeuropixelsV2e configureNeuropixelsV2e, ConfigureNeuropixelsV2eBno055 configureBno055) { InitializeComponent(); - ConfigureNeuropixelsV2e = new(configureNeuropixelsV2e) + DialogNeuropixelsV2e = new(configureNeuropixelsV2e) { TopLevel = false, FormBorderStyle = FormBorderStyle.None, @@ -19,9 +34,9 @@ public NeuropixelsV2eHeadstageDialog(ConfigureNeuropixelsV2e configureNeuropixel Parent = this }; - panelNeuropixelsV2e.Controls.Add(ConfigureNeuropixelsV2e); - this.AddMenuItemsFromDialogToFileOption(ConfigureNeuropixelsV2e, "NeuropixelsV2e"); - ConfigureNeuropixelsV2e.Show(); + panelNeuropixelsV2e.Controls.Add(DialogNeuropixelsV2e); + this.AddMenuItemsFromDialogToFileOption(DialogNeuropixelsV2e, "NeuropixelsV2e"); + DialogNeuropixelsV2e.Show(); ConfigureBno055 = new(configureBno055) { @@ -42,7 +57,7 @@ private void ButtonClick(object sender, System.EventArgs e) { if (button.Name == nameof(buttonOkay)) { - ConfigureNeuropixelsV2e.UpdateProbeGroups(); + DialogNeuropixelsV2e.UpdateProbeGroups(); DialogResult = DialogResult.OK; } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs index ac57fd42..057ece97 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs @@ -5,8 +5,12 @@ namespace OpenEphys.Onix1.Design { + /// + /// Class that opens a new dialog for a . + /// public class NeuropixelsV2eHeadstageEditor : WorkflowComponentEditor { + /// public override bool EditComponent(ITypeDescriptorContext context, object component, IServiceProvider provider, IWin32Window owner) { if (provider != null) @@ -20,11 +24,11 @@ public override bool EditComponent(ITypeDescriptorContext context, object compon { configureHeadstage.Bno055.Enable = editorDialog.ConfigureBno055.ConfigureNode.Enable; - 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; + configureHeadstage.NeuropixelsV2e.Enable = editorDialog.DialogNeuropixelsV2e.ConfigureNode.Enable; + configureHeadstage.NeuropixelsV2e.ProbeConfigurationA = editorDialog.DialogNeuropixelsV2e.ConfigureNode.ProbeConfigurationA; + configureHeadstage.NeuropixelsV2e.ProbeConfigurationB = editorDialog.DialogNeuropixelsV2e.ConfigureNode.ProbeConfigurationB; + configureHeadstage.NeuropixelsV2e.GainCalibrationFileA = editorDialog.DialogNeuropixelsV2e.ConfigureNode.GainCalibrationFileA; + configureHeadstage.NeuropixelsV2e.GainCalibrationFileB = editorDialog.DialogNeuropixelsV2e.ConfigureNode.GainCalibrationFileB; return true; } diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs index 6c8d1684..7e0a6600 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs @@ -5,17 +5,33 @@ namespace OpenEphys.Onix1 { + /// + /// Class defining a . + /// public class NeuropixelsV2QuadShankElectrode : Electrode { /// - /// The bank, or logical block of channels, this electrode belongs to + /// Gets the bank, or logical block of channels, this electrode belongs to. /// [XmlIgnore] public NeuropixelsV2QuadShankBank Bank { get; private set; } + /// + /// Gets the block this electrode belongs to. + /// + [XmlIgnore] public int Block { get; private set; } + + /// + /// Gets the index within the block this electrode belongs to. + /// + [XmlIgnore] public int BlockIndex { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// Integer defining the index of the contact. public NeuropixelsV2QuadShankElectrode(int index) { Index = index; @@ -28,28 +44,21 @@ public NeuropixelsV2QuadShankElectrode(int index) Position = GetPosition(index); } - public NeuropixelsV2QuadShankElectrode(Contact contact) - { - Index = contact.Index; - Shank = Index / NeuropixelsV2.ElectrodePerShank; - IntraShankIndex = Index % NeuropixelsV2.ElectrodePerShank; - Bank = (NeuropixelsV2QuadShankBank)(IntraShankIndex / NeuropixelsV2.ChannelCount); - Block = IntraShankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; - BlockIndex = IntraShankIndex % NeuropixelsV2.ElectrodePerBlock; - Channel = GetChannelNumber(Shank, Block, BlockIndex); - Position = GetPosition(Index); - } - 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) + /// + /// Static method returning the channel number of a given electrode. + /// + /// Integer defining the index of the electrode in the probe. + /// An integer between 0 and 383 defining the channel number. + public static int GetChannelNumber(int electrodeIndex) { - var shank = electrodeNumber / NeuropixelsV2.ElectrodePerShank; - var shankIndex = electrodeNumber % NeuropixelsV2.ElectrodePerShank; + var shank = electrodeIndex / NeuropixelsV2.ElectrodePerShank; + var shankIndex = electrodeIndex % NeuropixelsV2.ElectrodePerShank; var block = shankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; var blockIndex = shankIndex % NeuropixelsV2.ElectrodePerBlock; diff --git a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs index 80f68f8a..00c1377d 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs @@ -6,6 +6,9 @@ namespace OpenEphys.Onix1 { + /// + /// A class for NeuropixelsV2e. + /// public class NeuropixelsV2eProbeGroup : ProbeGroup { const float shankOffsetX = 200f; @@ -13,6 +16,13 @@ public class NeuropixelsV2eProbeGroup : ProbeGroup const float shankPitchX = 250f; const int numberOfShanks = 4; + /// + /// Initializes a new instance of the class. + /// + /// + /// The default constructor will initialize the new with + /// the default settings for all contacts, including their positions, shapes, and IDs. + /// public NeuropixelsV2eProbeGroup() : base("probeinterface", "0.2.21", new List() @@ -29,31 +39,48 @@ public NeuropixelsV2eProbeGroup() DefaultDeviceChannelIndices(NeuropixelsV2.ChannelCount, NeuropixelsV2.ElectrodePerShank * numberOfShanks), Probe.DefaultContactIds(NeuropixelsV2.ElectrodePerShank * numberOfShanks), DefaultShankIds(NeuropixelsV2.ElectrodePerShank * numberOfShanks)) - }.ToArray()) + }) { } + /// + /// Initializes a new instance of the class. + /// + /// + /// This constructor is marked with the , and is the + /// entry point for deserializing the JSON data into a C# class. + /// + /// String defining the . + /// String defining the . + /// Array of s. [JsonConstructor] public NeuropixelsV2eProbeGroup(string specification, string version, Probe[] probes) : base(specification, version, probes) { } + /// + /// Copy constructor that initializes a copied instance of the class. + /// + /// An existing object. public NeuropixelsV2eProbeGroup(NeuropixelsV2eProbeGroup probeGroup) : base(probeGroup) { } - public static float[][] DefaultContactPositions(int numberOfChannels) + /// + /// Generates a 2D array of default contact positions based on the given number of channels. + /// + /// Value defining the number of contacts to create positions for. + /// + /// 2D array of floats [N x 2], where the first dimension is the contact index [N] and the second dimension [2] + /// contains the X and Y values, respectively. + /// + public static float[][] DefaultContactPositions(int numberOfContacts) { - if (numberOfChannels % 2 != 0) - { - throw new ArgumentException("Invalid number of channels given; must be a multiple of two"); - } - - float[][] contactPositions = new float[numberOfChannels][]; + float[][] contactPositions = new float[numberOfContacts][]; - for (int i = 0; i < numberOfChannels; i++) + for (int i = 0; i < numberOfContacts; i++) { contactPositions[i] = DefaultContactPosition(i); } @@ -61,9 +88,14 @@ public static float[][] DefaultContactPositions(int numberOfChannels) return contactPositions; } - public static float[] DefaultContactPosition(int index) + /// + /// Generates a float array containing the X and Y position of a single contact. + /// + /// Index of the contact. + /// A float array of size [2 x 1] with the X and Y coordinates, respectively. + public static float[] DefaultContactPosition(int contactIndex) { - return new float[2] { ContactPositionX(index), index % NeuropixelsV2.ElectrodePerShank / 2 * 15 + 170 }; + return new float[2] { ContactPositionX(contactIndex), contactIndex % NeuropixelsV2.ElectrodePerShank / 2 * 15 + 170 }; } private static float ContactPositionX(int index) @@ -192,29 +224,14 @@ public static List ToElectrodes(NeuropixelsV2eP foreach (var c in channelConfiguration.GetContacts()) { - electrodes.Add(new NeuropixelsV2QuadShankElectrode(c)); + electrodes.Add(new NeuropixelsV2QuadShankElectrode(c.Index)); } 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 + /// Convert a object to a list of electrodes, which only includes currently enabled electrodes /// /// A object /// List of electrodes that are enabled @@ -233,49 +250,10 @@ public static List ToChannelMap(NeuropixelsV2eP foreach (var c in enabledContacts) { - channelMap.Add(new NeuropixelsV2QuadShankElectrode(c)); + channelMap.Add(new NeuropixelsV2QuadShankElectrode(c.Index)); } 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.Index] = e.Channel; - } - - probeGroup.UpdateDeviceChannelIndices(0, deviceChannelIndices); - } } } diff --git a/OpenEphys.ProbeInterface b/OpenEphys.ProbeInterface index 411f31e5..524b4c1e 160000 --- a/OpenEphys.ProbeInterface +++ b/OpenEphys.ProbeInterface @@ -1 +1 @@ -Subproject commit 411f31e5dad40c379c9b5f608ac00f6e15967a17 +Subproject commit 524b4c1e03aa3acbeaecb3964810c9c3a84dfc50 From 8e2196dc707a320847d230e6b6baf2acc401f715 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Tue, 6 Aug 2024 17:53:20 -0400 Subject: [PATCH 11/35] Refactor NeuropixelsV2eDialog to be more modular - Add probe configuration dialog - Individual probe configuration variables can be selected in Bonsai and pop up their own dialog - Double clicking the ConfigureNeuropixelsV2e operator will place each probe configuration dialog in its own tab - Double clicking the ConfigureNeuropixelsV2eHeadstage operator will place the device dialog in its own tab, with both probe configuration tabs inside of that - Update .gitignore to ignore .vscode and .csproj.user files --- .gitignore | 5 +- .../ChannelConfigurationDialog.Designer.cs | 85 +- OpenEphys.Onix1.Design/DesignHelper.cs | 90 +- ...europixelsV2eChannelConfigurationDialog.cs | 6 +- .../NeuropixelsV2eDialog.Designer.cs | 653 +-------------- .../NeuropixelsV2eDialog.cs | 777 +----------------- .../NeuropixelsV2eDialog.resx | 51 -- .../NeuropixelsV2eHeadstageDialog.Designer.cs | 20 +- .../NeuropixelsV2eHeadstageDialog.cs | 2 +- ...elsV2eProbeConfigurationDialog.Designer.cs | 487 +++++++++++ .../NeuropixelsV2eProbeConfigurationDialog.cs | 662 +++++++++++++++ ...europixelsV2eProbeConfigurationDialog.resx | 153 ++++ .../NeuropixelsV2eProbeConfigurationEditor.cs | 57 ++ .../OpenEphys.Onix1.Design.csproj.user | 29 - OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs | 7 +- .../ConfigureNeuropixelsV2eBeta.cs | 4 +- ...europixelsV2QuadShankProbeConfiguration.cs | 29 +- 17 files changed, 1552 insertions(+), 1565 deletions(-) create mode 100644 OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs create mode 100644 OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs create mode 100644 OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx create mode 100644 OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs delete mode 100644 OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj.user diff --git a/.gitignore b/.gitignore index 0bd1fb74..fd03c416 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -.vs/ +.vs*/ /artifacts/ .bonsai/Packages/ .bonsai/*.exe .bonsai/*.exe.settings -.bonsai/*.exe.WebView2/ \ No newline at end of file +.bonsai/*.exe.WebView2/ +*.user diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs index 8ae07de3..f6d1a963 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs @@ -32,10 +32,10 @@ private void InitializeComponent() 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.fileMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.dropDownOpenFile = new System.Windows.Forms.ToolStripMenuItem(); + this.dropDownSaveFile = new System.Windows.Forms.ToolStripMenuItem(); + this.dropDownLoadDefault = 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(); @@ -49,28 +49,27 @@ 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, 0); + this.splitContainer1.Location = new System.Drawing.Point(0, 36); 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.Size = new System.Drawing.Size(686, 681); + this.splitContainer1.SplitterDistance = 585; 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.Location = new System.Drawing.Point(0, 0); this.zedGraphChannels.Margin = new System.Windows.Forms.Padding(6, 8, 6, 8); this.zedGraphChannels.Name = "zedGraphChannels"; this.zedGraphChannels.ScrollGrace = 0D; @@ -80,7 +79,7 @@ private void InitializeComponent() this.zedGraphChannels.ScrollMinX = 0D; this.zedGraphChannels.ScrollMinY = 0D; this.zedGraphChannels.ScrollMinY2 = 0D; - this.zedGraphChannels.Size = new System.Drawing.Size(686, 624); + this.zedGraphChannels.Size = new System.Drawing.Size(686, 585); this.zedGraphChannels.TabIndex = 4; this.zedGraphChannels.UseExtendedPrintDialog = true; this.zedGraphChannels.ZoomEvent += new ZedGraph.ZedGraphControl.ZoomEventHandler(this.ZoomEvent); @@ -90,49 +89,49 @@ private void InitializeComponent() 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.fileMenuItem}); this.menuStrip.Location = new System.Drawing.Point(0, 0); this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(686, 33); + this.menuStrip.Size = new System.Drawing.Size(686, 36); this.menuStrip.TabIndex = 5; - this.menuStrip.Text = "menuStrip1"; + this.menuStrip.Text = "menuStripChannelConfiguration"; // - // fileToolStripMenuItem + // fileMenuItem // - 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"; + this.fileMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.dropDownOpenFile, + this.dropDownSaveFile, + this.dropDownLoadDefault}); + this.fileMenuItem.Name = "fileMenuItem"; + this.fileMenuItem.Size = new System.Drawing.Size(54, 30); + this.fileMenuItem.Text = "File"; // - // menuItemOpenFile + // dropDownOpenFile // - 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); + this.dropDownOpenFile.Name = "dropDownOpenFile"; + this.dropDownOpenFile.Size = new System.Drawing.Size(215, 34); + this.dropDownOpenFile.Text = "Open File"; + this.dropDownOpenFile.Click += new System.EventHandler(this.MenuItemOpenFile); // - // menuItemSaveFile + // dropDownSaveFile // - 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); + this.dropDownSaveFile.Name = "dropDownSaveFile"; + this.dropDownSaveFile.Size = new System.Drawing.Size(215, 34); + this.dropDownSaveFile.Text = "Save File"; + this.dropDownSaveFile.Click += new System.EventHandler(this.MenuItemSaveFile); // - // loadDefaultToolStripMenuItem + // dropDownLoadDefault // - 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); + this.dropDownLoadDefault.Name = "dropDownLoadDefault"; + this.dropDownLoadDefault.Size = new System.Drawing.Size(215, 34); + this.dropDownLoadDefault.Text = "Load Default"; + this.dropDownLoadDefault.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.Location = new System.Drawing.Point(512, 24); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(162, 40); this.buttonCancel.TabIndex = 4; @@ -142,7 +141,7 @@ private void InitializeComponent() // 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.Location = new System.Drawing.Point(329, 24); this.buttonOK.Name = "buttonOK"; this.buttonOK.Size = new System.Drawing.Size(162, 40); this.buttonOK.TabIndex = 3; @@ -156,6 +155,7 @@ private void InitializeComponent() this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(686, 717); this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.menuStrip); this.MainMenuStrip = this.menuStrip; this.Name = "ChannelConfigurationDialog"; this.Text = "ChannelConfigurationDialog"; @@ -167,6 +167,7 @@ private void InitializeComponent() this.menuStrip.ResumeLayout(false); this.menuStrip.PerformLayout(); this.ResumeLayout(false); + this.PerformLayout(); } @@ -177,9 +178,9 @@ private void InitializeComponent() 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; + private System.Windows.Forms.ToolStripMenuItem fileMenuItem; + private System.Windows.Forms.ToolStripMenuItem dropDownOpenFile; + private System.Windows.Forms.ToolStripMenuItem dropDownSaveFile; + private System.Windows.Forms.ToolStripMenuItem dropDownLoadDefault; } } diff --git a/OpenEphys.Onix1.Design/DesignHelper.cs b/OpenEphys.Onix1.Design/DesignHelper.cs index edc41798..d6e8b94e 100644 --- a/OpenEphys.Onix1.Design/DesignHelper.cs +++ b/OpenEphys.Onix1.Design/DesignHelper.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows.Forms; @@ -20,7 +21,7 @@ public static void SerializeObject(object _object, string filepath) File.WriteAllText(filepath, stringJson); } - public static IEnumerable GetAllChildren(this Control root) + public static IEnumerable GetAllControls(this Control root) { var stack = new Stack(); stack.Push(root); @@ -34,25 +35,40 @@ public static IEnumerable GetAllChildren(this Control root) } } + public static IEnumerable GetTopLevelControls(this Control root) + { + var stack = new Stack(); + stack.Push(root); + + if (stack.Any()) + { + var next = stack.Pop(); + foreach (Control child in next.Controls) + { + yield return child; + } + } + } + /// /// 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) + /// + public static void AddMenuItemsFromDialogToFileOption(this Form thisForm, Form childForm) { const string FileString = "File"; - if (form != null) + if (childForm != null) { - var menuStrips = form.GetAllChildren() - .OfType() - .ToList(); + var childMenuStrip = childForm.GetAllControls() + .OfType() + .FirstOrDefault() ?? throw new InvalidOperationException($"There are no menu strips in any child controls of the {childForm.Text} dialog."); - var thisMenuStrip = thisForm.GetAllChildren() + var thisMenuStrip = thisForm.GetTopLevelControls() .OfType() - .FirstOrDefault(); + .FirstOrDefault() ?? throw new InvalidOperationException($"There are no menu strips at the top level of the {thisForm.Text} dialog to pull out."); ToolStripMenuItem existingMenuItem = null; @@ -64,19 +80,13 @@ public static void AddMenuItemsFromDialogToFileOption(this Form thisForm, Form f } } - if (menuStrips != null && menuStrips.Count > 0) + foreach (ToolStripMenuItem menuItem in childMenuStrip.Items) { - foreach (var menuStrip in menuStrips) + if (menuItem.Text == FileString) { - foreach (ToolStripMenuItem menuItem in menuStrip.Items) + while (menuItem.DropDownItems.Count > 0) { - if (menuItem.Text == FileString) - { - while (menuItem.DropDownItems.Count > 0) - { - existingMenuItem.DropDownItems.Add(menuItem.DropDownItems[0]); - } - } + existingMenuItem.DropDownItems.Add(menuItem.DropDownItems[0]); } } } @@ -88,55 +98,49 @@ public static void AddMenuItemsFromDialogToFileOption(this Form thisForm, Form f /// sub-menu name given, nested under the "File" MenuItem for the parent form /// /// - /// + /// /// - public static void AddMenuItemsFromDialogToFileOption(this Form thisForm, Form form, string subMenuName) + public static void AddMenuItemsFromDialogToFileOption(this Form thisForm, Form childForm, string subMenuName) { const string FileString = "File"; - if (form != null) + if (childForm != null) { - var menuStrips = form.GetAllChildren() - .OfType() - .ToList(); + var childMenuStrip = childForm.GetAllControls() + .OfType() + .First() ?? throw new InvalidOperationException($"There are no menu strips in any child controls of the {childForm.Text} dialog."); - var thisMenuStrip = thisForm.GetAllChildren() + var thisMenuStrip = thisForm.GetTopLevelControls() .OfType() - .FirstOrDefault(); + .FirstOrDefault() ?? throw new InvalidOperationException($"There are no menu strips at the top level of the {thisForm.Text} dialog to pull out."); - ToolStripMenuItem existingMenuItem = null; + ToolStripMenuItem thisFileMenuItem = null; foreach (ToolStripMenuItem menuItem in thisMenuStrip.Items) { if (menuItem.Text == FileString) { - existingMenuItem = menuItem; + thisFileMenuItem = menuItem; } } - ToolStripMenuItem newItems = new() + ToolStripMenuItem newChildMenuItems = new() { Text = subMenuName }; - if (menuStrips != null && menuStrips.Count > 0) + foreach (ToolStripMenuItem childItem in childMenuStrip.Items) { - foreach (var menuStrip in menuStrips) + if (childItem.Text == FileString) { - foreach (ToolStripMenuItem menuItem in menuStrip.Items) + while (childItem.DropDownItems.Count > 0) { - if (menuItem.Text == FileString) - { - while (menuItem.DropDownItems.Count > 0) - { - newItems.DropDownItems.Add(menuItem.DropDownItems[0]); - } - } + newChildMenuItems.DropDownItems.Add(childItem.DropDownItems[0]); } } - - existingMenuItem.DropDownItems.Add(newItems); } + + thisFileMenuItem.DropDownItems.Add(newChildMenuItems); } } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index de9999ab..e9ab17f2 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -39,7 +39,7 @@ public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2QuadShankProbeConfi zedGraphChannels.ZoomStepFraction = 0.5; - ProbeConfiguration = new(probeConfiguration); + ProbeConfiguration = probeConfiguration; ZoomBoundaryX = 400; ZoomBoundaryY = 400; @@ -56,7 +56,7 @@ internal override ProbeGroup DefaultChannelLayout() internal override void LoadDefaultChannelLayout() { - ProbeConfiguration = new(); + ProbeConfiguration = new(ProbeConfiguration.Probe); OnFileOpenHandler(); } @@ -74,7 +74,7 @@ internal override void OpenFile() { newConfiguration.Validate(); - ProbeConfiguration = new(newConfiguration); + ProbeConfiguration = new(newConfiguration, ProbeConfiguration.Reference, ProbeConfiguration.Probe); DrawProbeGroup(); RefreshZedGraph(); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs index 1f7eadb5..abd87db3 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs @@ -28,229 +28,21 @@ protected override void Dispose(bool disposing) /// 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; - 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(); 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.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(); - this.buttonGainCalibrationFileA = new System.Windows.Forms.Button(); - this.textBoxProbeCalibrationFileA = new System.Windows.Forms.TextBox(); - this.comboBoxReferenceA = new System.Windows.Forms.ComboBox(); - 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(); - 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(); - label6 = new System.Windows.Forms.Label(); - label7 = 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.tabPageElectrodes.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(202, 19); - 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(190, 194); - 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(190, 387); - 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(189, 422); - 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(189, 499); - label5.Name = "label5"; - label5.Size = new System.Drawing.Size(66, 20); - label5.TabIndex = 27; - label5.Text = "Probe B"; - // - // 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"; - // - // 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); @@ -261,7 +53,7 @@ private void InitializeComponent() this.menuStrip.Name = "menuStrip"; this.menuStrip.Size = new System.Drawing.Size(1265, 36); this.menuStrip.TabIndex = 0; - this.menuStrip.Text = "menuStrip1"; + this.menuStrip.Text = "menuStripNeuropixelsV2e"; // // fileToolStripMenuItem // @@ -269,52 +61,6 @@ private void InitializeComponent() this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 30); 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; @@ -325,317 +71,23 @@ private void InitializeComponent() // // splitContainer1.Panel1 // - this.splitContainer1.Panel1.Controls.Add(this.splitContainer2); + this.splitContainer1.Panel1.Controls.Add(this.tabControlProbe); // // splitContainer1.Panel2 // this.splitContainer1.Panel2.Controls.Add(this.panel1); - this.splitContainer1.Size = new System.Drawing.Size(1265, 748); - this.splitContainer1.SplitterDistance = 696; + this.splitContainer1.Size = new System.Drawing.Size(1265, 780); + this.splitContainer1.SplitterDistance = 734; 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, 696); - this.splitContainer2.SplitterDistance = 940; - 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(940, 696); - 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(932, 663); - 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(932, 663); - 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.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(321, 696); - 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, 663); - this.tabPageOptions.TabIndex = 0; - this.tabPageOptions.Text = "Options"; - this.tabPageOptions.UseVisualStyleBackColor = true; - // - // 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); - 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, 657); - 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; - 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; - // - // 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; - // - // tabPageElectrodes - // - 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); - 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(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(143, 520); - 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(143, 443); - 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.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(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(181, 232); - 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(181, 294); - 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(181, 132); - 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(181, 92); - 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(181, 52); - 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); + this.tabControlProbe.Size = new System.Drawing.Size(1265, 734); + this.tabControlProbe.TabIndex = 1; // // panel1 // @@ -644,13 +96,13 @@ private void InitializeComponent() 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.Size = new System.Drawing.Size(1265, 42); 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.Location = new System.Drawing.Point(1129, 4); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(124, 34); this.buttonCancel.TabIndex = 1; @@ -661,7 +113,7 @@ private void InitializeComponent() // 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.Location = new System.Drawing.Point(996, 4); this.buttonOkay.Name = "buttonOkay"; this.buttonOkay.Size = new System.Drawing.Size(124, 34); this.buttonOkay.TabIndex = 0; @@ -669,46 +121,12 @@ private void InitializeComponent() 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"; - 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); 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; @@ -717,27 +135,10 @@ private void InitializeComponent() 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.tabPageElectrodes.ResumeLayout(false); - this.panelChannelOptions.ResumeLayout(false); - this.panelChannelOptions.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).EndInit(); this.panel1.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); @@ -747,45 +148,11 @@ private void InitializeComponent() #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 tabPageElectrodes; - 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; - private System.Windows.Forms.Button buttonClearCalibrationFileB; - private System.Windows.Forms.Button buttonClearCalibrationFileA; + private System.Windows.Forms.TabControl tabControlProbe; } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs index d65c7eb9..b8f1bf61 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows.Forms; @@ -10,42 +11,7 @@ namespace OpenEphys.Onix1.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 - } + readonly IReadOnlyList ProbeConfigurations; /// /// Public object that is manipulated by @@ -69,569 +35,74 @@ public NeuropixelsV2eDialog(ConfigureNeuropixelsV2e configureNode) ConfigureNode = new(configureNode); - textBoxProbeCalibrationFileA.Text = ConfigureNode.GainCalibrationFileA; - textBoxProbeCalibrationFileB.Text = ConfigureNode.GainCalibrationFileB; - - ChannelConfigurationA = new(ConfigureNode.ProbeConfigurationA) - { - 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)) + ProbeConfigurations = new List { - panelProbeA.Visible = false; - } - - ChannelConfigurationB = new(ConfigureNode.ProbeConfigurationB) - { - 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; - 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; - 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)) + new(ConfigureNode.ProbeConfigurationA, ConfigureNode.GainCalibrationFileA) { - ConfigureNode.GainCalibrationFileA = textBox.Text; - ParseGainCalibrationFile("A"); - } - else if (textBox.Name == nameof(textBoxProbeCalibrationFileB)) + TopLevel = false, + FormBorderStyle = FormBorderStyle.None, + Dock = DockStyle.Fill, + Parent = this, + Tag = NeuropixelsV2Probe.ProbeA + }, + new(ConfigureNode.ProbeConfigurationB, ConfigureNode.GainCalibrationFileB) { - ConfigureNode.GainCalibrationFileB = textBox.Text; - ParseGainCalibrationFile("B"); + TopLevel = false, + FormBorderStyle = FormBorderStyle.None, + Dock = DockStyle.Fill, + Parent = this, + Tag = NeuropixelsV2Probe.ProbeB } - } - } - - private void ParseGainCalibrationFile(string probe) - { - if (probe == "A") - { - ParseGainCalibrationFile(ConfigureNode.GainCalibrationFileA, probeSnA, gainA); - } - else if (probe == "B") - { - ParseGainCalibrationFile(ConfigureNode.GainCalibrationFileB, probeSnB, gainB); - } - } + }; - private void ParseGainCalibrationFile(string filename, ToolStripStatusLabel probeSN, ToolStripStatusLabel gainLabel) - { - if (filename != null && filename != "") + foreach (var channelConfiguration in ProbeConfigurations) { - if (File.Exists(filename)) - { - using StreamReader gainCalibrationFile = new(filename); + string probeName = GetProbeName((NeuropixelsV2Probe)channelConfiguration.Tag); - probeSN.Text = ulong.Parse(gainCalibrationFile.ReadLine()).ToString(); - gainLabel.Text = double.Parse(gainCalibrationFile.ReadLine()).ToString(); - } - else - { - probeSN.Text = ""; - gainLabel.Text = ""; - } - } - - else - { - probeSN.Text = ""; - gainLabel.Text = ""; + tabControlProbe.TabPages.Add(probeName, probeName); + tabControlProbe.TabPages[probeName].Controls.Add(channelConfiguration); + this.AddMenuItemsFromDialogToFileOption(channelConfiguration, probeName); } } - private void SelectedIndexChanged(object sender, EventArgs e) + private string GetProbeName(NeuropixelsV2Probe probe) { - var comboBox = sender as ComboBox; - - if (comboBox.Name == nameof(comboBoxReferenceA)) - { - ConfigureNode.ProbeConfigurationA.Reference = (NeuropixelsV2QuadShankReference)comboBox.SelectedItem; - } - else if (comboBox.Name == nameof(comboBoxReferenceB)) + return probe switch { - 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); - } - } + NeuropixelsV2Probe.ProbeA => "Probe A", + NeuropixelsV2Probe.ProbeB => "Probe B", + _ => throw new ArgumentException("Invalid probe was specified.") + }; } - private void SetChannelPreset(ChannelPreset preset, NeuropixelsV2Probe probeSelected) + private int GetProbeIndex(NeuropixelsV2Probe probe) { - var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; - - var probeConfiguration = channelConfiguration.ProbeConfiguration; - var electrodes = NeuropixelsV2eProbeGroup.ToElectrodes(channelConfiguration.ProbeConfiguration.ChannelConfiguration); - - switch (preset) - { - case ChannelPreset.Shank0BankA: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 0).ToList()); - break; - - case ChannelPreset.Shank0BankB: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 0).ToList()); - break; - - case ChannelPreset.Shank0BankC: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 0).ToList()); - break; - - case ChannelPreset.Shank0BankD: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 0).ToList()); - break; - - case ChannelPreset.Shank1BankA: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 1).ToList()); - break; - - case ChannelPreset.Shank1BankB: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 1).ToList()); - break; - - case ChannelPreset.Shank1BankC: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 1).ToList()); - break; - - case ChannelPreset.Shank1BankD: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 1).ToList()); - break; - - case ChannelPreset.Shank2BankA: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 2).ToList()); - break; - - case ChannelPreset.Shank2BankB: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 2).ToList()); - break; - - case ChannelPreset.Shank2BankC: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 2).ToList()); - break; - - case ChannelPreset.Shank2BankD: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 2).ToList()); - break; - - case ChannelPreset.Shank3BankA: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 3).ToList()); - break; - - case ChannelPreset.Shank3BankB: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 3).ToList()); - break; - - case ChannelPreset.Shank3BankC: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 3).ToList()); - break; - - case ChannelPreset.Shank3BankD: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 3).ToList()); - break; - - case ChannelPreset.AllShanks0_95: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 1 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 2 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 3 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95)).ToList()); - break; - - case ChannelPreset.AllShanks96_191: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || - (e.Shank == 1 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || - (e.Shank == 2 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || - (e.Shank == 3 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191)).ToList()); - break; - - case ChannelPreset.AllShanks192_287: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 1 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 2 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 3 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287)).ToList()); - break; - - case ChannelPreset.AllShanks288_383: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 1 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 2 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 3 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383)).ToList()); - break; - - case ChannelPreset.AllShanks384_479: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || - (e.Shank == 1 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || - (e.Shank == 2 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || - (e.Shank == 3 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479)).ToList()); - break; - - case ChannelPreset.AllShanks480_575: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 1 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 2 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 3 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575)).ToList()); - break; - - case ChannelPreset.AllShanks576_671: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 1 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 2 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 3 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671)).ToList()); - break; - - case ChannelPreset.AllShanks672_767: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 1 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 2 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 3 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767)).ToList()); - break; - - case ChannelPreset.AllShanks768_863: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 1 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 2 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 3 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863)).ToList()); - break; - - case ChannelPreset.AllShanks864_959: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 1 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 2 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 3 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959)).ToList()); - break; - - case ChannelPreset.AllShanks960_1055: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 1 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 2 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 3 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055)).ToList()); - break; - - case ChannelPreset.AllShanks1056_1151: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 1 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 2 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 3 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151)).ToList()); - break; - - case ChannelPreset.AllShanks1152_1247: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 1 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 2 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 3 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247)).ToList()); - break; - } - - channelConfiguration.HighlightEnabledContacts(); - channelConfiguration.HighlightSelectedContacts(); - channelConfiguration.UpdateContactLabels(); - channelConfiguration.RefreshZedGraph(); + return probe == NeuropixelsV2Probe.ProbeA ? 0 : 1; } - private void CheckForExistingChannelPreset(NeuropixelsV2Probe probeSelected) + private void FormShown(object sender, EventArgs e) { - var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; - var comboBox = probeSelected == NeuropixelsV2Probe.ProbeA ? comboBoxChannelPresetsA : comboBoxChannelPresetsB; - - var channelMap = channelConfiguration.ProbeConfiguration.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.Index >= 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.Index >= 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.Index >= 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.Index >= 896)) && - e.Shank == 3)) - { - comboBox.SelectedItem = ChannelPreset.Shank3BankD; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 1 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 2 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 3 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks0_95; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 1 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 2 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 3 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks192_287; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 1 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 2 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 3 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks288_383; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || - (e.Shank == 1 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || - (e.Shank == 2 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || - (e.Shank == 3 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks384_479; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 1 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 2 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 3 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks480_575; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 1 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 2 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 3 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks576_671; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 1 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 2 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 3 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks672_767; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 1 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 2 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 3 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks768_863; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 1 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 2 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 3 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks864_959; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 1 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 2 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 3 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks960_1055; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 1 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 2 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 3 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks1056_1151; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 1 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 2 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 3 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247))) - { - comboBox.SelectedItem = ChannelPreset.AllShanks1152_1247; - } - else + if (!TopLevel) { - comboBox.SelectedItem = ChannelPreset.None; - } - } + splitContainer1.Panel2Collapsed = true; + splitContainer1.Panel2.Hide(); - private void UpdateChannelPresetIndex(object sender, EventArgs e) - { - if (sender is ChannelConfigurationDialog dialog) - { - if (dialog.Tag is NeuropixelsV2Probe probe) - { - CheckForExistingChannelPreset(probe); - } + menuStrip.Visible = false; } - } - private void LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - try + foreach (var channelConfiguration in ProbeConfigurations) { - System.Diagnostics.Process.Start("https://open-ephys.github.io/onix-docs/Software%20Guide/Bonsai.ONIX/Nodes/NeuropixelsV2eDevice.html"); - } - catch (Exception) - { - MessageBox.Show("Unable to open documentation link."); + channelConfiguration.Show(); } } internal void ButtonClick(object sender, EventArgs e) { - const float zoomFactor = 8f; - if (sender is Button button && button != null) { if (button.Name == nameof(buttonOkay)) { - UpdateProbeGroups(); + SaveVariables(); DialogResult = DialogResult.OK; } @@ -639,178 +110,16 @@ internal void ButtonClick(object sender, EventArgs e) { 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(); - } - 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() - { - ConfigureNode.ProbeConfigurationA = ChannelConfigurationA.ProbeConfiguration; - ConfigureNode.ProbeConfigurationB = ChannelConfigurationB.ProbeConfiguration; - } - - private void EnableSelectedContacts(NeuropixelsV2Probe probeSelected) - { - var channelConfiguration = probeSelected == NeuropixelsV2Probe.ProbeA ? ChannelConfigurationA : ChannelConfigurationB; - - var electrodes = NeuropixelsV2eProbeGroup.ToElectrodes(channelConfiguration.ProbeConfiguration.ChannelConfiguration); - - var selectedElectrodes = 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.DrawScale(); - 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) + internal void SaveVariables() { - var channelConfiguration = tabControlProbe.SelectedTab == tabPageProbeA ? ChannelConfigurationA : ChannelConfigurationB; + ConfigureNode.ProbeConfigurationA = ProbeConfigurations[GetProbeIndex(NeuropixelsV2Probe.ProbeA)].ProbeConfiguration; + ConfigureNode.ProbeConfigurationB = ProbeConfigurations[GetProbeIndex(NeuropixelsV2Probe.ProbeB)].ProbeConfiguration; - trackBarProbePosition.Value = (int)(channelConfiguration.GetRelativeVerticalPosition() * 100); + ConfigureNode.GainCalibrationFileA = ProbeConfigurations[GetProbeIndex(NeuropixelsV2Probe.ProbeA)].textBoxProbeCalibrationFile.Text; + ConfigureNode.GainCalibrationFileB = ProbeConfigurations[GetProbeIndex(NeuropixelsV2Probe.ProbeB)].textBoxProbeCalibrationFile.Text; } } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx index 024c125b..9d845151 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx @@ -117,58 +117,7 @@ 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 - - - False - 17, 17 - - 165, 17 - - - False - - - False - \ No newline at end of file diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs index 7717140f..bc43b86d 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs +++ b/OpenEphys.Onix1.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, 719); + this.tabControl1.Size = new System.Drawing.Size(1295, 731); 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, 686); + this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(1287, 698); 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, 680); + this.panelNeuropixelsV2e.Size = new System.Drawing.Size(1281, 692); 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, 36); + this.splitContainer1.Location = new System.Drawing.Point(0, 33); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -113,15 +113,15 @@ 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, 775); - this.splitContainer1.SplitterDistance = 719; + this.splitContainer1.Size = new System.Drawing.Size(1295, 778); + this.splitContainer1.SplitterDistance = 731; 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.Location = new System.Drawing.Point(1121, 1); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(162, 40); this.buttonCancel.TabIndex = 6; @@ -131,7 +131,7 @@ private void InitializeComponent() // 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.Location = new System.Drawing.Point(938, 1); this.buttonOkay.Name = "buttonOkay"; this.buttonOkay.Size = new System.Drawing.Size(162, 40); this.buttonOkay.TabIndex = 5; @@ -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, 36); + 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, 30); + this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 29); this.fileToolStripMenuItem.Text = "File"; // // NeuropixelsV2eHeadstageDialog diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs index 83b8e8d7..118436f5 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs @@ -57,7 +57,7 @@ private void ButtonClick(object sender, System.EventArgs e) { if (button.Name == nameof(buttonOkay)) { - DialogNeuropixelsV2e.UpdateProbeGroups(); + DialogNeuropixelsV2e.SaveVariables(); DialogResult = DialogResult.OK; } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs new file mode 100644 index 00000000..39867f12 --- /dev/null +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs @@ -0,0 +1,487 @@ +namespace OpenEphys.Onix1.Design +{ + partial class NeuropixelsV2eProbeConfigurationDialog + { + /// + /// 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.ToolStripStatusLabel toolStripStatusLabelGain; + System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelProbe; + System.Windows.Forms.Label probeCalibrationFile; + System.Windows.Forms.Label Reference; + System.Windows.Forms.Label label7; + System.Windows.Forms.Label label6; + System.Windows.Forms.Label labelPresets; + System.Windows.Forms.Label labelSelection; + System.Windows.Forms.Label label1; + this.toolStripLabelProbeNumber = 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.probeSn = new System.Windows.Forms.ToolStripStatusLabel(); + this.gain = new System.Windows.Forms.ToolStripStatusLabel(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.splitContainer2 = new System.Windows.Forms.SplitContainer(); + this.panelProbe = new System.Windows.Forms.Panel(); + this.panelChannelOptions = new System.Windows.Forms.Panel(); + this.buttonClearCalibrationFile = new System.Windows.Forms.Button(); + this.buttonChooseCalibrationFile = new System.Windows.Forms.Button(); + this.textBoxProbeCalibrationFile = new System.Windows.Forms.TextBox(); + this.comboBoxReference = new System.Windows.Forms.ComboBox(); + this.comboBoxChannelPresets = 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.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(); + toolStripStatusLabelGain = new System.Windows.Forms.ToolStripStatusLabel(); + toolStripStatusLabelProbe = new System.Windows.Forms.ToolStripStatusLabel(); + probeCalibrationFile = new System.Windows.Forms.Label(); + Reference = new System.Windows.Forms.Label(); + label7 = new System.Windows.Forms.Label(); + label6 = new System.Windows.Forms.Label(); + labelPresets = new System.Windows.Forms.Label(); + labelSelection = new System.Windows.Forms.Label(); + label1 = 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.panelChannelOptions.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).BeginInit(); + this.panel1.SuspendLayout(); + this.SuspendLayout(); + // + // toolStripStatusLabelGain + // + toolStripStatusLabelGain.Name = "toolStripStatusLabelGain"; + toolStripStatusLabelGain.Size = new System.Drawing.Size(47, 25); + toolStripStatusLabelGain.Text = "Gain"; + // + // toolStripStatusLabelProbe + // + toolStripStatusLabelProbe.Name = "toolStripStatusLabelProbe"; + toolStripStatusLabelProbe.Size = new System.Drawing.Size(44, 25); + toolStripStatusLabelProbe.Text = "SN: "; + // + // probeCalibrationFile + // + probeCalibrationFile.AutoSize = true; + probeCalibrationFile.Location = new System.Drawing.Point(8, 14); + probeCalibrationFile.MaximumSize = new System.Drawing.Size(200, 45); + probeCalibrationFile.Name = "probeCalibrationFile"; + probeCalibrationFile.Size = new System.Drawing.Size(159, 20); + probeCalibrationFile.TabIndex = 32; + probeCalibrationFile.Text = "Probe Calibration File"; + // + // Reference + // + Reference.AutoSize = true; + Reference.Location = new System.Drawing.Point(18, 134); + Reference.Name = "Reference"; + Reference.Size = new System.Drawing.Size(84, 20); + Reference.TabIndex = 30; + Reference.Text = "Reference"; + // + // label7 + // + label7.AutoSize = true; + label7.Location = new System.Drawing.Point(63, 218); + label7.Name = "label7"; + label7.Size = new System.Drawing.Size(57, 20); + label7.TabIndex = 29; + label7.Text = "10 mm"; + // + // 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(63, 649); + label6.Name = "label6"; + label6.Size = new System.Drawing.Size(48, 20); + label6.TabIndex = 28; + label6.Text = "0 mm"; + // + // labelPresets + // + labelPresets.AutoSize = true; + labelPresets.Location = new System.Drawing.Point(150, 354); + labelPresets.Name = "labelPresets"; + labelPresets.Size = new System.Drawing.Size(126, 20); + labelPresets.TabIndex = 23; + labelPresets.Text = "Channel Presets"; + // + // labelSelection + // + labelSelection.AutoSize = true; + labelSelection.Location = new System.Drawing.Point(187, 186); + labelSelection.Name = "labelSelection"; + labelSelection.Size = new System.Drawing.Size(75, 20); + labelSelection.TabIndex = 18; + labelSelection.Text = "Selection"; + // + // label1 + // + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(13, 186); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(66, 20); + label1.TabIndex = 5; + label1.Text = "Jump to"; + // + // toolStripLabelProbeNumber + // + this.toolStripLabelProbeNumber.Name = "toolStripLabelProbeNumber"; + this.toolStripLabelProbeNumber.Size = new System.Drawing.Size(59, 25); + this.toolStripLabelProbeNumber.Text = "Probe"; + // + // 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 = "menuStripNeuropixelsV2e"; + // + // 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[] { + this.toolStripLabelProbeNumber, + toolStripStatusLabelProbe, + this.probeSn, + toolStripStatusLabelGain, + this.gain}); + 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"; + // + // probeSn + // + this.probeSn.AutoSize = false; + this.probeSn.Name = "probeSn"; + this.probeSn.Size = new System.Drawing.Size(135, 25); + this.probeSn.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // gain + // + this.gain.AutoSize = false; + this.gain.Name = "gain"; + this.gain.Size = new System.Drawing.Size(120, 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.panelProbe); + // + // splitContainer2.Panel2 + // + this.splitContainer2.Panel2.Controls.Add(this.panelChannelOptions); + this.splitContainer2.Size = new System.Drawing.Size(1265, 699); + this.splitContainer2.SplitterDistance = 940; + this.splitContainer2.TabIndex = 1; + // + // panelProbe + // + this.panelProbe.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelProbe.Location = new System.Drawing.Point(0, 0); + this.panelProbe.Name = "panelProbe"; + this.panelProbe.Size = new System.Drawing.Size(940, 699); + this.panelProbe.TabIndex = 1; + // + // panelChannelOptions + // + this.panelChannelOptions.BackColor = System.Drawing.SystemColors.ControlLightLight; + this.panelChannelOptions.Controls.Add(this.buttonClearCalibrationFile); + this.panelChannelOptions.Controls.Add(this.buttonChooseCalibrationFile); + this.panelChannelOptions.Controls.Add(this.textBoxProbeCalibrationFile); + this.panelChannelOptions.Controls.Add(probeCalibrationFile); + this.panelChannelOptions.Controls.Add(this.comboBoxReference); + this.panelChannelOptions.Controls.Add(Reference); + this.panelChannelOptions.Controls.Add(label7); + this.panelChannelOptions.Controls.Add(label6); + this.panelChannelOptions.Controls.Add(this.comboBoxChannelPresets); + 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(label1); + this.panelChannelOptions.Controls.Add(this.buttonResetZoom); + 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(321, 699); + this.panelChannelOptions.TabIndex = 1; + // + // buttonClearCalibrationFile + // + this.buttonClearCalibrationFile.Location = new System.Drawing.Point(154, 69); + this.buttonClearCalibrationFile.Name = "buttonClearCalibrationFile"; + this.buttonClearCalibrationFile.Size = new System.Drawing.Size(141, 32); + this.buttonClearCalibrationFile.TabIndex = 35; + this.buttonClearCalibrationFile.Text = "Clear"; + this.buttonClearCalibrationFile.UseVisualStyleBackColor = true; + this.buttonClearCalibrationFile.Click += new System.EventHandler(this.ButtonClick); + // + // buttonChooseCalibrationFile + // + this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(12, 69); + this.buttonChooseCalibrationFile.Name = "buttonChooseCalibrationFile"; + this.buttonChooseCalibrationFile.Size = new System.Drawing.Size(141, 32); + this.buttonChooseCalibrationFile.TabIndex = 34; + this.buttonChooseCalibrationFile.Text = "Choose"; + this.buttonChooseCalibrationFile.UseVisualStyleBackColor = true; + this.buttonChooseCalibrationFile.Click += new System.EventHandler(this.ButtonClick); + // + // textBoxProbeCalibrationFile + // + this.textBoxProbeCalibrationFile.Location = new System.Drawing.Point(12, 37); + this.textBoxProbeCalibrationFile.Name = "textBoxProbeCalibrationFile"; + this.textBoxProbeCalibrationFile.ReadOnly = true; + this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(283, 26); + this.textBoxProbeCalibrationFile.TabIndex = 33; + this.textBoxProbeCalibrationFile.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.textBoxProbeCalibrationFile.TextChanged += new System.EventHandler(this.FileTextChanged); + // + // comboBoxReference + // + this.comboBoxReference.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxReference.FormattingEnabled = true; + this.comboBoxReference.Location = new System.Drawing.Point(119, 131); + this.comboBoxReference.Name = "comboBoxReference"; + this.comboBoxReference.Size = new System.Drawing.Size(176, 28); + this.comboBoxReference.TabIndex = 31; + // + // comboBoxChannelPresets + // + this.comboBoxChannelPresets.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxChannelPresets.FormattingEnabled = true; + this.comboBoxChannelPresets.Location = new System.Drawing.Point(136, 377); + this.comboBoxChannelPresets.Name = "comboBoxChannelPresets"; + this.comboBoxChannelPresets.Size = new System.Drawing.Size(162, 28); + this.comboBoxChannelPresets.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.AutoSize = false; + this.trackBarProbePosition.Location = new System.Drawing.Point(17, 209); + this.trackBarProbePosition.Maximum = 100; + this.trackBarProbePosition.Name = "trackBarProbePosition"; + this.trackBarProbePosition.Orientation = System.Windows.Forms.Orientation.Vertical; + this.trackBarProbePosition.Size = new System.Drawing.Size(56, 469); + 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(136, 209); + this.buttonEnableContacts.Name = "buttonEnableContacts"; + this.buttonEnableContacts.Size = new System.Drawing.Size(169, 56); + this.buttonEnableContacts.TabIndex = 20; + this.buttonEnableContacts.Text = "Enable Selected Contacts"; + this.buttonEnableContacts.UseVisualStyleBackColor = true; + this.buttonEnableContacts.Click += new System.EventHandler(this.ButtonClick); + // + // buttonClearSelections + // + this.buttonClearSelections.Location = new System.Drawing.Point(136, 271); + this.buttonClearSelections.Name = "buttonClearSelections"; + this.buttonClearSelections.Size = new System.Drawing.Size(169, 59); + this.buttonClearSelections.TabIndex = 19; + this.buttonClearSelections.Text = "Deselect Contacts"; + this.buttonClearSelections.UseVisualStyleBackColor = true; + this.buttonClearSelections.Click += new System.EventHandler(this.ButtonClick); + // + // buttonResetZoom + // + this.buttonResetZoom.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonResetZoom.Location = new System.Drawing.Point(159, 635); + this.buttonResetZoom.Name = "buttonResetZoom"; + this.buttonResetZoom.Size = new System.Drawing.Size(117, 34); + this.buttonResetZoom.TabIndex = 4; + this.buttonResetZoom.Text = "Reset Zoom"; + this.buttonResetZoom.UseVisualStyleBackColor = true; + this.buttonResetZoom.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.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.linkLabelDocumentation.AutoSize = true; + this.linkLabelDocumentation.BackColor = System.Drawing.Color.GhostWhite; + this.linkLabelDocumentation.Location = new System.Drawing.Point(1125, 790); + 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"; + this.linkLabelDocumentation.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkClicked); + // + // NeuropixelsV2eProbeConfigurationDialog + // + 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 = "NeuropixelsV2eProbeConfigurationDialog"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "NeuropixelsV2eProbeConfigurationDialog"; + 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.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.Panel panel1; + private System.Windows.Forms.Button buttonCancel; + private System.Windows.Forms.Button buttonOkay; + private System.Windows.Forms.ToolStripStatusLabel probeSn; + private System.Windows.Forms.LinkLabel linkLabelDocumentation; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.SplitContainer splitContainer2; + private System.Windows.Forms.ToolStripStatusLabel gain; + private System.Windows.Forms.Panel panelProbe; + private System.Windows.Forms.Panel panelChannelOptions; + private System.Windows.Forms.Button buttonClearCalibrationFile; + private System.Windows.Forms.Button buttonChooseCalibrationFile; + internal System.Windows.Forms.TextBox textBoxProbeCalibrationFile; + private System.Windows.Forms.ComboBox comboBoxReference; + private System.Windows.Forms.ComboBox comboBoxChannelPresets; + private System.Windows.Forms.TrackBar trackBarProbePosition; + private System.Windows.Forms.Button buttonEnableContacts; + private System.Windows.Forms.Button buttonClearSelections; + private System.Windows.Forms.Button buttonResetZoom; + private System.Windows.Forms.ToolStripStatusLabel toolStripLabelProbeNumber; + } +} diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs new file mode 100644 index 00000000..fbb0e974 --- /dev/null +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -0,0 +1,662 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Windows.Forms; + +namespace OpenEphys.Onix1.Design +{ + /// + /// Partial class to create a GUI for . + /// + public partial class NeuropixelsV2eProbeConfigurationDialog : Form + { + readonly NeuropixelsV2eChannelConfigurationDialog ChannelConfiguration; + + 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 object that is manipulated by + /// . + /// + /// + /// When a is passed to + /// , it is copied and stored in this + /// variable so that any modifications made to configuration settings can be easily reversed. + /// + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfiguration { get; set; } + + /// + /// Initializes a new instance of . + /// + /// A object holding the current configuration settings. + /// String containing the path to the gain calibration file for this probe. + public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2QuadShankProbeConfiguration configuration, string calibrationFile) + { + InitializeComponent(); + Shown += FormShown; + + ProbeConfiguration = new(configuration); + + textBoxProbeCalibrationFile.Text = calibrationFile; + + toolStripLabelProbeNumber.Text = GetProbeName(ProbeConfiguration.Probe); + + ChannelConfiguration = new(ProbeConfiguration) + { + TopLevel = false, + FormBorderStyle = FormBorderStyle.None, + Dock = DockStyle.Fill, + Parent = this + }; + + panelProbe.Controls.Add(ChannelConfiguration); + this.AddMenuItemsFromDialogToFileOption(ChannelConfiguration); + + panelProbe.Visible = File.Exists(textBoxProbeCalibrationFile.Text); + + ChannelConfiguration.OnZoom += UpdateTrackBarLocation; + ChannelConfiguration.OnFileLoad += UpdateChannelPresetIndex; + + comboBoxReference.DataSource = Enum.GetValues(typeof(NeuropixelsV2QuadShankReference)); + comboBoxReference.SelectedItem = ProbeConfiguration.Reference; + comboBoxReference.SelectedIndexChanged += SelectedIndexChanged; + + comboBoxChannelPresets.DataSource = Enum.GetValues(typeof(ChannelPreset)); + comboBoxChannelPresets.SelectedIndexChanged += SelectedIndexChanged; + CheckForExistingChannelPreset(); + } + + private string GetProbeName(NeuropixelsV2Probe probe) + { + return probe switch + { + NeuropixelsV2Probe.ProbeA => "Probe A", + NeuropixelsV2Probe.ProbeB => "Probe B", + _ => throw new ArgumentException("Invalid probe was specified.") + }; + } + + private void FormShown(object sender, EventArgs e) + { + if (!TopLevel) + { + splitContainer1.Panel2Collapsed = true; + splitContainer1.Panel2.Hide(); + + menuStrip.Visible = false; + } + + ChannelConfiguration.Show(); + ChannelConfiguration.ConnectResizeEventHandler(); + } + + private void FileTextChanged(object sender, EventArgs e) + { + if (sender is TextBox textBox && textBox != null && textBox.Name == nameof(textBoxProbeCalibrationFile)) + { + ParseGainCalibrationFile(textBox.Text, probeSn, gain); + } + } + + private void ParseGainCalibrationFile(string filename, ToolStripStatusLabel probeSN, ToolStripStatusLabel gainLabel) + { + if (filename != null && filename != "") + { + if (File.Exists(filename)) + { + using StreamReader gainCalibrationFile = new(filename); + + probeSN.Text = ulong.Parse(gainCalibrationFile.ReadLine()).ToString(); + gainLabel.Text = double.Parse(gainCalibrationFile.ReadLine()).ToString(); + } + else + { + probeSN.Text = ""; + gainLabel.Text = ""; + } + } + + else + { + probeSN.Text = ""; + gainLabel.Text = ""; + } + } + + private void SelectedIndexChanged(object sender, EventArgs e) + { + var comboBox = sender as ComboBox; + + if (comboBox.Name == nameof(comboBoxReference)) + { + ProbeConfiguration.Reference = (NeuropixelsV2QuadShankReference)comboBox.SelectedItem; + } + else if (comboBox.Name == nameof(comboBoxChannelPresets)) + { + if ((ChannelPreset)comboBox.SelectedItem != ChannelPreset.None) + { + SetChannelPreset((ChannelPreset)comboBox.SelectedItem); + } + } + } + + private void SetChannelPreset(ChannelPreset preset) + { + var probeConfiguration = ChannelConfiguration.ProbeConfiguration; + var electrodes = NeuropixelsV2eProbeGroup.ToElectrodes(ChannelConfiguration.ProbeConfiguration.ChannelConfiguration); + + switch (preset) + { + case ChannelPreset.Shank0BankA: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 0).ToList()); + break; + + case ChannelPreset.Shank0BankB: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 0).ToList()); + break; + + case ChannelPreset.Shank0BankC: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 0).ToList()); + break; + + case ChannelPreset.Shank0BankD: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 0).ToList()); + break; + + case ChannelPreset.Shank1BankA: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 1).ToList()); + break; + + case ChannelPreset.Shank1BankB: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 1).ToList()); + break; + + case ChannelPreset.Shank1BankC: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 1).ToList()); + break; + + case ChannelPreset.Shank1BankD: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 1).ToList()); + break; + + case ChannelPreset.Shank2BankA: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 2).ToList()); + break; + + case ChannelPreset.Shank2BankB: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 2).ToList()); + break; + + case ChannelPreset.Shank2BankC: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 2).ToList()); + break; + + case ChannelPreset.Shank2BankD: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 2).ToList()); + break; + + case ChannelPreset.Shank3BankA: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 3).ToList()); + break; + + case ChannelPreset.Shank3BankB: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 3).ToList()); + break; + + case ChannelPreset.Shank3BankC: + probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 3).ToList()); + break; + + case ChannelPreset.Shank3BankD: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 3).ToList()); + break; + + case ChannelPreset.AllShanks0_95: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 1 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 2 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 3 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95)).ToList()); + break; + + case ChannelPreset.AllShanks96_191: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || + (e.Shank == 1 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || + (e.Shank == 2 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || + (e.Shank == 3 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191)).ToList()); + break; + + case ChannelPreset.AllShanks192_287: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 1 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 2 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 3 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287)).ToList()); + break; + + case ChannelPreset.AllShanks288_383: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 1 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 2 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 3 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383)).ToList()); + break; + + case ChannelPreset.AllShanks384_479: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || + (e.Shank == 1 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || + (e.Shank == 2 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || + (e.Shank == 3 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479)).ToList()); + break; + + case ChannelPreset.AllShanks480_575: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 1 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 2 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 3 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575)).ToList()); + break; + + case ChannelPreset.AllShanks576_671: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 1 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 2 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 3 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671)).ToList()); + break; + + case ChannelPreset.AllShanks672_767: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 1 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 2 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 3 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767)).ToList()); + break; + + case ChannelPreset.AllShanks768_863: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 1 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 2 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 3 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863)).ToList()); + break; + + case ChannelPreset.AllShanks864_959: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 1 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 2 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 3 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959)).ToList()); + break; + + case ChannelPreset.AllShanks960_1055: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 1 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 2 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 3 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055)).ToList()); + break; + + case ChannelPreset.AllShanks1056_1151: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 1 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 2 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 3 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151)).ToList()); + break; + + case ChannelPreset.AllShanks1152_1247: + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 1 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 2 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 3 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247)).ToList()); + break; + } + + ChannelConfiguration.HighlightEnabledContacts(); + ChannelConfiguration.HighlightSelectedContacts(); + ChannelConfiguration.UpdateContactLabels(); + ChannelConfiguration.RefreshZedGraph(); + } + + private void CheckForExistingChannelPreset() + { + var channelMap = ChannelConfiguration.ProbeConfiguration.ChannelMap; + + if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 0)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank0BankA; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 0)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank0BankB; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 0)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank0BankC; + } + else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 0)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank0BankD; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 1)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank1BankA; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 1)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank1BankB; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 1)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank1BankC; + } + else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 1)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank1BankD; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 2)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank2BankA; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 2)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank2BankB; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 2)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank2BankC; + } + else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 2)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank2BankD; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && + e.Shank == 3)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank3BankA; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && + e.Shank == 3)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank3BankB; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && + e.Shank == 3)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank3BankC; + } + else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || + (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && + e.Shank == 3)) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank3BankD; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 1 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 2 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || + (e.Shank == 3 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks0_95; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 1 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 2 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || + (e.Shank == 3 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks192_287; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 1 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 2 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || + (e.Shank == 3 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks288_383; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || + (e.Shank == 1 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || + (e.Shank == 2 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || + (e.Shank == 3 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks384_479; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 1 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 2 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || + (e.Shank == 3 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks480_575; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 1 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 2 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || + (e.Shank == 3 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks576_671; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 1 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 2 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || + (e.Shank == 3 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks672_767; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 1 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 2 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || + (e.Shank == 3 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks768_863; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 1 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 2 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || + (e.Shank == 3 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks864_959; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 1 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 2 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || + (e.Shank == 3 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks960_1055; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 1 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 2 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || + (e.Shank == 3 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks1056_1151; + } + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 1 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 2 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || + (e.Shank == 3 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247))) + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks1152_1247; + } + else + { + comboBoxChannelPresets.SelectedItem = ChannelPreset.None; + } + } + + private void UpdateChannelPresetIndex(object sender, EventArgs e) + { + CheckForExistingChannelPreset(); + } + + private void LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + try + { + System.Diagnostics.Process.Start("https://open-ephys.github.io/onix1-bonsai-docs/api/OpenEphys.Onix1.ConfigureNeuropixelsV2eHeadstage.html"); + } + catch (Exception) + { + MessageBox.Show("Unable to open documentation link."); + } + } + + internal void ButtonClick(object sender, EventArgs e) + { + if (sender is Button button && button != null) + { + if (button.Name == nameof(buttonOkay)) + { + DialogResult = DialogResult.OK; + } + else if (button.Name == nameof(buttonCancel)) + { + DialogResult = DialogResult.Cancel; + } + else if (button.Name == nameof(buttonChooseCalibrationFile)) + { + var ofd = new OpenFileDialog() + { + CheckFileExists = true, + Filter = "Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv|All Files|*.*", + FilterIndex = 0 + }; + + if (ofd.ShowDialog() == DialogResult.OK) + { + textBoxProbeCalibrationFile.Text = ofd.FileName; + panelProbe.Visible = true; + } + else + { + panelProbe.Visible = File.Exists(textBoxProbeCalibrationFile.Text); + } + } + else if (button.Name == nameof(buttonResetZoom)) + { + ResetZoom(); + } + else if (button.Name == nameof(buttonClearSelections)) + { + ChannelConfiguration.SetAllSelections(false); + ChannelConfiguration.HighlightEnabledContacts(); + ChannelConfiguration.HighlightSelectedContacts(); + ChannelConfiguration.UpdateContactLabels(); + ChannelConfiguration.RefreshZedGraph(); + } + else if (button.Name == nameof(buttonEnableContacts)) + { + EnableSelectedContacts(); + + ChannelConfiguration.SetAllSelections(false); + ChannelConfiguration.HighlightEnabledContacts(); + ChannelConfiguration.HighlightSelectedContacts(); + ChannelConfiguration.UpdateContactLabels(); + ChannelConfiguration.RefreshZedGraph(); + } + else if (button.Name == nameof(buttonClearCalibrationFile)) + { + textBoxProbeCalibrationFile.Text = ""; + panelProbe.Visible = false; + } + } + } + + private void EnableSelectedContacts() + { + var electrodes = NeuropixelsV2eProbeGroup.ToElectrodes(ChannelConfiguration.ProbeConfiguration.ChannelConfiguration); + + var selectedElectrodes = electrodes.Where((e, ind) => ChannelConfiguration.SelectedContacts[ind]) + .ToList(); + + ChannelConfiguration.EnableElectrodes(selectedElectrodes); + + CheckForExistingChannelPreset(); + } + + private void ResetZoom() + { + ChannelConfiguration.ResetZoom(); + ChannelConfiguration.DrawScale(); + ChannelConfiguration.RefreshZedGraph(); + } + + private void MoveToVerticalPosition(float relativePosition) + { + ChannelConfiguration.MoveToVerticalPosition(relativePosition); + ChannelConfiguration.RefreshZedGraph(); + } + + private void TrackBarScroll(object sender, EventArgs e) + { + if (sender is TrackBar trackBar && trackBar != null) + { + if (trackBar.Name == nameof(trackBarProbePosition)) + { + MoveToVerticalPosition((float)trackBar.Value / trackBar.Maximum); + } + } + } + + private void UpdateTrackBarLocation(object sender, EventArgs e) + { + trackBarProbePosition.Value = (int)(ChannelConfiguration.GetRelativeVerticalPosition() * trackBarProbePosition.Maximum); + } + } +} diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx new file mode 100644 index 00000000..7232902c --- /dev/null +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 17, 17 + + + 165, 17 + + \ No newline at end of file diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs new file mode 100644 index 00000000..bbe4f1a2 --- /dev/null +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs @@ -0,0 +1,57 @@ +using System; +using System.ComponentModel; +using System.Drawing.Design; +using System.Windows.Forms; +using System.Windows.Forms.Design; +using Bonsai.Design; + +namespace OpenEphys.Onix1.Design +{ + /// + /// Class that opens a new dialog for a . + /// + public class NeuropixelsV2eProbeConfigurationEditor : UITypeEditor + { + /// + public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) + { + return UITypeEditorEditStyle.Modal; + } + + /// + public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) + { + if (provider != null) + { + var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); + var editorState = (IWorkflowEditorState)provider.GetService(typeof(IWorkflowEditorState)); + + if (editorService != null && editorState != null && !editorState.WorkflowRunning && + value is NeuropixelsV2QuadShankProbeConfiguration configuration) + { + var instance = (ConfigureNeuropixelsV2e)context.Instance; + + var calibrationFile = configuration.Probe == NeuropixelsV2Probe.ProbeA ? instance.GainCalibrationFileA : instance.GainCalibrationFileB; + + using var editorDialog = new NeuropixelsV2eProbeConfigurationDialog(configuration, calibrationFile); + + if (editorDialog.ShowDialog() == DialogResult.OK) + { + if (configuration.Probe == NeuropixelsV2Probe.ProbeA) + { + instance.GainCalibrationFileA = editorDialog.textBoxProbeCalibrationFile.Text; + } + else + { + instance.GainCalibrationFileB = editorDialog.textBoxProbeCalibrationFile.Text; + } + + return editorDialog.ProbeConfiguration; + } + } + } + + return base.EditValue(context, provider, value); + } + } +} diff --git a/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj.user b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj.user deleted file mode 100644 index 760e23fa..00000000 --- a/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj.user +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - Form - - - Form - - - Form - - - Form - - - Form - - - Form - - - - - Designer - - - \ No newline at end of file diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index bdd933bc..0013668d 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Drawing.Design; using System.Reactive.Disposables; using Bonsai; @@ -52,7 +53,8 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) /// [Category(ConfigurationCategory)] [Description("Probe A electrode configuration.")] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new(); + [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new(NeuropixelsV2Probe.ProbeA); /// /// Gets or sets the path to the gain calibration file for Probe A. @@ -71,7 +73,8 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) /// [Category(ConfigurationCategory)] [Description("Probe B electrode configuration.")] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new(); + [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new(NeuropixelsV2Probe.ProbeB); /// /// Gets or sets the path to the gain calibration file for Probe B. diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index 2fee7957..9c401c8e 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -45,7 +45,7 @@ public ConfigureNeuropixelsV2eBeta() /// [Category(ConfigurationCategory)] [Description("Probe A electrode configuration.")] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new(); + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new(NeuropixelsV2Probe.ProbeA); /// /// Gets or sets the path to the gain calibration file for Probe A. @@ -64,7 +64,7 @@ public ConfigureNeuropixelsV2eBeta() /// [Category(ConfigurationCategory)] [Description("Probe B electrode configuration.")] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new(); + public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new(NeuropixelsV2Probe.ProbeB); /// /// Gets or sets the path to the gain calibration file for Probe B. diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs index 82819ce0..391c8a2d 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs @@ -90,6 +90,19 @@ public NeuropixelsV2QuadShankProbeConfiguration() } } + /// + /// Initializes a new instance of the class. + /// + public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe probe) + { + ChannelMap = new List(NeuropixelsV2.ChannelCount); + for (int i = 0; i < NeuropixelsV2.ChannelCount; i++) + { + ChannelMap.Add(ProbeModel.FirstOrDefault(e => e.Channel == i)); + } + Probe = probe; + } + /// /// Copy constructor for the class. /// @@ -99,6 +112,7 @@ public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2QuadShankProbeConfi Reference = probeConfiguration.Reference; ChannelMap = probeConfiguration.ChannelMap; ChannelConfiguration = new(probeConfiguration.ChannelConfiguration); + Probe = probeConfiguration.Probe; } /// @@ -107,11 +121,15 @@ public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2QuadShankProbeConfi /// generated from the . /// /// The existing instance to use. - public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2eProbeGroup channelConfiguration) + /// The reference value. + /// The for this probe. + [JsonConstructor] + public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2eProbeGroup channelConfiguration, NeuropixelsV2QuadShankReference reference, NeuropixelsV2Probe probe) { ChannelConfiguration = channelConfiguration; - - SelectElectrodes(NeuropixelsV2eProbeGroup.ToChannelMap(channelConfiguration)); + ChannelMap = NeuropixelsV2eProbeGroup.ToChannelMap(channelConfiguration); + Reference = reference; + Probe = probe; } private static List CreateProbeModel() @@ -124,6 +142,11 @@ private static List CreateProbeModel() return electrodes; } + /// + /// Gets or sets the for this probe. + /// + public NeuropixelsV2Probe Probe { get; set; } = NeuropixelsV2Probe.ProbeA; + /// /// Gets or sets the reference for all electrodes. /// From 910869a26df321116682ecd11208f33b59750806 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Thu, 8 Aug 2024 09:41:22 -0400 Subject: [PATCH 12/35] Removed unused local variable --- .../NeuropixelsV2eChannelConfigurationDialog.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index e9ab17f2..5b2f1255 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -231,9 +231,6 @@ internal override void UpdateContactLabels() if (ProbeConfiguration.ChannelConfiguration == null) return; - var indices = ProbeConfiguration.ChannelConfiguration.GetDeviceChannelIndices() - .Select(ind => ind == -1).ToArray(); - var textObjs = zedGraphChannels.GraphPane.GraphObjList.OfType() .Where(t => t.Tag is ContactTag); @@ -245,11 +242,11 @@ internal override void UpdateContactLabels() } textObjsToUpdate = textObjs.Where(c => - { - var tag = c.Tag as ContactTag; - var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactIndex); - return ProbeConfiguration.ChannelMap[channel].Index == tag.ContactIndex; - }); + { + var tag = c.Tag as ContactTag; + var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactIndex); + return ProbeConfiguration.ChannelMap[channel].Index == tag.ContactIndex; + }); foreach (var textObj in textObjsToUpdate) { From 09a936b99701887f72223d2020fb2d0220ebfc5c Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 9 Aug 2024 11:52:38 -0400 Subject: [PATCH 13/35] Fixes channel configuration loading/saving at all dialog levels --- .bonsai/Bonsai.config | 84 +++---- ...europixelsV2eChannelConfigurationDialog.cs | 2 +- .../NeuropixelsV2eProbeConfigurationDialog.cs | 207 +++++++++--------- OpenEphys.Onix1/Electrode.cs | 2 +- .../NeuropixelsV2QuadShankElectrode.cs | 8 +- ...europixelsV2QuadShankProbeConfiguration.cs | 17 ++ OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs | 23 ++ 7 files changed, 192 insertions(+), 151 deletions(-) diff --git a/.bonsai/Bonsai.config b/.bonsai/Bonsai.config index f859f006..1efa5fec 100644 --- a/.bonsai/Bonsai.config +++ b/.bonsai/Bonsai.config @@ -50,49 +50,49 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 5b2f1255..b7a28be2 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -56,7 +56,7 @@ internal override ProbeGroup DefaultChannelLayout() internal override void LoadDefaultChannelLayout() { - ProbeConfiguration = new(ProbeConfiguration.Probe); + ProbeConfiguration = new(ProbeConfiguration.Probe, ProbeConfiguration.Reference); OnFileOpenHandler(); } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs index fbb0e974..658090e5 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows.Forms; @@ -88,7 +87,7 @@ public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2QuadShankProbeConfigu panelProbe.Visible = File.Exists(textBoxProbeCalibrationFile.Text); ChannelConfiguration.OnZoom += UpdateTrackBarLocation; - ChannelConfiguration.OnFileLoad += UpdateChannelPresetIndex; + ChannelConfiguration.OnFileLoad += OnFileLoadEvent; comboBoxReference.DataSource = Enum.GetValues(typeof(NeuropixelsV2QuadShankReference)); comboBoxReference.SelectedItem = ProbeConfiguration.Reference; @@ -265,94 +264,94 @@ private void SetChannelPreset(ChannelPreset preset) break; case ChannelPreset.AllShanks0_95: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 1 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 2 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 3 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95)).ToList()); break; case ChannelPreset.AllShanks96_191: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || - (e.Shank == 1 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || - (e.Shank == 2 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191) || - (e.Shank == 3 && e.IntraShankIndex >= 96 && e.IntraShankIndex <= 191)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 96 && e.IntraShankElectrodeIndex <= 191) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 96 && e.IntraShankElectrodeIndex <= 191) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 96 && e.IntraShankElectrodeIndex <= 191) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 96 && e.IntraShankElectrodeIndex <= 191)).ToList()); break; case ChannelPreset.AllShanks192_287: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 1 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 2 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 3 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287)).ToList()); break; case ChannelPreset.AllShanks288_383: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 1 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 2 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 3 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383)).ToList()); break; case ChannelPreset.AllShanks384_479: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || - (e.Shank == 1 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || - (e.Shank == 2 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479) || - (e.Shank == 3 && e.IntraShankIndex >= 384 && e.IntraShankIndex <= 479)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 384 && e.IntraShankElectrodeIndex <= 479) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 384 && e.IntraShankElectrodeIndex <= 479) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 384 && e.IntraShankElectrodeIndex <= 479) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 384 && e.IntraShankElectrodeIndex <= 479)).ToList()); break; case ChannelPreset.AllShanks480_575: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 1 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 2 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 3 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575)).ToList()); break; case ChannelPreset.AllShanks576_671: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 1 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 2 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 3 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671)).ToList()); break; case ChannelPreset.AllShanks672_767: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 1 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 2 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 3 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767)).ToList()); break; case ChannelPreset.AllShanks768_863: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 1 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 2 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 3 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863)).ToList()); break; case ChannelPreset.AllShanks864_959: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 1 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 2 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 3 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959)).ToList()); break; case ChannelPreset.AllShanks960_1055: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 1 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 2 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 3 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055)).ToList()); break; case ChannelPreset.AllShanks1056_1151: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 1 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 2 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 3 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151)).ToList()); break; case ChannelPreset.AllShanks1152_1247: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 1 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 2 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 3 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247)).ToList()); + probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247)).ToList()); break; } @@ -450,87 +449,87 @@ private void CheckForExistingChannelPreset() { comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank3BankD; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 1 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 2 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95) || - (e.Shank == 3 && e.IntraShankIndex >= 0 && e.IntraShankIndex <= 95))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks0_95; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 1 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 2 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287) || - (e.Shank == 3 && e.IntraShankIndex >= 192 && e.IntraShankIndex <= 287))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks192_287; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 1 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 2 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383) || - (e.Shank == 3 && e.IntraShankIndex >= 288 && e.IntraShankIndex <= 383))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks288_383; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || - (e.Shank == 1 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || - (e.Shank == 2 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479) || - (e.Shank == 3 && e.IntraShankIndex >= 394 && e.IntraShankIndex <= 479))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 394 && e.IntraShankElectrodeIndex <= 479) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 394 && e.IntraShankElectrodeIndex <= 479) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 394 && e.IntraShankElectrodeIndex <= 479) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 394 && e.IntraShankElectrodeIndex <= 479))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks384_479; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 1 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 2 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575) || - (e.Shank == 3 && e.IntraShankIndex >= 480 && e.IntraShankIndex <= 575))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks480_575; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 1 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 2 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671) || - (e.Shank == 3 && e.IntraShankIndex >= 576 && e.IntraShankIndex <= 671))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks576_671; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 1 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 2 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767) || - (e.Shank == 3 && e.IntraShankIndex >= 672 && e.IntraShankIndex <= 767))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks672_767; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 1 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 2 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863) || - (e.Shank == 3 && e.IntraShankIndex >= 768 && e.IntraShankIndex <= 863))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks768_863; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 1 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 2 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959) || - (e.Shank == 3 && e.IntraShankIndex >= 864 && e.IntraShankIndex <= 959))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks864_959; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 1 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 2 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055) || - (e.Shank == 3 && e.IntraShankIndex >= 960 && e.IntraShankIndex <= 1055))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks960_1055; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 1 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 2 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151) || - (e.Shank == 3 && e.IntraShankIndex >= 1056 && e.IntraShankIndex <= 1151))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks1056_1151; } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 1 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 2 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247) || - (e.Shank == 3 && e.IntraShankIndex >= 1152 && e.IntraShankIndex <= 1247))) + else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || + (e.Shank == 1 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || + (e.Shank == 2 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || + (e.Shank == 3 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247))) { comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks1152_1247; } @@ -540,8 +539,10 @@ private void CheckForExistingChannelPreset() } } - private void UpdateChannelPresetIndex(object sender, EventArgs e) + private void OnFileLoadEvent(object sender, EventArgs e) { + // NB: Ensure that the newly loaded ProbeConfiguration in the ChannelConfigurationDialog is reflected here. + ProbeConfiguration = ChannelConfiguration.ProbeConfiguration; CheckForExistingChannelPreset(); } diff --git a/OpenEphys.Onix1/Electrode.cs b/OpenEphys.Onix1/Electrode.cs index 37c8b38a..028c56a4 100644 --- a/OpenEphys.Onix1/Electrode.cs +++ b/OpenEphys.Onix1/Electrode.cs @@ -25,7 +25,7 @@ public abstract class Electrode /// Gets the index of the electrode within the context of . /// [XmlIgnore] - public int IntraShankIndex { get; internal set; } + public int IntraShankElectrodeIndex { get; internal set; } /// /// Gets the electrical channel that this electrode is mapped to. diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs index 7e0a6600..e34ef6a3 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs @@ -36,10 +36,10 @@ public NeuropixelsV2QuadShankElectrode(int index) { Index = index; Shank = index / NeuropixelsV2.ElectrodePerShank; - IntraShankIndex = index % NeuropixelsV2.ElectrodePerShank; - Bank = (NeuropixelsV2QuadShankBank)(IntraShankIndex / NeuropixelsV2.ChannelCount); - Block = IntraShankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; - BlockIndex = IntraShankIndex % NeuropixelsV2.ElectrodePerBlock; + IntraShankElectrodeIndex = index % NeuropixelsV2.ElectrodePerShank; + Bank = (NeuropixelsV2QuadShankBank)(IntraShankElectrodeIndex / NeuropixelsV2.ChannelCount); + Block = IntraShankElectrodeIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; + BlockIndex = IntraShankElectrodeIndex % NeuropixelsV2.ElectrodePerBlock; Channel = GetChannelNumber(Shank, Block, BlockIndex); Position = GetPosition(index); } diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs index 391c8a2d..7754a543 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs @@ -103,6 +103,20 @@ public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe probe) Probe = probe; } + /// + /// Initializes a new instance of the class. + /// + public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe probe, NeuropixelsV2QuadShankReference reference) + { + ChannelMap = new List(NeuropixelsV2.ChannelCount); + for (int i = 0; i < NeuropixelsV2.ChannelCount; i++) + { + ChannelMap.Add(ProbeModel.FirstOrDefault(e => e.Channel == i)); + } + Probe = probe; + Reference = reference; + } + /// /// Copy constructor for the class. /// @@ -184,6 +198,8 @@ public void SelectElectrodes(List electrodes) throw new InvalidOperationException($"Channel map does not match the expected number of active channels " + $"for a NeuropixelsV2 probe. Expected {NeuropixelsV2.ChannelCount}, but there are {ChannelMap.Count} values."); } + + ChannelConfiguration.UpdateDeviceChannelIndices(ChannelMap); } /// @@ -213,6 +229,7 @@ public string ChannelConfigurationString { var jsonString = Encoding.UTF8.GetString(Convert.FromBase64String(value)); ChannelConfiguration = JsonConvert.DeserializeObject(jsonString); + SelectElectrodes(NeuropixelsV2eProbeGroup.ToChannelMap(ChannelConfiguration)); } } } diff --git a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs index 00c1377d..026a666d 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs @@ -255,5 +255,28 @@ public static List ToChannelMap(NeuropixelsV2eP return channelMap.OrderBy(e => e.Channel).ToList(); } + + /// + /// Updates the based on the given channel map. + /// + /// Existing . + internal void UpdateDeviceChannelIndices(List channelMap) + { + var numberOfContacts = NumberOfContacts; + + int[] newDeviceChannelIndices = new int[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + newDeviceChannelIndices[i] = -1; + } + + foreach (var e in channelMap) + { + newDeviceChannelIndices[e.Index] = e.Channel; + } + + UpdateDeviceChannelIndices(0, newDeviceChannelIndices); + } } } From b94f5bc1978fa97d32af6417bc36af8694914979 Mon Sep 17 00:00:00 2001 From: jonnew Date: Tue, 13 Aug 2024 16:05:19 -0400 Subject: [PATCH 14/35] Convert BreakoutAnalogOutput data to offset binary - The analog outputs on the breakout board expect 16-bit words in offset binary - We were using twos-complement. This fix converts all outgoing data to offset binary before the final calls to device.Write() --- OpenEphys.Onix1/BreakoutAnalogOutput.cs | 21 ++++++++++++++++---- OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/OpenEphys.Onix1/BreakoutAnalogOutput.cs b/OpenEphys.Onix1/BreakoutAnalogOutput.cs index 0b3c47b0..cbcf0918 100644 --- a/OpenEphys.Onix1/BreakoutAnalogOutput.cs +++ b/OpenEphys.Onix1/BreakoutAnalogOutput.cs @@ -37,7 +37,7 @@ public class BreakoutAnalogOutput : Sink /// /// 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) + public override unsafe IObservable Process(IObservable source) { var dataType = DataType; return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => @@ -87,7 +87,14 @@ public override IObservable Process(IObservable source) } var dataSize = outputBuffer.Step * outputBuffer.Rows; - device.Write(outputBuffer.Data, dataSize); + + // twos-complement to offset binary + var dataPtr = (short *)outputBuffer.Data.ToPointer(); + const short Mask = -32768; + for (int i = 0; i < dataSize / sizeof(short); i++) + *(dataPtr + i) ^= Mask; + + device.Write(outputBuffer.Data, dataSize); }); }); } @@ -108,6 +115,12 @@ public IObservable Process(IObservable source) return source.Do(data => { AssertChannelCount(data.Length); + var samples = new ushort[data.Length]; + for (int i = 0; i < samples.Length; i++) + { + const short Mask = -32768; + data[i] ^= Mask; // twos-complement to offset binary + } device.Write(data); }); }); @@ -130,10 +143,10 @@ public IObservable Process(IObservable source) return source.Do(data => { AssertChannelCount(data.Length); - var samples = new short[data.Length]; + var samples = new ushort[data.Length]; for (int i = 0; i < samples.Length; i++) { - samples[i] = (short)(data[i] * divisionsPerVolt); + samples[i] = (ushort)(data[i] * divisionsPerVolt + BreakoutAnalogIO.DacMidScale); } device.Write(samples); diff --git a/OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs b/OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs index 4a65ae10..4f6749c1 100644 --- a/OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs +++ b/OpenEphys.Onix1/ConfigureBreakoutAnalogIO.cs @@ -281,6 +281,7 @@ static class BreakoutAnalogIO // constants public const int ChannelCount = 12; public const int NumberOfDivisions = 1 << 16; + public const int DacMidScale = 1 << 15; // managed registers public const uint ENABLE = 0; From a5aff1b702efa1c7fb7f9b65ce873e8f0947d38c Mon Sep 17 00:00:00 2001 From: bparks13 Date: Mon, 12 Aug 2024 14:21:40 -0400 Subject: [PATCH 15/35] Address review comments - Fix scaling due to resolution differences - Reorganize GUI elements - Check if the probe is in view after every zoom event - Check for probe in view after panning event - Add tooltips to buttons - Ensure that configuration options are not saved unless the user presses Okay --- .../ChannelConfigurationDialog.Designer.cs | 79 +++-- .../ChannelConfigurationDialog.cs | 335 ++++++++++++++---- .../GenericDeviceDialog.Designer.cs | 24 +- ...europixelsV2eChannelConfigurationDialog.cs | 10 +- .../NeuropixelsV2eDialog.Designer.cs | 35 +- .../NeuropixelsV2eHeadstageDialog.Designer.cs | 56 +-- ...elsV2eProbeConfigurationDialog.Designer.cs | 225 ++++++------ .../NeuropixelsV2eProbeConfigurationDialog.cs | 4 - ...europixelsV2eProbeConfigurationDialog.resx | 18 +- ...europixelsV2QuadShankProbeConfiguration.cs | 8 +- .../NeuropixelsV2RegisterContext.cs | 1 - 11 files changed, 528 insertions(+), 267 deletions(-) delete mode 100644 OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs index f6d1a963..11e574a0 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs @@ -31,13 +31,13 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.zedGraphChannels = new ZedGraph.ZedGraphControl(); + this.buttonCancel = new System.Windows.Forms.Button(); + this.buttonOK = new System.Windows.Forms.Button(); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.fileMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.dropDownOpenFile = new System.Windows.Forms.ToolStripMenuItem(); this.dropDownSaveFile = new System.Windows.Forms.ToolStripMenuItem(); this.dropDownLoadDefault = 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(); @@ -49,7 +49,8 @@ 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, 36); + this.splitContainer1.Location = new System.Drawing.Point(0, 24); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -61,8 +62,9 @@ private void InitializeComponent() // this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); this.splitContainer1.Panel2.Controls.Add(this.buttonOK); - this.splitContainer1.Size = new System.Drawing.Size(686, 681); - this.splitContainer1.SplitterDistance = 585; + this.splitContainer1.Size = new System.Drawing.Size(457, 442); + this.splitContainer1.SplitterDistance = 400; + this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 0; // // zedGraphChannels @@ -70,7 +72,7 @@ private void InitializeComponent() this.zedGraphChannels.AutoSize = true; this.zedGraphChannels.Dock = System.Windows.Forms.DockStyle.Fill; this.zedGraphChannels.Location = new System.Drawing.Point(0, 0); - this.zedGraphChannels.Margin = new System.Windows.Forms.Padding(6, 8, 6, 8); + this.zedGraphChannels.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); this.zedGraphChannels.Name = "zedGraphChannels"; this.zedGraphChannels.ScrollGrace = 0D; this.zedGraphChannels.ScrollMaxX = 0D; @@ -79,20 +81,44 @@ private void InitializeComponent() this.zedGraphChannels.ScrollMinX = 0D; this.zedGraphChannels.ScrollMinY = 0D; this.zedGraphChannels.ScrollMinY2 = 0D; - this.zedGraphChannels.Size = new System.Drawing.Size(686, 585); + this.zedGraphChannels.Size = new System.Drawing.Size(457, 400); this.zedGraphChannels.TabIndex = 4; this.zedGraphChannels.UseExtendedPrintDialog = true; this.zedGraphChannels.ZoomEvent += new ZedGraph.ZedGraphControl.ZoomEventHandler(this.ZoomEvent); // + // 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(339, 7); + this.buttonCancel.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(108, 26); + 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(217, 7); + this.buttonOK.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonOK.Name = "buttonOK"; + this.buttonOK.Size = new System.Drawing.Size(108, 26); + this.buttonOK.TabIndex = 3; + this.buttonOK.Text = "OK"; + this.buttonOK.UseVisualStyleBackColor = true; + this.buttonOK.Click += new System.EventHandler(this.ButtonOK); + // // 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.fileMenuItem}); this.menuStrip.Location = new System.Drawing.Point(0, 0); this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(686, 36); + this.menuStrip.Padding = new System.Windows.Forms.Padding(4, 1, 0, 1); + this.menuStrip.Size = new System.Drawing.Size(457, 24); this.menuStrip.TabIndex = 5; this.menuStrip.Text = "menuStripChannelConfiguration"; // @@ -103,60 +129,39 @@ private void InitializeComponent() this.dropDownSaveFile, this.dropDownLoadDefault}); this.fileMenuItem.Name = "fileMenuItem"; - this.fileMenuItem.Size = new System.Drawing.Size(54, 30); + this.fileMenuItem.Size = new System.Drawing.Size(37, 22); this.fileMenuItem.Text = "File"; // // dropDownOpenFile // this.dropDownOpenFile.Name = "dropDownOpenFile"; - this.dropDownOpenFile.Size = new System.Drawing.Size(215, 34); + this.dropDownOpenFile.Size = new System.Drawing.Size(141, 22); this.dropDownOpenFile.Text = "Open File"; this.dropDownOpenFile.Click += new System.EventHandler(this.MenuItemOpenFile); // // dropDownSaveFile // this.dropDownSaveFile.Name = "dropDownSaveFile"; - this.dropDownSaveFile.Size = new System.Drawing.Size(215, 34); + this.dropDownSaveFile.Size = new System.Drawing.Size(141, 22); this.dropDownSaveFile.Text = "Save File"; this.dropDownSaveFile.Click += new System.EventHandler(this.MenuItemSaveFile); // // dropDownLoadDefault // this.dropDownLoadDefault.Name = "dropDownLoadDefault"; - this.dropDownLoadDefault.Size = new System.Drawing.Size(215, 34); + this.dropDownLoadDefault.Size = new System.Drawing.Size(141, 22); this.dropDownLoadDefault.Text = "Load Default"; this.dropDownLoadDefault.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, 24); - 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, 24); - 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.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(686, 717); + this.ClientSize = new System.Drawing.Size(457, 466); this.Controls.Add(this.splitContainer1); this.Controls.Add(this.menuStrip); this.MainMenuStrip = this.menuStrip; + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.Name = "ChannelConfigurationDialog"; this.Text = "ChannelConfigurationDialog"; this.splitContainer1.Panel1.ResumeLayout(false); diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index 18359bc1..89fb002a 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -86,15 +86,29 @@ internal virtual void ZoomEvent(ZedGraphControl sender, ZoomState oldState, Zoom if (newState.Type == ZoomState.StateType.Zoom || newState.Type == ZoomState.StateType.WheelZoom) { SetEqualAxisLimits(sender); - CheckZoomBoundaries(sender); - CenterAxesOnCursor(sender); + + if (CheckZoomBoundaries(sender)) + { + CenterAxesOnCursor(sender); + CheckProbeIsInView(sender); + } + else + { + sender.ZoomOut(sender.GraphPane); + } + } + else if (newState.Type == ZoomState.StateType.Pan) + { + CheckProbeIsInView(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; + var rangeX = CalculateScaleRange(zedGraphControl.GraphPane.XAxis.Scale); + var rangeY = CalculateScaleRange(zedGraphControl.GraphPane.YAxis.Scale); + + if (rangeX == rangeY) return; if (rangeX > rangeY) { @@ -114,15 +128,25 @@ private void SetEqualAxisLimits(ZedGraphControl zedGraphControl) private void CenterAxesOnCursor(ZedGraphControl zedGraphControl) { + if ((zedGraphControl.GraphPane.XAxis.Scale.Min == ZoomOutBoundaryMinX && + zedGraphControl.GraphPane.XAxis.Scale.Max == ZoomOutBoundaryMaxX && + zedGraphControl.GraphPane.YAxis.Scale.Min == ZoomOutBoundaryMinY && + zedGraphControl.GraphPane.YAxis.Scale.Max == ZoomOutBoundaryMaxY) || + (CalculateScaleRange(zedGraphControl.GraphPane.XAxis.Scale) == ZoomInBoundaryX && + CalculateScaleRange(zedGraphControl.GraphPane.YAxis.Scale) == ZoomInBoundaryY)) + { + return; + } + var mouseClientPosition = PointToClient(Cursor.Position); mouseClientPosition.X -= (zedGraphControl.Parent.Width - zedGraphControl.Width) / 2; var currentMousePosition = TransformPixelsToCoordinates(mouseClientPosition, zedGraphControl.GraphPane); - var centerX = CalculateRange(zedGraphChannels.GraphPane.XAxis.Scale) / 2 + + var centerX = CalculateScaleRange(zedGraphChannels.GraphPane.XAxis.Scale) / 2 + zedGraphControl.GraphPane.XAxis.Scale.Min; - var centerY = CalculateRange(zedGraphChannels.GraphPane.YAxis.Scale) / 2 + + var centerY = CalculateScaleRange(zedGraphChannels.GraphPane.YAxis.Scale) / 2 + zedGraphControl.GraphPane.YAxis.Scale.Min; var diffX = centerX - currentMousePosition.X; @@ -135,7 +159,174 @@ private void CenterAxesOnCursor(ZedGraphControl zedGraphControl) zedGraphControl.GraphPane.YAxis.Scale.Max += diffY; } - private static double CalculateRange(Scale scale) + private struct ProbeEdge + { + public double Left; + public double Bottom; + public double Right; + public double Top; + + public ProbeEdge(ZedGraphControl zedGraphControl) + { + Left = GetProbeContourMinX(zedGraphControl.GraphPane.GraphObjList); + Right = GetProbeContourMaxX(zedGraphControl.GraphPane.GraphObjList); + Bottom = GetProbeContourMinY(zedGraphControl.GraphPane.GraphObjList); + Top = GetProbeContourMaxY(zedGraphControl.GraphPane.GraphObjList); + } + } + + private struct ScaleEdge + { + public double Left; + public double Bottom; + public double Right; + public double Top; + + public ScaleEdge(ZedGraphControl zedGraphControl) + { + Left = zedGraphControl.GraphPane.XAxis.Scale.Min; + Right = zedGraphControl.GraphPane.XAxis.Scale.Max; + Bottom = zedGraphControl.GraphPane.YAxis.Scale.Min; + Top = zedGraphControl.GraphPane.YAxis.Scale.Max; + } + } + + private void CheckProbeIsInView(ZedGraphControl zedGraphControl) + { + var probeEdge = new ProbeEdge(zedGraphControl); + var scaleEdge = new ScaleEdge(zedGraphControl); + + var rangeX = CalculateScaleRange(zedGraphControl.GraphPane.XAxis.Scale); + var rangeY = CalculateScaleRange(zedGraphControl.GraphPane.YAxis.Scale); + + var boundaryX = rangeX / 4; + var boundaryY = rangeY / 4; + + if (IsProbeCentered(probeEdge, scaleEdge)) + { + return; + } + else if (IsProbeCenteredX(probeEdge, scaleEdge)) + { + if (IsProbeTooHigh(probeEdge, scaleEdge, boundaryY)) + { + MoveProbeDown(zedGraphControl, probeEdge, scaleEdge, boundaryY); + } + else if (IsProbeTooLow(probeEdge, scaleEdge, boundaryY)) + { + MoveProbeUp(zedGraphControl, probeEdge, scaleEdge, boundaryY); + } + } + else if (IsProbeCenteredY(probeEdge, scaleEdge)) + { + if (IsProbeTooLeft(probeEdge, scaleEdge, boundaryX)) + { + MoveProbeRight(zedGraphControl, probeEdge, scaleEdge, boundaryX); + } + else if (IsProbeTooRight(probeEdge, scaleEdge, boundaryX)) + { + MoveProbeLeft(zedGraphControl, probeEdge, scaleEdge, boundaryX); + } + } + else + { + if (IsProbeTooHigh(probeEdge, scaleEdge, boundaryY)) + { + MoveProbeDown(zedGraphControl, probeEdge, scaleEdge, boundaryY); + } + else if (IsProbeTooLow(probeEdge, scaleEdge, boundaryY)) + { + MoveProbeUp(zedGraphControl, probeEdge, scaleEdge, boundaryY); + } + + if (IsProbeTooLeft(probeEdge, scaleEdge, boundaryX)) + { + MoveProbeRight(zedGraphControl, probeEdge, scaleEdge, boundaryX); + } + else if (IsProbeTooRight(probeEdge, scaleEdge, boundaryX)) + { + MoveProbeLeft(zedGraphControl, probeEdge, scaleEdge, boundaryX); + } + } + } + + private void MoveProbeLeft(ZedGraphControl zedGraphControl, ProbeEdge probeEdge, ScaleEdge scaleEdge, double boundary) + { + bool probeSmallerThanScale = probeEdge.Right - probeEdge.Left < scaleEdge.Right - scaleEdge.Left; + + double diff = probeSmallerThanScale ? probeEdge.Right - scaleEdge.Right : probeEdge.Left - (scaleEdge.Left + boundary); + + zedGraphControl.GraphPane.XAxis.Scale.Min += diff; + zedGraphControl.GraphPane.XAxis.Scale.Max += diff; + } + + private bool IsProbeTooRight(ProbeEdge probeEdge, ScaleEdge scaleEdge, double boundary) + { + return probeEdge.Left > scaleEdge.Left + boundary; + } + + private void MoveProbeRight(ZedGraphControl zedGraphControl, ProbeEdge probeEdge, ScaleEdge scaleEdge, double boundary) + { + bool probeSmallerThanScale = probeEdge.Right - probeEdge.Left < scaleEdge.Right - scaleEdge.Left; + + var diff = probeSmallerThanScale ? probeEdge.Left - scaleEdge.Left : probeEdge.Right - (scaleEdge.Right - boundary); + + zedGraphControl.GraphPane.XAxis.Scale.Min += diff; + zedGraphControl.GraphPane.XAxis.Scale.Max += diff; + } + + private bool IsProbeTooLeft(ProbeEdge probeEdge, ScaleEdge scaleEdge, double boundary) + { + return probeEdge.Right < scaleEdge.Right - boundary; + } + + private void MoveProbeUp(ZedGraphControl zedGraphControl, ProbeEdge probeEdge, ScaleEdge scaleEdge, double boundary) + { + var diff = probeEdge.Top - (scaleEdge.Top - boundary); + + zedGraphControl.GraphPane.YAxis.Scale.Min += diff; + zedGraphControl.GraphPane.YAxis.Scale.Max += diff; + } + + private bool IsProbeTooLow(ProbeEdge probeEdge, ScaleEdge scaleEdge, double boundary) + { + return probeEdge.Top < scaleEdge.Top - boundary; + } + + private void MoveProbeDown(ZedGraphControl zedGraphControl, ProbeEdge probeEdge, ScaleEdge scaleEdge, double boundary) + { + var diff = probeEdge.Bottom - (scaleEdge.Bottom + boundary); + + zedGraphControl.GraphPane.YAxis.Scale.Min += diff; + zedGraphControl.GraphPane.YAxis.Scale.Max += diff; + } + + private bool IsProbeTooHigh(ProbeEdge probeEdge, ScaleEdge scaleEdge, double boundary) + { + return probeEdge.Bottom > scaleEdge.Bottom + boundary; + } + + private bool IsProbeCenteredY(ProbeEdge probeEdge, ScaleEdge scaleEdge) + { + return probeEdge.Bottom >= scaleEdge.Bottom && probeEdge.Top <= scaleEdge.Top || + probeEdge.Bottom <= scaleEdge.Bottom && probeEdge.Top >= scaleEdge.Top; + } + + private bool IsProbeCenteredX(ProbeEdge probeEdge, ScaleEdge scaleEdge) + { + return probeEdge.Left >= scaleEdge.Left && probeEdge.Right <= scaleEdge.Right || + probeEdge.Left <= scaleEdge.Left && probeEdge.Right >= scaleEdge.Right; + } + + private bool IsProbeCentered(ProbeEdge probeEdge, ScaleEdge scaleEdge) + { + return (probeEdge.Left >= scaleEdge.Left && probeEdge.Right <= scaleEdge.Right && + probeEdge.Bottom >= scaleEdge.Bottom && probeEdge.Top <= scaleEdge.Top) || + (probeEdge.Left <= scaleEdge.Left && probeEdge.Right >= scaleEdge.Right && + probeEdge.Bottom <= scaleEdge.Bottom && probeEdge.Top >= scaleEdge.Top); + } + + private static double CalculateScaleRange(Scale scale) { return scale.Max - scale.Min; } @@ -146,10 +337,10 @@ private static double CalculateRange(Scale scale) /// /// When zooming in excessively, it is possible to lose view of the entire probe and make it /// difficult to zoom back out. This value is the boundary, where if the current zoom would make the x-axis - /// less than , would - /// automatically zoom back out to match . + /// less than , would + /// automatically zoom back out to match . /// - public double ZoomBoundaryX { get; internal set; } = 20; + public double ZoomInBoundaryX { get; internal set; } = 20; /// /// Gets the value of the zoom boundary on the y-axis. @@ -157,44 +348,57 @@ private static double CalculateRange(Scale scale) /// /// When zooming in excessively, it is possible to lose view of the entire probe and make it /// difficult to zoom back out. This value is the boundary, where if the current zoom would make the y-axis - /// less than , would - /// automatically zoom back out to match . + /// less than , would + /// automatically zoom back out to match . /// - public double ZoomBoundaryY { get; internal set; } = 20; + public double ZoomInBoundaryY { get; internal set; } = 20; /// - /// Checks if the is too zoomed in. If the graph is too zoomed in, - /// reset the boundaries to match and . + /// Checks if the is too zoomed in or out. If the graph is too zoomed in, + /// reset the boundaries to match and . If the graph is too zoomed out, + /// reset the boundaries to match the automatically generated boundaries based on the size of the probe. /// /// A object. - /// True if the control is zoomed out, false if it is zoomed in too far. + /// True if the zoom boundary has been correctly handled, False if the previous zoom state should be reinstated. private bool CheckZoomBoundaries(ZedGraphControl zedGraphControl) { - var rangeX = CalculateRange(zedGraphControl.GraphPane.XAxis.Scale); - var rangeY = CalculateRange(zedGraphControl.GraphPane.YAxis.Scale); + var rangeX = CalculateScaleRange(zedGraphControl.GraphPane.XAxis.Scale); + var rangeY = CalculateScaleRange(zedGraphControl.GraphPane.YAxis.Scale); - if (rangeX < ZoomBoundaryX || rangeY < ZoomBoundaryY) + if (rangeX < ZoomInBoundaryX || rangeY < ZoomInBoundaryY) { - if (rangeX < ZoomBoundaryX) + if (rangeX / ZoomInBoundaryX == zedGraphControl.ZoomStepFraction || rangeY / ZoomInBoundaryY == zedGraphControl.ZoomStepFraction) + return false; + else { - var diffX = (ZoomBoundaryX - rangeX) / 2; + var diff = (ZoomInBoundaryX - rangeX) / 2; + zedGraphControl.GraphPane.XAxis.Scale.Min -= diff; + zedGraphControl.GraphPane.XAxis.Scale.Max += diff; - zedGraphControl.GraphPane.XAxis.Scale.Min -= diffX; - zedGraphControl.GraphPane.XAxis.Scale.Max += diffX; - } + diff = (ZoomInBoundaryY - rangeY) / 2; + zedGraphControl.GraphPane.YAxis.Scale.Min -= diff; + zedGraphControl.GraphPane.YAxis.Scale.Max += diff; - if (rangeY < ZoomBoundaryY) + return true; + } + } + else + { + if (zedGraphControl.GraphPane.XAxis.Scale.Min < ZoomOutBoundaryMinX && zedGraphControl.GraphPane.XAxis.Scale.Max > ZoomOutBoundaryMaxX || + CalculateScaleRange(zedGraphControl.GraphPane.XAxis.Scale) >= ZoomOutBoundaryMaxX - ZoomOutBoundaryMinX) { - var diffY = (ZoomBoundaryY - rangeY) / 2; - - zedGraphControl.GraphPane.YAxis.Scale.Min -= diffY; - zedGraphControl.GraphPane.YAxis.Scale.Max += diffY; + zedGraphControl.GraphPane.XAxis.Scale.Min = ZoomOutBoundaryMinX; + zedGraphControl.GraphPane.XAxis.Scale.Max = ZoomOutBoundaryMaxX; + } + if (zedGraphControl.GraphPane.YAxis.Scale.Min < ZoomOutBoundaryMinY && zedGraphControl.GraphPane.YAxis.Scale.Max > ZoomOutBoundaryMaxY || + CalculateScaleRange(zedGraphControl.GraphPane.YAxis.Scale) >= ZoomOutBoundaryMaxY - ZoomOutBoundaryMinY) + { + zedGraphControl.GraphPane.YAxis.Scale.Min = ZoomOutBoundaryMinY; + zedGraphControl.GraphPane.YAxis.Scale.Max = ZoomOutBoundaryMaxY; } - return false; + return true; } - - return true; } private void FormShown(object sender, EventArgs e) @@ -267,6 +471,7 @@ internal void DrawProbeGroup() DrawProbeContour(); SetEqualAspectRatio(); + SetZoomOutBoundaries(); DrawContacts(); HighlightEnabledContacts(); HighlightSelectedContacts(); @@ -274,6 +479,11 @@ internal void DrawProbeGroup() DrawScale(); } + private double ZoomOutBoundaryMinX = default; + private double ZoomOutBoundaryMaxX = default; + private double ZoomOutBoundaryMinY = default; + private double ZoomOutBoundaryMaxY = default; + internal void DrawProbeContour() { if (ChannelConfiguration == null) @@ -291,15 +501,29 @@ internal void DrawProbeContour() } } + private void SetZoomOutBoundaries() + { + // TODO: These boundaries need to be redefined when loading a new file + var rangeX = CalculateScaleRange(zedGraphChannels.GraphPane.XAxis.Scale); + var rangeY = CalculateScaleRange(zedGraphChannels.GraphPane.YAxis.Scale); + + var margin = Math.Min(rangeX, rangeY) * 0.02; + + ZoomOutBoundaryMinX = zedGraphChannels.GraphPane.XAxis.Scale.Min - margin; + ZoomOutBoundaryMinY = zedGraphChannels.GraphPane.YAxis.Scale.Min - margin; + ZoomOutBoundaryMaxX = zedGraphChannels.GraphPane.XAxis.Scale.Max + margin; + ZoomOutBoundaryMaxY = zedGraphChannels.GraphPane.YAxis.Scale.Max + margin; + } + 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 minX = GetProbeContourMinX(zedGraphChannels.GraphPane.GraphObjList); + var minY = GetProbeContourMinY(zedGraphChannels.GraphPane.GraphObjList); + var maxX = GetProbeContourMaxX(zedGraphChannels.GraphPane.GraphObjList); + var maxY = GetProbeContourMaxY(zedGraphChannels.GraphPane.GraphObjList); var rangeX = maxX - minX; var rangeY = maxY - minY; @@ -319,13 +543,11 @@ internal void SetEqualAspectRatio() maxX += diff; } - var margin = Math.Max(rangeX, rangeY) * 0.02; - - zedGraphChannels.GraphPane.XAxis.Scale.Min = minX - margin; - zedGraphChannels.GraphPane.XAxis.Scale.Max = maxX + margin; + zedGraphChannels.GraphPane.XAxis.Scale.Min = minX; + zedGraphChannels.GraphPane.XAxis.Scale.Max = maxX; - zedGraphChannels.GraphPane.YAxis.Scale.Min = minY - margin; - zedGraphChannels.GraphPane.YAxis.Scale.Max = maxY + margin; + zedGraphChannels.GraphPane.YAxis.Scale.Min = minY; + zedGraphChannels.GraphPane.YAxis.Scale.Max = maxY; } internal void DrawContacts() @@ -556,25 +778,25 @@ internal float ContactSize() return 1f; } - internal static double MinX(GraphObjList graphObjs) + internal static double GetProbeContourMinX(GraphObjList graphObjs) { return graphObjs.OfType() .Min(obj => { return obj.Points.Min(p => p.X); }); } - internal static double MinY(GraphObjList graphObjs) + internal static double GetProbeContourMinY(GraphObjList graphObjs) { return graphObjs.OfType() .Min(obj => { return obj.Points.Min(p => p.Y); }); } - internal static double MaxX(GraphObjList graphObjs) + internal static double GetProbeContourMaxX(GraphObjList graphObjs) { return graphObjs.OfType() .Max(obj => { return obj.Points.Max(p => p.X); }); } - internal static double MaxY(GraphObjList graphObjs) + internal static double GetProbeContourMaxY(GraphObjList graphObjs) { return graphObjs.OfType() .Max(obj => { return obj.Points.Max(p => p.Y); }); @@ -673,8 +895,7 @@ private void ZedGraphChannels_Resize(object sender, EventArgs e) UpdateControlSizeBasedOnAxisSize(); UpdateFontSize(); DrawScale(); - zedGraphChannels.AxisChange(); - zedGraphChannels.Refresh(); + RefreshZedGraph(); } /// @@ -738,16 +959,6 @@ private void ButtonOK(object sender, EventArgs e) } } - 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, false); - - UpdateFontSize(); - } - internal void ResetZoom() { zedGraphChannels.ZoomOutAll(zedGraphChannels.GraphPane); @@ -772,8 +983,8 @@ public void MoveToVerticalPosition(float 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 minY = GetProbeContourMinY(zedGraphChannels.GraphPane.GraphObjList); + var maxY = GetProbeContourMaxY(zedGraphChannels.GraphPane.GraphObjList); var newMinY = (maxY - minY - currentRange) * relativePosition; @@ -783,8 +994,8 @@ public void MoveToVerticalPosition(float relativePosition) internal float GetRelativeVerticalPosition() { - var minY = MinY(zedGraphChannels.GraphPane.GraphObjList); - var maxY = MaxY(zedGraphChannels.GraphPane.GraphObjList); + var minY = GetProbeContourMinY(zedGraphChannels.GraphPane.GraphObjList); + var maxY = GetProbeContourMaxY(zedGraphChannels.GraphPane.GraphObjList); var currentRange = zedGraphChannels.GraphPane.YAxis.Scale.Max - zedGraphChannels.GraphPane.YAxis.Scale.Min; diff --git a/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs b/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs index 9fbeb09b..4722774d 100644 --- a/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs @@ -42,8 +42,9 @@ private void InitializeComponent() // this.propertyGrid.Dock = System.Windows.Forms.DockStyle.Fill; this.propertyGrid.Location = new System.Drawing.Point(0, 0); + this.propertyGrid.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.propertyGrid.Name = "propertyGrid"; - this.propertyGrid.Size = new System.Drawing.Size(378, 532); + this.propertyGrid.Size = new System.Drawing.Size(252, 345); this.propertyGrid.TabIndex = 0; // // splitContainer1 @@ -51,6 +52,7 @@ private void InitializeComponent() 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.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -62,17 +64,19 @@ private void InitializeComponent() // 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.Size = new System.Drawing.Size(252, 386); + this.splitContainer1.SplitterDistance = 345; + this.splitContainer1.SplitterWidth = 3; 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.Location = new System.Drawing.Point(135, 5); + this.buttonCancel.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.buttonCancel.Name = "buttonCancel"; - this.buttonCancel.Size = new System.Drawing.Size(162, 38); + this.buttonCancel.Size = new System.Drawing.Size(108, 25); this.buttonCancel.TabIndex = 6; this.buttonCancel.Text = "Cancel"; this.buttonCancel.UseVisualStyleBackColor = true; @@ -80,9 +84,10 @@ private void InitializeComponent() // 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.Location = new System.Drawing.Point(13, 5); + this.buttonOK.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.buttonOK.Name = "buttonOK"; - this.buttonOK.Size = new System.Drawing.Size(162, 38); + this.buttonOK.Size = new System.Drawing.Size(108, 25); this.buttonOK.TabIndex = 5; this.buttonOK.Text = "OK"; this.buttonOK.UseVisualStyleBackColor = true; @@ -90,10 +95,11 @@ private void InitializeComponent() // // GenericDeviceDialog // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(378, 594); + this.ClientSize = new System.Drawing.Size(252, 386); this.Controls.Add(this.splitContainer1); + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "GenericDeviceDialog"; diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index b7a28be2..4f845014 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -41,8 +41,8 @@ public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2QuadShankProbeConfi ProbeConfiguration = probeConfiguration; - ZoomBoundaryX = 400; - ZoomBoundaryY = 400; + ZoomInBoundaryX = 600; + ZoomInBoundaryY = 600; HighlightEnabledContacts(); UpdateContactLabels(); @@ -138,9 +138,9 @@ internal override void DrawScale() 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); + var x = GetProbeContourMaxX(zedGraphChannels.GraphPane.GraphObjList) + 50; + var minY = GetProbeContourMinY(zedGraphChannels.GraphPane.GraphObjList); + var maxY = GetProbeContourMaxY(zedGraphChannels.GraphPane.GraphObjList); zedGraphChannels.GraphPane.CurveList.Clear(); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs index abd87db3..d8fdc335 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs @@ -45,27 +45,28 @@ private void InitializeComponent() // // 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, 36); + this.menuStrip.Padding = new System.Windows.Forms.Padding(4, 1, 0, 1); + this.menuStrip.Size = new System.Drawing.Size(843, 24); this.menuStrip.TabIndex = 0; this.menuStrip.Text = "menuStripNeuropixelsV2e"; // // fileToolStripMenuItem // this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 30); + this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 22); this.fileToolStripMenuItem.Text = "File"; // // 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, 36); + this.splitContainer1.Location = new System.Drawing.Point(0, 24); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -76,17 +77,19 @@ private void InitializeComponent() // splitContainer1.Panel2 // this.splitContainer1.Panel2.Controls.Add(this.panel1); - this.splitContainer1.Size = new System.Drawing.Size(1265, 780); - this.splitContainer1.SplitterDistance = 734; + this.splitContainer1.Size = new System.Drawing.Size(843, 506); + this.splitContainer1.SplitterDistance = 475; + this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 2; // // tabControlProbe // this.tabControlProbe.Dock = System.Windows.Forms.DockStyle.Fill; this.tabControlProbe.Location = new System.Drawing.Point(0, 0); + this.tabControlProbe.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.tabControlProbe.Name = "tabControlProbe"; this.tabControlProbe.SelectedIndex = 0; - this.tabControlProbe.Size = new System.Drawing.Size(1265, 734); + this.tabControlProbe.Size = new System.Drawing.Size(843, 475); this.tabControlProbe.TabIndex = 1; // // panel1 @@ -95,16 +98,18 @@ private void InitializeComponent() 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.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(1265, 42); + this.panel1.Size = new System.Drawing.Size(843, 28); 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, 4); + this.buttonCancel.Location = new System.Drawing.Point(753, 4); + this.buttonCancel.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.buttonCancel.Name = "buttonCancel"; - this.buttonCancel.Size = new System.Drawing.Size(124, 34); + this.buttonCancel.Size = new System.Drawing.Size(83, 22); this.buttonCancel.TabIndex = 1; this.buttonCancel.Text = "Cancel"; this.buttonCancel.UseVisualStyleBackColor = true; @@ -113,9 +118,10 @@ private void InitializeComponent() // 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, 4); + this.buttonOkay.Location = new System.Drawing.Point(664, 4); + this.buttonOkay.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.buttonOkay.Name = "buttonOkay"; - this.buttonOkay.Size = new System.Drawing.Size(124, 34); + this.buttonOkay.Size = new System.Drawing.Size(83, 22); this.buttonOkay.TabIndex = 0; this.buttonOkay.Text = "OK"; this.buttonOkay.UseVisualStyleBackColor = true; @@ -123,13 +129,14 @@ private void InitializeComponent() // // NeuropixelsV2eDialog // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1265, 816); + this.ClientSize = new System.Drawing.Size(843, 530); this.Controls.Add(this.splitContainer1); this.Controls.Add(this.menuStrip); this.DoubleBuffered = true; this.MainMenuStrip = this.menuStrip; + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.Name = "NeuropixelsV2eDialog"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "NeuropixelsV2eDialog"; diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs index bc43b86d..77436f6f 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs @@ -54,18 +54,20 @@ private void InitializeComponent() 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.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.tabControl1.Name = "tabControl1"; this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(1295, 731); + this.tabControl1.Size = new System.Drawing.Size(863, 467); this.tabControl1.TabIndex = 0; // // tabPageNeuropixelsV2e // this.tabPageNeuropixelsV2e.Controls.Add(this.panelNeuropixelsV2e); - this.tabPageNeuropixelsV2e.Location = new System.Drawing.Point(4, 29); + this.tabPageNeuropixelsV2e.Location = new System.Drawing.Point(4, 22); + this.tabPageNeuropixelsV2e.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.tabPageNeuropixelsV2e.Name = "tabPageNeuropixelsV2e"; - this.tabPageNeuropixelsV2e.Padding = new System.Windows.Forms.Padding(3); - this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(1287, 698); + this.tabPageNeuropixelsV2e.Padding = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(855, 441); this.tabPageNeuropixelsV2e.TabIndex = 0; this.tabPageNeuropixelsV2e.Text = "NeuropixelsV2e"; this.tabPageNeuropixelsV2e.UseVisualStyleBackColor = true; @@ -73,18 +75,20 @@ private void InitializeComponent() // panelNeuropixelsV2e // this.panelNeuropixelsV2e.Dock = System.Windows.Forms.DockStyle.Fill; - this.panelNeuropixelsV2e.Location = new System.Drawing.Point(3, 3); + this.panelNeuropixelsV2e.Location = new System.Drawing.Point(2, 2); + this.panelNeuropixelsV2e.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.panelNeuropixelsV2e.Name = "panelNeuropixelsV2e"; - this.panelNeuropixelsV2e.Size = new System.Drawing.Size(1281, 692); + this.panelNeuropixelsV2e.Size = new System.Drawing.Size(851, 437); this.panelNeuropixelsV2e.TabIndex = 0; // // tabPageBno055 // this.tabPageBno055.Controls.Add(this.panelBno055); - this.tabPageBno055.Location = new System.Drawing.Point(4, 29); + this.tabPageBno055.Location = new System.Drawing.Point(4, 22); + this.tabPageBno055.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.tabPageBno055.Name = "tabPageBno055"; - this.tabPageBno055.Padding = new System.Windows.Forms.Padding(3); - this.tabPageBno055.Size = new System.Drawing.Size(1287, 686); + this.tabPageBno055.Padding = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.tabPageBno055.Size = new System.Drawing.Size(855, 441); this.tabPageBno055.TabIndex = 1; this.tabPageBno055.Text = "Bno055"; this.tabPageBno055.UseVisualStyleBackColor = true; @@ -92,16 +96,18 @@ private void InitializeComponent() // panelBno055 // this.panelBno055.Dock = System.Windows.Forms.DockStyle.Fill; - this.panelBno055.Location = new System.Drawing.Point(3, 3); + this.panelBno055.Location = new System.Drawing.Point(2, 2); + this.panelBno055.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.panelBno055.Name = "panelBno055"; - this.panelBno055.Size = new System.Drawing.Size(1281, 680); + this.panelBno055.Size = new System.Drawing.Size(851, 437); 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.Location = new System.Drawing.Point(0, 24); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -113,17 +119,19 @@ 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 = 731; + this.splitContainer1.Size = new System.Drawing.Size(863, 503); + this.splitContainer1.SplitterDistance = 467; + this.splitContainer1.SplitterWidth = 3; 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, 1); + this.buttonCancel.Location = new System.Drawing.Point(747, 3); + this.buttonCancel.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.buttonCancel.Name = "buttonCancel"; - this.buttonCancel.Size = new System.Drawing.Size(162, 40); + this.buttonCancel.Size = new System.Drawing.Size(108, 26); this.buttonCancel.TabIndex = 6; this.buttonCancel.Text = "Cancel"; this.buttonCancel.UseVisualStyleBackColor = true; @@ -131,9 +139,10 @@ private void InitializeComponent() // 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, 1); + this.buttonOkay.Location = new System.Drawing.Point(625, 3); + this.buttonOkay.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.buttonOkay.Name = "buttonOkay"; - this.buttonOkay.Size = new System.Drawing.Size(162, 40); + this.buttonOkay.Size = new System.Drawing.Size(108, 26); this.buttonOkay.TabIndex = 5; this.buttonOkay.Text = "OK"; this.buttonOkay.UseVisualStyleBackColor = true; @@ -141,31 +150,32 @@ private void InitializeComponent() // // 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.Padding = new System.Windows.Forms.Padding(4, 1, 0, 1); + this.menuStrip1.Size = new System.Drawing.Size(863, 24); 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(37, 22); this.fileToolStripMenuItem.Text = "File"; // // NeuropixelsV2eHeadstageDialog // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1295, 811); + this.ClientSize = new System.Drawing.Size(863, 527); this.Controls.Add(this.splitContainer1); this.Controls.Add(this.menuStrip1); this.DoubleBuffered = true; this.MainMenuStrip = this.menuStrip1; + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.Name = "NeuropixelsV2eHeadstageDialog"; this.Text = "NeuropixelsV2eHeadstageDialog"; this.tabControl1.ResumeLayout(false); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs index 39867f12..7a372fe1 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs @@ -28,6 +28,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelGain; System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelProbe; System.Windows.Forms.Label probeCalibrationFile; @@ -35,8 +36,6 @@ private void InitializeComponent() System.Windows.Forms.Label label7; System.Windows.Forms.Label label6; System.Windows.Forms.Label labelPresets; - System.Windows.Forms.Label labelSelection; - System.Windows.Forms.Label label1; this.toolStripLabelProbeNumber = new System.Windows.Forms.ToolStripStatusLabel(); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -46,13 +45,13 @@ private void InitializeComponent() this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.splitContainer2 = new System.Windows.Forms.SplitContainer(); this.panelProbe = new System.Windows.Forms.Panel(); + this.trackBarProbePosition = new System.Windows.Forms.TrackBar(); this.panelChannelOptions = new System.Windows.Forms.Panel(); this.buttonClearCalibrationFile = new System.Windows.Forms.Button(); this.buttonChooseCalibrationFile = new System.Windows.Forms.Button(); this.textBoxProbeCalibrationFile = new System.Windows.Forms.TextBox(); this.comboBoxReference = new System.Windows.Forms.ComboBox(); this.comboBoxChannelPresets = 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(); @@ -60,6 +59,7 @@ 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.toolTip = new System.Windows.Forms.ToolTip(this.components); toolStripStatusLabelGain = new System.Windows.Forms.ToolStripStatusLabel(); toolStripStatusLabelProbe = new System.Windows.Forms.ToolStripStatusLabel(); probeCalibrationFile = new System.Windows.Forms.Label(); @@ -67,8 +67,6 @@ private void InitializeComponent() label7 = new System.Windows.Forms.Label(); label6 = new System.Windows.Forms.Label(); labelPresets = new System.Windows.Forms.Label(); - labelSelection = new System.Windows.Forms.Label(); - label1 = new System.Windows.Forms.Label(); this.menuStrip.SuspendLayout(); this.statusStrip.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); @@ -79,110 +77,102 @@ private void InitializeComponent() this.splitContainer2.Panel1.SuspendLayout(); this.splitContainer2.Panel2.SuspendLayout(); this.splitContainer2.SuspendLayout(); - this.panelChannelOptions.SuspendLayout(); + this.panelProbe.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).BeginInit(); + this.panelChannelOptions.SuspendLayout(); this.panel1.SuspendLayout(); this.SuspendLayout(); // // toolStripStatusLabelGain // + toolStripStatusLabelGain.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); toolStripStatusLabelGain.Name = "toolStripStatusLabelGain"; - toolStripStatusLabelGain.Size = new System.Drawing.Size(47, 25); - toolStripStatusLabelGain.Text = "Gain"; + toolStripStatusLabelGain.Size = new System.Drawing.Size(38, 17); + toolStripStatusLabelGain.Text = "Gain: "; // // toolStripStatusLabelProbe // + toolStripStatusLabelProbe.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); toolStripStatusLabelProbe.Name = "toolStripStatusLabelProbe"; - toolStripStatusLabelProbe.Size = new System.Drawing.Size(44, 25); + toolStripStatusLabelProbe.Size = new System.Drawing.Size(29, 17); toolStripStatusLabelProbe.Text = "SN: "; // // probeCalibrationFile // probeCalibrationFile.AutoSize = true; - probeCalibrationFile.Location = new System.Drawing.Point(8, 14); - probeCalibrationFile.MaximumSize = new System.Drawing.Size(200, 45); + probeCalibrationFile.Location = new System.Drawing.Point(5, 9); + probeCalibrationFile.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + probeCalibrationFile.MaximumSize = new System.Drawing.Size(133, 29); probeCalibrationFile.Name = "probeCalibrationFile"; - probeCalibrationFile.Size = new System.Drawing.Size(159, 20); + probeCalibrationFile.Size = new System.Drawing.Size(106, 13); probeCalibrationFile.TabIndex = 32; probeCalibrationFile.Text = "Probe Calibration File"; // // Reference // Reference.AutoSize = true; - Reference.Location = new System.Drawing.Point(18, 134); + Reference.Location = new System.Drawing.Point(12, 87); + Reference.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); Reference.Name = "Reference"; - Reference.Size = new System.Drawing.Size(84, 20); + Reference.Size = new System.Drawing.Size(57, 13); Reference.TabIndex = 30; Reference.Text = "Reference"; // // label7 // + label7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); label7.AutoSize = true; - label7.Location = new System.Drawing.Point(63, 218); + label7.Location = new System.Drawing.Point(573, 0); + label7.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label7.Name = "label7"; - label7.Size = new System.Drawing.Size(57, 20); + label7.Size = new System.Drawing.Size(38, 13); label7.TabIndex = 29; label7.Text = "10 mm"; // // label6 // - label6.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + label6.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); label6.AutoSize = true; - label6.Location = new System.Drawing.Point(63, 649); + label6.Location = new System.Drawing.Point(573, 442); + label6.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label6.Name = "label6"; - label6.Size = new System.Drawing.Size(48, 20); + label6.Size = new System.Drawing.Size(32, 13); label6.TabIndex = 28; label6.Text = "0 mm"; // // labelPresets // labelPresets.AutoSize = true; - labelPresets.Location = new System.Drawing.Point(150, 354); + labelPresets.Location = new System.Drawing.Point(12, 119); + labelPresets.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); labelPresets.Name = "labelPresets"; - labelPresets.Size = new System.Drawing.Size(126, 20); + labelPresets.Size = new System.Drawing.Size(46, 26); labelPresets.TabIndex = 23; - labelPresets.Text = "Channel Presets"; - // - // labelSelection - // - labelSelection.AutoSize = true; - labelSelection.Location = new System.Drawing.Point(187, 186); - labelSelection.Name = "labelSelection"; - labelSelection.Size = new System.Drawing.Size(75, 20); - labelSelection.TabIndex = 18; - labelSelection.Text = "Selection"; - // - // label1 - // - label1.AutoSize = true; - label1.Location = new System.Drawing.Point(13, 186); - label1.Name = "label1"; - label1.Size = new System.Drawing.Size(66, 20); - label1.TabIndex = 5; - label1.Text = "Jump to"; + labelPresets.Text = "Channel\r\nPresets"; // // toolStripLabelProbeNumber // + this.toolStripLabelProbeNumber.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); this.toolStripLabelProbeNumber.Name = "toolStripLabelProbeNumber"; - this.toolStripLabelProbeNumber.Size = new System.Drawing.Size(59, 25); + this.toolStripLabelProbeNumber.Size = new System.Drawing.Size(40, 17); this.toolStripLabelProbeNumber.Text = "Probe"; // // 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.Padding = new System.Windows.Forms.Padding(4, 1, 0, 1); + this.menuStrip.Size = new System.Drawing.Size(834, 24); this.menuStrip.TabIndex = 0; this.menuStrip.Text = "menuStripNeuropixelsV2e"; // // fileToolStripMenuItem // this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(54, 29); + this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 22); this.fileToolStripMenuItem.Text = "File"; // // statusStrip @@ -194,9 +184,10 @@ private void InitializeComponent() this.probeSn, toolStripStatusLabelGain, this.gain}); - this.statusStrip.Location = new System.Drawing.Point(0, 784); + this.statusStrip.Location = new System.Drawing.Point(0, 511); this.statusStrip.Name = "statusStrip"; - this.statusStrip.Size = new System.Drawing.Size(1265, 32); + this.statusStrip.Padding = new System.Windows.Forms.Padding(1, 0, 9, 0); + this.statusStrip.Size = new System.Drawing.Size(834, 22); this.statusStrip.TabIndex = 1; this.statusStrip.Text = "statusStrip1"; // @@ -204,20 +195,22 @@ private void InitializeComponent() // this.probeSn.AutoSize = false; this.probeSn.Name = "probeSn"; - this.probeSn.Size = new System.Drawing.Size(135, 25); + this.probeSn.Size = new System.Drawing.Size(120, 17); this.probeSn.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // gain // this.gain.AutoSize = false; this.gain.Name = "gain"; - this.gain.Size = new System.Drawing.Size(120, 25); + this.gain.Size = new System.Drawing.Size(120, 17); + this.gain.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // 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.Location = new System.Drawing.Point(0, 24); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(2); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -228,8 +221,9 @@ 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(834, 487); + this.splitContainer1.SplitterDistance = 455; + this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 2; // // splitContainer2 @@ -237,6 +231,7 @@ private void InitializeComponent() 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.Margin = new System.Windows.Forms.Padding(2); this.splitContainer2.Name = "splitContainer2"; // // splitContainer2.Panel1 @@ -246,18 +241,40 @@ private void InitializeComponent() // splitContainer2.Panel2 // this.splitContainer2.Panel2.Controls.Add(this.panelChannelOptions); - this.splitContainer2.Size = new System.Drawing.Size(1265, 699); - this.splitContainer2.SplitterDistance = 940; + this.splitContainer2.Size = new System.Drawing.Size(834, 455); + this.splitContainer2.SplitterDistance = 619; + this.splitContainer2.SplitterWidth = 3; this.splitContainer2.TabIndex = 1; // // panelProbe // + this.panelProbe.Controls.Add(label6); + this.panelProbe.Controls.Add(label7); + this.panelProbe.Controls.Add(this.trackBarProbePosition); this.panelProbe.Dock = System.Windows.Forms.DockStyle.Fill; this.panelProbe.Location = new System.Drawing.Point(0, 0); + this.panelProbe.Margin = new System.Windows.Forms.Padding(2); this.panelProbe.Name = "panelProbe"; - this.panelProbe.Size = new System.Drawing.Size(940, 699); + this.panelProbe.Size = new System.Drawing.Size(619, 455); this.panelProbe.TabIndex = 1; // + // trackBarProbePosition + // + this.trackBarProbePosition.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.trackBarProbePosition.AutoSize = false; + this.trackBarProbePosition.Location = new System.Drawing.Point(568, 9); + this.trackBarProbePosition.Margin = new System.Windows.Forms.Padding(2); + this.trackBarProbePosition.Maximum = 100; + this.trackBarProbePosition.Name = "trackBarProbePosition"; + this.trackBarProbePosition.Orientation = System.Windows.Forms.Orientation.Vertical; + this.trackBarProbePosition.Size = new System.Drawing.Size(37, 444); + 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); + // // panelChannelOptions // this.panelChannelOptions.BackColor = System.Drawing.SystemColors.ControlLightLight; @@ -267,27 +284,24 @@ private void InitializeComponent() this.panelChannelOptions.Controls.Add(probeCalibrationFile); this.panelChannelOptions.Controls.Add(this.comboBoxReference); this.panelChannelOptions.Controls.Add(Reference); - this.panelChannelOptions.Controls.Add(label7); - this.panelChannelOptions.Controls.Add(label6); this.panelChannelOptions.Controls.Add(this.comboBoxChannelPresets); 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(label1); this.panelChannelOptions.Controls.Add(this.buttonResetZoom); this.panelChannelOptions.Dock = System.Windows.Forms.DockStyle.Fill; this.panelChannelOptions.Location = new System.Drawing.Point(0, 0); + this.panelChannelOptions.Margin = new System.Windows.Forms.Padding(2); this.panelChannelOptions.Name = "panelChannelOptions"; - this.panelChannelOptions.Size = new System.Drawing.Size(321, 699); + this.panelChannelOptions.Size = new System.Drawing.Size(212, 455); this.panelChannelOptions.TabIndex = 1; // // buttonClearCalibrationFile // - this.buttonClearCalibrationFile.Location = new System.Drawing.Point(154, 69); + this.buttonClearCalibrationFile.Location = new System.Drawing.Point(103, 45); + this.buttonClearCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.buttonClearCalibrationFile.Name = "buttonClearCalibrationFile"; - this.buttonClearCalibrationFile.Size = new System.Drawing.Size(141, 32); + this.buttonClearCalibrationFile.Size = new System.Drawing.Size(94, 21); this.buttonClearCalibrationFile.TabIndex = 35; this.buttonClearCalibrationFile.Text = "Clear"; this.buttonClearCalibrationFile.UseVisualStyleBackColor = true; @@ -295,9 +309,10 @@ private void InitializeComponent() // // buttonChooseCalibrationFile // - this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(12, 69); + this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(8, 45); + this.buttonChooseCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.buttonChooseCalibrationFile.Name = "buttonChooseCalibrationFile"; - this.buttonChooseCalibrationFile.Size = new System.Drawing.Size(141, 32); + this.buttonChooseCalibrationFile.Size = new System.Drawing.Size(94, 21); this.buttonChooseCalibrationFile.TabIndex = 34; this.buttonChooseCalibrationFile.Text = "Choose"; this.buttonChooseCalibrationFile.UseVisualStyleBackColor = true; @@ -305,10 +320,11 @@ private void InitializeComponent() // // textBoxProbeCalibrationFile // - this.textBoxProbeCalibrationFile.Location = new System.Drawing.Point(12, 37); + this.textBoxProbeCalibrationFile.Location = new System.Drawing.Point(8, 24); + this.textBoxProbeCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.textBoxProbeCalibrationFile.Name = "textBoxProbeCalibrationFile"; this.textBoxProbeCalibrationFile.ReadOnly = true; - this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(283, 26); + this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(190, 20); this.textBoxProbeCalibrationFile.TabIndex = 33; this.textBoxProbeCalibrationFile.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; this.textBoxProbeCalibrationFile.TextChanged += new System.EventHandler(this.FileTextChanged); @@ -317,64 +333,58 @@ private void InitializeComponent() // this.comboBoxReference.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxReference.FormattingEnabled = true; - this.comboBoxReference.Location = new System.Drawing.Point(119, 131); + this.comboBoxReference.Location = new System.Drawing.Point(79, 85); + this.comboBoxReference.Margin = new System.Windows.Forms.Padding(2); this.comboBoxReference.Name = "comboBoxReference"; - this.comboBoxReference.Size = new System.Drawing.Size(176, 28); + this.comboBoxReference.Size = new System.Drawing.Size(119, 21); this.comboBoxReference.TabIndex = 31; // // comboBoxChannelPresets // this.comboBoxChannelPresets.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxChannelPresets.FormattingEnabled = true; - this.comboBoxChannelPresets.Location = new System.Drawing.Point(136, 377); + this.comboBoxChannelPresets.Location = new System.Drawing.Point(79, 124); + this.comboBoxChannelPresets.Margin = new System.Windows.Forms.Padding(2); this.comboBoxChannelPresets.Name = "comboBoxChannelPresets"; - this.comboBoxChannelPresets.Size = new System.Drawing.Size(162, 28); + this.comboBoxChannelPresets.Size = new System.Drawing.Size(118, 21); this.comboBoxChannelPresets.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.AutoSize = false; - this.trackBarProbePosition.Location = new System.Drawing.Point(17, 209); - this.trackBarProbePosition.Maximum = 100; - this.trackBarProbePosition.Name = "trackBarProbePosition"; - this.trackBarProbePosition.Orientation = System.Windows.Forms.Orientation.Vertical; - this.trackBarProbePosition.Size = new System.Drawing.Size(56, 469); - 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(136, 209); + this.buttonEnableContacts.Location = new System.Drawing.Point(8, 165); + this.buttonEnableContacts.Margin = new System.Windows.Forms.Padding(2); this.buttonEnableContacts.Name = "buttonEnableContacts"; - this.buttonEnableContacts.Size = new System.Drawing.Size(169, 56); + this.buttonEnableContacts.Size = new System.Drawing.Size(94, 36); this.buttonEnableContacts.TabIndex = 20; this.buttonEnableContacts.Text = "Enable Selected Contacts"; + this.toolTip.SetToolTip(this.buttonEnableContacts, "Click and drag to select contacts in the probe view. \r\nPress this button to enabl" + + "e the selected contacts."); this.buttonEnableContacts.UseVisualStyleBackColor = true; this.buttonEnableContacts.Click += new System.EventHandler(this.ButtonClick); // // buttonClearSelections // - this.buttonClearSelections.Location = new System.Drawing.Point(136, 271); + this.buttonClearSelections.Location = new System.Drawing.Point(106, 163); + this.buttonClearSelections.Margin = new System.Windows.Forms.Padding(2); this.buttonClearSelections.Name = "buttonClearSelections"; - this.buttonClearSelections.Size = new System.Drawing.Size(169, 59); + this.buttonClearSelections.Size = new System.Drawing.Size(94, 38); this.buttonClearSelections.TabIndex = 19; this.buttonClearSelections.Text = "Deselect Contacts"; + this.toolTip.SetToolTip(this.buttonClearSelections, "Remove selections from contacts in the probe view. Press this button to deselect " + + "contacts.\r\nNote that this does not disable contacts, but simply deselects them."); this.buttonClearSelections.UseVisualStyleBackColor = true; this.buttonClearSelections.Click += new System.EventHandler(this.ButtonClick); // // buttonResetZoom // - this.buttonResetZoom.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.buttonResetZoom.Location = new System.Drawing.Point(159, 635); + this.buttonResetZoom.Location = new System.Drawing.Point(57, 212); + this.buttonResetZoom.Margin = new System.Windows.Forms.Padding(2); this.buttonResetZoom.Name = "buttonResetZoom"; - this.buttonResetZoom.Size = new System.Drawing.Size(117, 34); + this.buttonResetZoom.Size = new System.Drawing.Size(94, 36); this.buttonResetZoom.TabIndex = 4; this.buttonResetZoom.Text = "Reset Zoom"; + this.toolTip.SetToolTip(this.buttonResetZoom, "Reset the zoom in the probe view so that the probe is zoomed out and centered.\r\nP" + + "ress this button to reset the zoom."); this.buttonResetZoom.UseVisualStyleBackColor = true; this.buttonResetZoom.Click += new System.EventHandler(this.ButtonClick); // @@ -384,16 +394,19 @@ private void InitializeComponent() 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.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(1265, 48); + this.panel1.Size = new System.Drawing.Size(834, 29); 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.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonCancel.Location = new System.Drawing.Point(744, 3); + this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; - this.buttonCancel.Size = new System.Drawing.Size(124, 34); + this.buttonCancel.Size = new System.Drawing.Size(83, 22); this.buttonCancel.TabIndex = 1; this.buttonCancel.Text = "Cancel"; this.buttonCancel.UseVisualStyleBackColor = true; @@ -402,9 +415,10 @@ private void InitializeComponent() // 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.Location = new System.Drawing.Point(655, 3); + this.buttonOkay.Margin = new System.Windows.Forms.Padding(2); this.buttonOkay.Name = "buttonOkay"; - this.buttonOkay.Size = new System.Drawing.Size(124, 34); + this.buttonOkay.Size = new System.Drawing.Size(83, 22); this.buttonOkay.TabIndex = 0; this.buttonOkay.Text = "OK"; this.buttonOkay.UseVisualStyleBackColor = true; @@ -415,9 +429,10 @@ private void InitializeComponent() this.linkLabelDocumentation.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.linkLabelDocumentation.AutoSize = true; this.linkLabelDocumentation.BackColor = System.Drawing.Color.GhostWhite; - this.linkLabelDocumentation.Location = new System.Drawing.Point(1125, 790); + this.linkLabelDocumentation.Location = new System.Drawing.Point(741, 517); + this.linkLabelDocumentation.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.linkLabelDocumentation.Name = "linkLabelDocumentation"; - this.linkLabelDocumentation.Size = new System.Drawing.Size(118, 20); + this.linkLabelDocumentation.Size = new System.Drawing.Size(79, 13); this.linkLabelDocumentation.TabIndex = 3; this.linkLabelDocumentation.TabStop = true; this.linkLabelDocumentation.Text = "Documentation"; @@ -425,15 +440,16 @@ private void InitializeComponent() // // NeuropixelsV2eProbeConfigurationDialog // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1265, 816); + this.ClientSize = new System.Drawing.Size(834, 533); 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.Margin = new System.Windows.Forms.Padding(2); this.Name = "NeuropixelsV2eProbeConfigurationDialog"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "NeuropixelsV2eProbeConfigurationDialog"; @@ -449,9 +465,11 @@ private void InitializeComponent() this.splitContainer2.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); this.splitContainer2.ResumeLayout(false); + this.panelProbe.ResumeLayout(false); + this.panelProbe.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).EndInit(); this.panelChannelOptions.ResumeLayout(false); this.panelChannelOptions.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).EndInit(); this.panel1.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); @@ -483,5 +501,6 @@ private void InitializeComponent() private System.Windows.Forms.Button buttonClearSelections; private System.Windows.Forms.Button buttonResetZoom; private System.Windows.Forms.ToolStripStatusLabel toolStripLabelProbeNumber; + private System.Windows.Forms.ToolTip toolTip; } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs index 658090e5..38ae1866 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -566,10 +566,6 @@ internal void ButtonClick(object sender, EventArgs e) { DialogResult = DialogResult.OK; } - else if (button.Name == nameof(buttonCancel)) - { - DialogResult = DialogResult.Cancel; - } else if (button.Name == nameof(buttonChooseCalibrationFile)) { var ofd = new OpenFileDialog() diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx index 7232902c..74e92246 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx @@ -138,16 +138,22 @@ False - - False - - - False - 17, 17 165, 17 + + 274, 17 + + + 274, 17 + + + 274, 17 + + + 274, 17 + \ No newline at end of file diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs index 7754a543..6bb8e944 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs @@ -124,8 +124,9 @@ public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe probe, Neurop public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2QuadShankProbeConfiguration probeConfiguration) { Reference = probeConfiguration.Reference; - ChannelMap = probeConfiguration.ChannelMap; - ChannelConfiguration = new(probeConfiguration.ChannelConfiguration); + ChannelConfiguration = new(); + ChannelConfiguration.UpdateDeviceChannelIndices(probeConfiguration.ChannelMap); + ChannelMap = NeuropixelsV2eProbeGroup.ToChannelMap(ChannelConfiguration); Probe = probeConfiguration.Probe; } @@ -140,8 +141,9 @@ public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2QuadShankProbeConfi [JsonConstructor] public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2eProbeGroup channelConfiguration, NeuropixelsV2QuadShankReference reference, NeuropixelsV2Probe probe) { - ChannelConfiguration = channelConfiguration; ChannelMap = NeuropixelsV2eProbeGroup.ToChannelMap(channelConfiguration); + ChannelConfiguration = new(); + ChannelConfiguration.UpdateDeviceChannelIndices(ChannelMap); Reference = reference; Probe = probe; } diff --git a/OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs b/OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs deleted file mode 100644 index 5f282702..00000000 --- a/OpenEphys.Onix1/NeuropixelsV2RegisterContext.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 44947e5b3e259323a7e616133f35d93fb07d4f4b Mon Sep 17 00:00:00 2001 From: jonnew Date: Wed, 14 Aug 2024 17:41:34 -0400 Subject: [PATCH 16/35] Improve description of neuropixels calibration files - More explaination - More emphasis on their importance - Path to getting a new one if yours is lost --- OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs | 14 ++++++++++---- OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs | 14 ++++++++++---- OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs | 14 ++++++++++---- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs index 578bb8d7..f3d9d9e7 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs @@ -90,8 +90,11 @@ public ConfigureNeuropixelsV1e() /// 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. + /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared + /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. + /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be + /// with the probe serial number to retrieve a new copy. /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the Neuropixels 1.0 gain calibration file.")] @@ -102,8 +105,11 @@ public ConfigureNeuropixelsV1e() /// 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. + /// Each probe must be provided with an ADC calibration file that contains probe-specific hardware settings that is + /// created by IMEC during factory calibration. These files are used to set internal bias currents, correct for ADC + /// nonlinearities, correct ADC-zero crossing non-monotonicities, etc. Using the correct calibration file is mandatory + /// for the probe to operate correctly. If you have lost track of the ADC calibration file for your probe, you can + /// email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. /// [FileNameFilter("ADC calibration files (*_ADCCalibration.csv)|*_ADCCalibration.csv")] [Description("Path to the Neuropixels 1.0 ADC calibration file.")] diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index b8893b62..7e25a6c7 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -41,8 +41,11 @@ public ConfigureNeuropixelsV2e() /// 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. + /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared + /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. + /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be + /// with the probe serial number to retrieve a new copy. /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe A.")] @@ -60,8 +63,11 @@ public ConfigureNeuropixelsV2e() /// 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. + /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared + /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. + /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be + /// with the probe serial number to retrieve a new copy. /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe B.")] diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index ce537096..588137c4 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -51,8 +51,11 @@ public ConfigureNeuropixelsV2eBeta() /// 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. + /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared + /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. + /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be + /// with the probe serial number to retrieve a new copy. /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe A.")] @@ -70,8 +73,11 @@ public ConfigureNeuropixelsV2eBeta() /// 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. + /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared + /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. + /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be + /// with the probe serial number to retrieve a new copy. /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe B.")] From 35b02e47b9e24dddad57ac7f93371531c3e4a3b0 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Tue, 13 Aug 2024 17:35:08 -0400 Subject: [PATCH 17/35] Clean up submodule and add remarks - Add remarks for ProbeConfiguration variables - Add Category attributes to Neuropixels properties - Remove submodule and reference OpenEphys.ProbeInterface directly from NuGet - Remove AnyCPU options --- .gitmodules | 3 --- .../ChannelConfigurationDialog.cs | 4 ++-- ...europixelsV2eChannelConfigurationDialog.cs | 6 +++++- .../OpenEphys.Onix1.Design.csproj | 4 ++-- OpenEphys.Onix1.sln | 20 ------------------- OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs | 14 +++++++++++++ .../ConfigureNeuropixelsV2eBeta.cs | 2 ++ OpenEphys.Onix1/NeuropixelsV2.cs | 18 ++++++++--------- OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs | 2 +- OpenEphys.Onix1/OpenEphys.Onix1.csproj | 5 +---- OpenEphys.ProbeInterface | 1 - 11 files changed, 36 insertions(+), 43 deletions(-) delete mode 160000 OpenEphys.ProbeInterface diff --git a/.gitmodules b/.gitmodules index eed9e0fa..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "OpenEphys.ProbeInterface"] - path = OpenEphys.ProbeInterface - url = https://github.com/open-ephys/OpenEphys.ProbeInterface diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index 89fb002a..412cd26b 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -4,7 +4,7 @@ using System.Windows.Forms; using ZedGraph; using System; -using OpenEphys.ProbeInterface; +using OpenEphys.ProbeInterface.NET; using System.Collections.Generic; namespace OpenEphys.Onix1.Design @@ -19,7 +19,7 @@ public abstract partial class ChannelConfigurationDialog : Form /// /// Local variable that holds the channel configuration in memory until the user presses Okay /// - ProbeGroup ChannelConfiguration; + internal ProbeGroup ChannelConfiguration; internal readonly List ReferenceContacts = new(); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 4f845014..f69189ca 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -3,7 +3,7 @@ using System.Drawing; using System.Linq; using System.Windows.Forms; -using OpenEphys.ProbeInterface; +using OpenEphys.ProbeInterface.NET; using ZedGraph; namespace OpenEphys.Onix1.Design @@ -57,6 +57,10 @@ internal override ProbeGroup DefaultChannelLayout() internal override void LoadDefaultChannelLayout() { ProbeConfiguration = new(ProbeConfiguration.Probe, ProbeConfiguration.Reference); + ChannelConfiguration = ProbeConfiguration.ChannelConfiguration; + + DrawProbeGroup(); + RefreshZedGraph(); OnFileOpenHandler(); } diff --git a/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj index 82c3b81b..ae3173ed 100644 --- a/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj +++ b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj @@ -11,14 +11,14 @@ - + + - diff --git a/OpenEphys.Onix1.sln b/OpenEphys.Onix1.sln index e82153ce..77983339 100644 --- a/OpenEphys.Onix1.sln +++ b/OpenEphys.Onix1.sln @@ -12,40 +12,20 @@ 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 diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index 1f5a3368..62565276 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -51,6 +51,12 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) /// /// Gets or sets the electrode configuration for Probe A. /// + /// + /// Configuration is accomplished using a GUI to aid in channel selection and relevant configuration properties. + /// To open a probe configuration GUI, select the ellipses next the variable + /// in the property pane, or double-click to configure both + /// probes and the simultaneously. + /// [Category(ConfigurationCategory)] [Description("Probe A electrode configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] @@ -63,6 +69,7 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) /// 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. /// + [Category(ConfigurationCategory)] [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)] @@ -71,6 +78,12 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) /// /// Gets or sets the electrode configuration for Probe B. /// + /// + /// Configuration is accomplished using a GUI to aid in channel selection and relevant configuration properties. + /// To open a probe configuration GUI, select the ellipses next the variable + /// in the property pane, or double-click to configure both + /// probes and the simultaneously. + /// [Category(ConfigurationCategory)] [Description("Probe B electrode configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] @@ -83,6 +96,7 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) /// 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. /// + [Category(ConfigurationCategory)] [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)] diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index abac067f..c16e9fe5 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -54,6 +54,7 @@ public ConfigureNeuropixelsV2eBeta() /// 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. /// + [Category(ConfigurationCategory)] [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)] @@ -73,6 +74,7 @@ public ConfigureNeuropixelsV2eBeta() /// 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. /// + [Category(ConfigurationCategory)] [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)] diff --git a/OpenEphys.Onix1/NeuropixelsV2.cs b/OpenEphys.Onix1/NeuropixelsV2.cs index ef175626..ccf7aec9 100644 --- a/OpenEphys.Onix1/NeuropixelsV2.cs +++ b/OpenEphys.Onix1/NeuropixelsV2.cs @@ -39,10 +39,10 @@ internal static BitArray[] GenerateShankBits(NeuropixelsV2QuadShankProbeConfigur { BitArray[] shankBits = { - new(NeuropixelsV2.RegistersPerShank, false), - new(NeuropixelsV2.RegistersPerShank, false), - new(NeuropixelsV2.RegistersPerShank, false), - new(NeuropixelsV2.RegistersPerShank, false) + new(RegistersPerShank, false), + new(RegistersPerShank, false), + new(RegistersPerShank, false), + new(RegistersPerShank, false) }; @@ -69,7 +69,7 @@ internal static BitArray[] GenerateShankBits(NeuropixelsV2QuadShankProbeConfigur shankBits[3][1285] = true; } - const int PixelOffset = (NeuropixelsV2.ElectrodePerShank - 1) / 2; + const int PixelOffset = (ElectrodePerShank - 1) / 2; const int ReferencePixelOffset = 3; foreach (var c in probe.ChannelMap) { @@ -89,8 +89,8 @@ internal static BitArray[] GenerateBaseBits(NeuropixelsV2QuadShankProbeConfigura { BitArray[] baseBits = { - new(NeuropixelsV2.ChannelCount * NeuropixelsV2.BaseBitsPerChannel / 2, false), - new(NeuropixelsV2.ChannelCount * NeuropixelsV2.BaseBitsPerChannel / 2, false) + new(ChannelCount * BaseBitsPerChannel / 2, false), + new(ChannelCount * BaseBitsPerChannel / 2, false) }; var referenceBit = probe.Reference switch @@ -103,10 +103,10 @@ internal static BitArray[] GenerateBaseBits(NeuropixelsV2QuadShankProbeConfigura _ => throw new InvalidOperationException("Invalid reference selection."), }; - for (int i = 0; i < NeuropixelsV2.ChannelCount; i++) + for (int i = 0; i < ChannelCount; i++) { var configIndex = i % 2; - var bitOffset = (382 - i + configIndex) / 2 * NeuropixelsV2.BaseBitsPerChannel; + var bitOffset = (382 - i + configIndex) / 2 * BaseBitsPerChannel; baseBits[configIndex][bitOffset + 0] = false; // standby bit baseBits[configIndex][bitOffset + referenceBit] = true; } diff --git a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs index 026a666d..150cc4b2 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; -using OpenEphys.ProbeInterface; +using OpenEphys.ProbeInterface.NET; namespace OpenEphys.Onix1 { diff --git a/OpenEphys.Onix1/OpenEphys.Onix1.csproj b/OpenEphys.Onix1/OpenEphys.Onix1.csproj index 6a5a0e95..06743351 100644 --- a/OpenEphys.Onix1/OpenEphys.Onix1.csproj +++ b/OpenEphys.Onix1/OpenEphys.Onix1.csproj @@ -13,10 +13,7 @@ - - - - + diff --git a/OpenEphys.ProbeInterface b/OpenEphys.ProbeInterface deleted file mode 160000 index 524b4c1e..00000000 --- a/OpenEphys.ProbeInterface +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 524b4c1e03aa3acbeaecb3964810c9c3a84dfc50 From ee68e23c19e2195052c5423cc257fca00d2f2210 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Wed, 14 Aug 2024 18:04:08 -0400 Subject: [PATCH 18/35] Remove unused resources --- .../Properties/Resources.Designer.cs | 60 ------------------ .../Properties/Resources.resx | 19 ------ .../Resources/StatusBlockedImage.png | Bin 268 -> 0 bytes .../Resources/StatusCriticalImage.png | Bin 306 -> 0 bytes .../Resources/StatusReadyImage.png | Bin 309 -> 0 bytes .../Resources/StatusRefreshImage.png | Bin 13423 -> 0 bytes .../Resources/StatusWarningImage.png | Bin 1010 -> 0 bytes .../Resources/UploadImage.png | Bin 7224 -> 0 bytes 8 files changed, 79 deletions(-) delete mode 100644 OpenEphys.Onix1.Design/Resources/StatusBlockedImage.png delete mode 100644 OpenEphys.Onix1.Design/Resources/StatusCriticalImage.png delete mode 100644 OpenEphys.Onix1.Design/Resources/StatusReadyImage.png delete mode 100644 OpenEphys.Onix1.Design/Resources/StatusRefreshImage.png delete mode 100644 OpenEphys.Onix1.Design/Resources/StatusWarningImage.png delete mode 100644 OpenEphys.Onix1.Design/Resources/UploadImage.png diff --git a/OpenEphys.Onix1.Design/Properties/Resources.Designer.cs b/OpenEphys.Onix1.Design/Properties/Resources.Designer.cs index e45b694d..26223761 100644 --- a/OpenEphys.Onix1.Design/Properties/Resources.Designer.cs +++ b/OpenEphys.Onix1.Design/Properties/Resources.Designer.cs @@ -59,65 +59,5 @@ internal Resources() { 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.Onix1.Design/Properties/Resources.resx b/OpenEphys.Onix1.Design/Properties/Resources.resx index 59d3809d..1af7de15 100644 --- a/OpenEphys.Onix1.Design/Properties/Resources.resx +++ b/OpenEphys.Onix1.Design/Properties/Resources.resx @@ -117,23 +117,4 @@ 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.Onix1.Design/Resources/StatusBlockedImage.png b/OpenEphys.Onix1.Design/Resources/StatusBlockedImage.png deleted file mode 100644 index 3e35e47ec39de3a1a290571722b841580dc83564..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/OpenEphys.Onix1.Design/Resources/StatusCriticalImage.png b/OpenEphys.Onix1.Design/Resources/StatusCriticalImage.png deleted file mode 100644 index 4762e0feb7e5d09d680c8afff79f6a21df83ea8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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| diff --git a/OpenEphys.Onix1.Design/Resources/StatusReadyImage.png b/OpenEphys.Onix1.Design/Resources/StatusReadyImage.png deleted file mode 100644 index cbe7c2eb5064543f9eb9f2ee746779d4923a85ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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( diff --git a/OpenEphys.Onix1.Design/Resources/StatusRefreshImage.png b/OpenEphys.Onix1.Design/Resources/StatusRefreshImage.png deleted file mode 100644 index 6729e542dba81fe3a757ac54eea5006cd3f24364..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13423 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 Date: Thu, 15 Aug 2024 09:51:08 -0400 Subject: [PATCH 19/35] Expose headstage port status data stream - Fixes #156 - Rename *FmcLink* to *Port* - Improve error string when SERDES lock cannot be established - Move PortName enum into ConfigurePortController.cs --- OpenEphys.Onix1/ConfigureHeadstage64.cs | 33 ++----- .../ConfigureNeuropixelsV1eHeadstage.cs | 10 +-- .../ConfigureNeuropixelsV2eBetaHeadstage.cs | 4 +- .../ConfigureNeuropixelsV2eHeadstage.cs | 4 +- ... ConfigureNeuropixelsV2ePortController.cs} | 6 +- ...ntroller.cs => ConfigurePortController.cs} | 85 ++++++++++++++++--- OpenEphys.Onix1/PortStatus.cs | 46 ++++++++++ OpenEphys.Onix1/PortStatusFrame.cs | 48 +++++++++++ 8 files changed, 188 insertions(+), 48 deletions(-) rename OpenEphys.Onix1/{ConfigureNeuropixelsV2eLinkController.cs => ConfigureNeuropixelsV2ePortController.cs} (79%) rename OpenEphys.Onix1/{ConfigureFmcLinkController.cs => ConfigurePortController.cs} (55%) create mode 100644 OpenEphys.Onix1/PortStatus.cs create mode 100644 OpenEphys.Onix1/PortStatusFrame.cs diff --git a/OpenEphys.Onix1/ConfigureHeadstage64.cs b/OpenEphys.Onix1/ConfigureHeadstage64.cs index 9e7fd150..161ed2e3 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64.cs @@ -5,13 +5,13 @@ namespace OpenEphys.Onix1 { /// - /// A class that configures an ONIX headstage-64 in the specified port. + /// A class that configures an ONIX headstage-64 on the specified port. /// [Description("Configures an ONIX headstage-64 in the specified port.")] public class ConfigureHeadstage64 : MultiDeviceFactory { PortName port; - readonly ConfigureHeadstage64LinkController LinkController = new(); + readonly ConfigureHeadstage64PortController LinkController = new(); /// /// Initializes a new instance of the class. @@ -136,7 +136,7 @@ internal override IEnumerable GetDevices() yield return OpticalStimulator; } - class ConfigureHeadstage64LinkController : ConfigureFmcLinkController + class ConfigureHeadstage64PortController : ConfigurePortController { protected override bool ConfigurePortVoltage(DeviceContext device) { @@ -153,7 +153,7 @@ protected override bool ConfigurePortVoltage(DeviceContext device) var voltage = MaxVoltage; for (; voltage >= MinVoltage; voltage -= VoltageIncrement) { - device.WriteRegister(FmcLinkController.PORTVOLTAGE, voltage); + device.WriteRegister(PortController.PORTVOLTAGE, voltage); Thread.Sleep(200); if (!CheckLinkState(device)) { @@ -162,32 +162,15 @@ protected override bool ConfigurePortVoltage(DeviceContext device) } } - device.WriteRegister(FmcLinkController.PORTVOLTAGE, MinVoltage); - device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0); + device.WriteRegister(PortController.PORTVOLTAGE, MinVoltage); + device.WriteRegister(PortController.PORTVOLTAGE, 0); Thread.Sleep(1000); - device.WriteRegister(FmcLinkController.PORTVOLTAGE, voltage + VoltageOffset); + device.WriteRegister(PortController.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.Onix1/ConfigureNeuropixelsV1eHeadstage.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs index df88662e..69585a67 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs @@ -5,13 +5,13 @@ namespace OpenEphys.Onix1 { /// - /// A class that configures a NeuropixelsV1e headstage. + /// A class that configures a NeuropixelsV1e headstage on the specified port. /// [Description("Configures a NeuropixelsV1e headstage.")] public class ConfigureNeuropixelsV1eHeadstage : MultiDeviceFactory { PortName port; - readonly ConfigureNeuropixelsV1eLinkController LinkController = new(); + readonly ConfigureNeuropixelsV1ePortController LinkController = new(); /// /// Initialize a new instance of a class. @@ -83,7 +83,7 @@ internal override IEnumerable GetDevices() yield return Bno055; } - class ConfigureNeuropixelsV1eLinkController : ConfigureFmcLinkController + class ConfigureNeuropixelsV1ePortController : ConfigurePortController { protected override bool ConfigurePortVoltage(DeviceContext device) { @@ -108,9 +108,9 @@ protected override bool ConfigurePortVoltage(DeviceContext device) void SetVoltage(DeviceContext device, double voltage) { - device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0); + device.WriteRegister(PortController.PORTVOLTAGE, 0); Thread.Sleep(200); - device.WriteRegister(FmcLinkController.PORTVOLTAGE, (uint)(10 * voltage)); + device.WriteRegister(PortController.PORTVOLTAGE, (uint)(10 * voltage)); Thread.Sleep(200); } } diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs index 9c3c3b22..87137174 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs @@ -4,13 +4,13 @@ namespace OpenEphys.Onix1 { /// - /// A class that configures a NeuropixelsV2eBeta headstage. + /// A class that configures a NeuropixelsV2eBeta headstage on the specified port. /// [Description("Configures a NeuropixelsV2eBeta headstage.")] public class ConfigureNeuropixelsV2eBetaHeadstage : MultiDeviceFactory { PortName port; - readonly ConfigureNeuropixelsV2eLinkController LinkController = new(); + readonly ConfigureNeuropixelsV2ePortController LinkController = new(); /// /// Initialize a new instance of a class. diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs index d5a04d59..9c00b137 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs @@ -4,13 +4,13 @@ namespace OpenEphys.Onix1 { /// - /// A class that configures a NeuropixelsV2e headstage. + /// A class that configures a NeuropixelsV2e headstage on the specified port. /// [Description("configures a NeuropixelsV2e headstage.")] public class ConfigureNeuropixelsV2eHeadstage : MultiDeviceFactory { PortName port; - readonly ConfigureNeuropixelsV2eLinkController LinkController = new(); + readonly ConfigureNeuropixelsV2ePortController LinkController = new(); /// /// Initialize a new instance of a class. diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eLinkController.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2ePortController.cs similarity index 79% rename from OpenEphys.Onix1/ConfigureNeuropixelsV2eLinkController.cs rename to OpenEphys.Onix1/ConfigureNeuropixelsV2ePortController.cs index 86cce964..b1aff536 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eLinkController.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2ePortController.cs @@ -2,7 +2,7 @@ namespace OpenEphys.Onix1 { - class ConfigureNeuropixelsV2eLinkController : ConfigureFmcLinkController + class ConfigureNeuropixelsV2ePortController : ConfigurePortController { protected override bool ConfigurePortVoltage(DeviceContext device) @@ -29,9 +29,9 @@ protected override bool ConfigurePortVoltage(DeviceContext device) void SetVoltage(DeviceContext device, double voltage) { - device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0); + device.WriteRegister(PortController.PORTVOLTAGE, 0); Thread.Sleep(200); - device.WriteRegister(FmcLinkController.PORTVOLTAGE, (uint)(10 * voltage)); + device.WriteRegister(PortController.PORTVOLTAGE, (uint)(10 * voltage)); Thread.Sleep(200); } } diff --git a/OpenEphys.Onix1/ConfigureFmcLinkController.cs b/OpenEphys.Onix1/ConfigurePortController.cs similarity index 55% rename from OpenEphys.Onix1/ConfigureFmcLinkController.cs rename to OpenEphys.Onix1/ConfigurePortController.cs index 818c9e7f..dda3acaf 100644 --- a/OpenEphys.Onix1/ConfigureFmcLinkController.cs +++ b/OpenEphys.Onix1/ConfigurePortController.cs @@ -1,14 +1,15 @@ using System; using System.ComponentModel; using System.Reactive.Disposables; +using System.Reflection; using System.Threading; namespace OpenEphys.Onix1 { - internal abstract class ConfigureFmcLinkController : SingleDeviceFactory + internal abstract class ConfigurePortController : SingleDeviceFactory { - public ConfigureFmcLinkController() - : base(typeof(FmcLinkController)) + public ConfigurePortController() + : base(typeof(PortController)) { } @@ -23,15 +24,15 @@ public ConfigureFmcLinkController() protected virtual bool CheckLinkState(DeviceContext device) { - var linkState = device.ReadRegister(FmcLinkController.LINKSTATE); - return (linkState & FmcLinkController.LINKSTATE_SL) != 0; + var linkState = device.ReadRegister(PortController.LINKSTATE); + return (linkState & PortController.LINKSTATE_SL) != 0; } protected abstract bool ConfigurePortVoltage(DeviceContext device); private bool ConfigurePortVoltageOverride(DeviceContext device, double voltage) { - device.WriteRegister(FmcLinkController.PORTVOLTAGE, (uint)(voltage * 10)); + device.WriteRegister(PortController.PORTVOLTAGE, (uint)(voltage * 10)); Thread.Sleep(500); return CheckLinkState(device); } @@ -44,7 +45,7 @@ public override IObservable Process(IObservable source var portVoltage = PortVoltage; return source.ConfigureHost(context => { - // configure passthrough mode on the FMC link controller + // configure passthrough mode on the port controller // assuming the device address is the port number var portShift = ((int)deviceAddress - 1) * 2; var passthroughState = (hubConfiguration == HubConfiguration.Passthrough ? 1 : 0) << portShift; @@ -54,8 +55,8 @@ public override IObservable Process(IObservable source .ConfigureLink(context => { var device = context.GetDeviceContext(deviceAddress, DeviceType); - void dispose() => device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0); - device.WriteRegister(FmcLinkController.ENABLE, 1); + void dispose() => device.WriteRegister(PortController.PORTVOLTAGE, 0); + device.WriteRegister(PortController.ENABLE, 1); var serdesLock = portVoltage.HasValue ? ConfigurePortVoltageOverride(device, portVoltage.GetValueOrDefault()) @@ -63,7 +64,12 @@ public override IObservable Process(IObservable source if (!serdesLock) { dispose(); - throw new InvalidOperationException("Unable to get SERDES lock on FMC link controller."); + var port = (PortName)deviceAddress; + var portString = port.GetType() + .GetField(port.ToString())? + .GetCustomAttribute()? + .Description ?? "Address " + deviceAddress.ToString(); + throw new InvalidOperationException($"Unable to acquire communication lock on {portString}."); } return Disposable.Create(dispose); }) @@ -75,7 +81,7 @@ public override IObservable Process(IObservable source } } - internal static class FmcLinkController + internal static class PortController { public const int ID = 23; @@ -88,6 +94,14 @@ internal static class FmcLinkController public const uint LINKSTATE_PP = 0x2; // parity check pass bit public const uint LINKSTATE_SL = 0x1; // SERDES lock bit + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(PortController)) + { + } + } } internal enum HubConfiguration @@ -95,4 +109,53 @@ internal enum HubConfiguration Standard, Passthrough } + + /// + /// Specifies the headstage port status codes. + /// + [Flags] + public enum PortStatusCode : byte + { + /// + /// Specifies that the status code should be disregarded. + /// + Invalid = 0x0, + /// + /// Specifies a cyclic redundancy check failure. + /// + CrcError = 0x1, + /// + /// Specifies that too many devices were indicated in the hub device table. + /// + TooManyDevices = 0x2, + /// + /// Specifies a hub initialization error. + /// + InitializationError = 0x4, + /// + /// Specifies the receipt of a badly formatted data packet. + /// + BadPacketFormat = 0x8, + } + + /// + /// 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. + /// + [Description("Port A")] + PortA = 1, + /// + /// Specifies Port B. + /// + [Description("Port B")] + PortB = 2 + } } diff --git a/OpenEphys.Onix1/PortStatus.cs b/OpenEphys.Onix1/PortStatus.cs new file mode 100644 index 00000000..88514db6 --- /dev/null +++ b/OpenEphys.Onix1/PortStatus.cs @@ -0,0 +1,46 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that produces a sequence of port status information frames. + /// + /// + /// This data stream class must be linked to an appropriate headstage, + /// miniscope, etc. configuration whose communication is dictated by + /// a . + /// + [Description("Produces a sequence of port status information.")] + public class PortStatus : Source + { + /// + [TypeConverter(typeof(PortController.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence of objects, which contains information + /// about the the a headstage port communication status. + /// + /// + /// A will be produced only in exceptional circumstances. For + /// instance, when the headstage becomes disconnected, a packet fails a CRC check, etc. + /// + /// A sequence of objects. + public override IObservable Generate() + { + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(PortController)); + + return deviceInfo.Context + .GetDeviceFrames(device.Address) + .Select(frame => new PortStatusFrame(frame)); + }); + } + } +} diff --git a/OpenEphys.Onix1/PortStatusFrame.cs b/OpenEphys.Onix1/PortStatusFrame.cs new file mode 100644 index 00000000..340f8402 --- /dev/null +++ b/OpenEphys.Onix1/PortStatusFrame.cs @@ -0,0 +1,48 @@ +using System.Runtime.InteropServices; + +namespace OpenEphys.Onix1 +{ + /// + /// A class that contains port status information. + /// + public class PortStatusFrame : DataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// A data frame produced by a port controller device. + public unsafe PortStatusFrame(oni.Frame frame) + : base(frame.Clock) + { + var payload = (PortStatusPayload*)frame.Data.ToPointer(); + HubClock = payload->HubClock; + var statusCodeValid = (payload->SerdesStatus & 0x0004) == 4; + StatusCode = statusCodeValid ? payload->Code : PortStatusCode.Invalid; + SerdesLocked = (payload->SerdesStatus & 0x0001) == 1; + SerdesPass = (payload->SerdesStatus & 0x0002) == 2; + } + + /// + /// Gets the port status code. + /// + public PortStatusCode StatusCode { get; } + + /// + /// Gets the SERDES forward channel lock status. + /// + public bool SerdesLocked { get; } + + /// + /// Gets the SERDES on-chip parity check status. + /// + public bool SerdesPass { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct PortStatusPayload + { + public ulong HubClock; + public PortStatusCode Code; + public byte SerdesStatus; + } +} From 3305365de57befadca972ccc93b981e27bb195c2 Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 15 Aug 2024 10:29:58 -0400 Subject: [PATCH 20/35] Propogate boolean in Headstage64 stimulator triggers - These operators were not acting as sinks because they were not propogating their observable sequence - Fixes #83 --- OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs | 6 +++++- OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs b/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs index e0cd6894..879f6d24 100644 --- a/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs +++ b/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs @@ -208,7 +208,11 @@ public override IObservable Process(IObservable source) { var device = deviceInfo.GetDeviceContext(typeof(Headstage64ElectricalStimulator)); var triggerObserver = Observer.Create( - value => device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, value ? 1u : 0u), + value => + { + device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, value ? 1u : 0u); + observer.OnNext(value); + }, observer.OnError, observer.OnCompleted); diff --git a/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs b/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs index 34f6f060..88a796ee 100644 --- a/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs +++ b/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs @@ -202,7 +202,11 @@ public override IObservable Process(IObservable source) { var device = deviceInfo.GetDeviceContext(typeof(Headstage64OpticalStimulator)); var triggerObserver = Observer.Create( - value => device.WriteRegister(Headstage64OpticalStimulator.TRIGGER, value ? 1u : 0u), + value => + { + device.WriteRegister(Headstage64OpticalStimulator.TRIGGER, value ? 1u : 0u); + observer.OnNext(value); + }, observer.OnError, observer.OnCompleted); From 499b8151bfc92335d4a0c12c36c522e80b6443ee Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 15 Aug 2024 11:43:01 -0400 Subject: [PATCH 21/35] Implement ConfigureNeuropixelV1/2eBno055.Enable - Fixes #191 - Previously ConfigureNeuropixelV1/2eBno055.Enable was not doing anything - Now enable state is passed through a custom DeviceInfo object to the NeuropixelV1/2eBno055Data operator. If its false, the operator returns an Observable.Empty. --- .../ConfigureNeuropixelsV1eBno055.cs | 13 ++++- .../ConfigureNeuropixelsV2eBno055.cs | 13 ++++- OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs | 43 +++++++++------- OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs | 49 ++++++++++--------- 4 files changed, 75 insertions(+), 43 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV1eBno055.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1eBno055.cs index b5cbb9e9..47b53239 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV1eBno055.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1eBno055.cs @@ -51,7 +51,7 @@ public override IObservable Process(IObservable source var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); ConfigureDeserializer(device); ConfigureBno055(device); - var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); + var deviceInfo = new NeuropixelsV1eBno055DeviceInfo(context, DeviceType, deviceAddress, enable); return DeviceManager.RegisterDevice(deviceName, deviceInfo); }); } @@ -91,4 +91,15 @@ public NameConverter() } } } + + class NeuropixelsV1eBno055DeviceInfo : DeviceInfo + { + public NeuropixelsV1eBno055DeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, bool enable) + : base(context, deviceType, deviceAddress) + { + Enable = enable; + } + + public bool Enable { get; } + } } diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs index 8a678573..91ca06b5 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBno055.cs @@ -51,7 +51,7 @@ public override IObservable Process(IObservable source var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); ConfigureDeserializer(device); ConfigureBno055(device); - var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); + var deviceInfo = new NeuropixelsV2eBno055DeviceInfo(context, DeviceType, deviceAddress, enable); return DeviceManager.RegisterDevice(deviceName, deviceInfo); }); } @@ -91,4 +91,15 @@ public NameConverter() } } } + + class NeuropixelsV2eBno055DeviceInfo : DeviceInfo + { + public NeuropixelsV2eBno055DeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, bool enable) + : base(context, deviceType, deviceAddress) + { + Enable = enable; + } + + public bool Enable { get; } + } } diff --git a/OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs b/OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs index 5f9b3917..e6f66bc1 100644 --- a/OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs +++ b/OpenEphys.Onix1/NeuropixelsV1eBno055Data.cs @@ -39,32 +39,37 @@ public override IObservable Generate() public unsafe IObservable Generate(IObservable source) { return DeviceManager.GetDevice(DeviceName).SelectMany( - deviceInfo => Observable.Create(observer => + deviceInfo => { - var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV1eBno055)); - var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); - var i2c = new I2CRegisterContext(passthrough, NeuropixelsV1eBno055.BNO055Address); - - return source.SubscribeSafe(observer, _ => + return !((NeuropixelsV1eBno055DeviceInfo)deviceInfo).Enable + ? Observable.Empty() + : Observable.Create(observer => { - Bno055DataFrame frame = default; - device.Context.EnsureContext(() => + var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV1eBno055)); + var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); + var i2c = new I2CRegisterContext(passthrough, NeuropixelsV1eBno055.BNO055Address); + + return source.SubscribeSafe(observer, _ => { - 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) + 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) { - frame = new Bno055DataFrame(clock, (Bno055DataPayload*)dataPtr); + observer.OnNext(frame); } }); - - if (frame != null) - { - observer.OnNext(frame); - } }); - })); + }); } } } diff --git a/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs b/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs index 9028faf1..a376a53a 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs @@ -39,32 +39,37 @@ public override IObservable Generate() public unsafe IObservable Generate(IObservable source) { return DeviceManager.GetDevice(DeviceName).SelectMany( - deviceInfo => Observable.Create(observer => + deviceInfo => { - 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(() => + return !((NeuropixelsV2eBno055DeviceInfo)deviceInfo).Enable + ? Observable.Empty() + : Observable.Create(observer => { - 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) + var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2eBno055)); + var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); + var i2c = new I2CRegisterContext(passthrough, NeuropixelsV2eBno055.BNO055Address); + + return source.SubscribeSafe(observer, _ => { - frame = new Bno055DataFrame(clock, (Bno055DataPayload*)dataPtr); - } - }); + 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); - } - }); - })); + if (frame != null) + { + observer.OnNext(frame); + } + }); + }); + }); } } } From ab30047a2c535242d4b06c99f78d809dad8ef643 Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 15 Aug 2024 14:37:50 -0400 Subject: [PATCH 22/35] Address code review comments - Improve clarity of neuropixesl calibration file descriptions --- OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs | 21 ++++++++++++++----- OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs | 11 +++++++--- .../ConfigureNeuropixelsV2eBeta.cs | 11 +++++++--- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs index f3d9d9e7..81e0c550 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs @@ -90,11 +90,16 @@ public ConfigureNeuropixelsV1e() /// Gets or sets the path to the gain calibration file. /// /// - /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// + /// Each probe is linked to a gain calibration file that contains gain adjustments determined by IMEC during /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. - /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be - /// with the probe serial number to retrieve a new copy. + /// + /// + /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the + /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration + /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. + /// /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the Neuropixels 1.0 gain calibration file.")] @@ -105,11 +110,17 @@ public ConfigureNeuropixelsV1e() /// Gets or sets the path to the ADC calibration file. /// /// + /// /// Each probe must be provided with an ADC calibration file that contains probe-specific hardware settings that is /// created by IMEC during factory calibration. These files are used to set internal bias currents, correct for ADC /// nonlinearities, correct ADC-zero crossing non-monotonicities, etc. Using the correct calibration file is mandatory - /// for the probe to operate correctly. If you have lost track of the ADC calibration file for your probe, you can - /// email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. + /// for the probe to operate correctly. + /// + /// + /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the + /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration + /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. + /// /// [FileNameFilter("ADC calibration files (*_ADCCalibration.csv)|*_ADCCalibration.csv")] [Description("Path to the Neuropixels 1.0 ADC calibration file.")] diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index 7e25a6c7..5740e463 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -63,11 +63,16 @@ public ConfigureNeuropixelsV2e() /// Gets or sets the path to the gain calibration file for Probe B. /// /// - /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// + /// Each probe is linked to a gain calibration file that contains gain adjustments determined by IMEC during /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. - /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be - /// with the probe serial number to retrieve a new copy. + /// + /// + /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the + /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration + /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. + /// /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe B.")] diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index 588137c4..f9462a5d 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -51,11 +51,16 @@ public ConfigureNeuropixelsV2eBeta() /// Gets or sets the path to the gain calibration file for Probe A. /// /// - /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// + /// Each probe is linked to a gain calibration file that contains gain adjustments determined by IMEC during /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. - /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be - /// with the probe serial number to retrieve a new copy. + /// + /// + /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the + /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration + /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. + /// /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe A.")] From 71d96aae56b3c6f07088e801555bffb7eeb15faa Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 16 Aug 2024 10:45:43 -0400 Subject: [PATCH 23/35] Address review comments - Remove documentation link - Link the trackbar size to the zedGraph size - Align GUI elements - Reduce thickness of the scale lines when zoomed out - Change wording of open/save file menus for clarity - Add Open Ephys logo to all dialogs - Remove empty .gitmodules file --- .gitmodules | 0 .../ChannelConfigurationDialog.Designer.cs | 26 +- .../ChannelConfigurationDialog.cs | 8 + .../ChannelConfigurationDialog.resx | 1655 ++++++++++++++++ .../GenericDeviceDialog.Designer.cs | 16 +- .../GenericDeviceDialog.resx | 1655 ++++++++++++++++ ...europixelsV2eChannelConfigurationDialog.cs | 2 +- .../NeuropixelsV2eDialog.Designer.cs | 24 +- .../NeuropixelsV2eDialog.resx | 1655 ++++++++++++++++ .../NeuropixelsV2eHeadstageDialog.Designer.cs | 36 +- .../NeuropixelsV2eHeadstageDialog.resx | 1655 ++++++++++++++++ ...elsV2eProbeConfigurationDialog.Designer.cs | 88 +- .../NeuropixelsV2eProbeConfigurationDialog.cs | 23 +- ...europixelsV2eProbeConfigurationDialog.resx | 1664 ++++++++++++++++- OpenEphys.Onix1.Design/icon.ico | Bin 0 -> 98966 bytes 15 files changed, 8393 insertions(+), 114 deletions(-) delete mode 100644 .gitmodules create mode 100644 OpenEphys.Onix1.Design/icon.ico diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs index 11e574a0..597be31d 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs @@ -29,6 +29,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ChannelConfigurationDialog)); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.zedGraphChannels = new ZedGraph.ZedGraphControl(); this.buttonCancel = new System.Windows.Forms.Button(); @@ -50,7 +51,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, 24); - this.splitContainer1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(2); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -63,7 +64,7 @@ private void InitializeComponent() this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); this.splitContainer1.Panel2.Controls.Add(this.buttonOK); this.splitContainer1.Size = new System.Drawing.Size(457, 442); - this.splitContainer1.SplitterDistance = 400; + this.splitContainer1.SplitterDistance = 403; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 0; // @@ -81,7 +82,7 @@ private void InitializeComponent() this.zedGraphChannels.ScrollMinX = 0D; this.zedGraphChannels.ScrollMinY = 0D; this.zedGraphChannels.ScrollMinY2 = 0D; - this.zedGraphChannels.Size = new System.Drawing.Size(457, 400); + this.zedGraphChannels.Size = new System.Drawing.Size(457, 403); this.zedGraphChannels.TabIndex = 4; this.zedGraphChannels.UseExtendedPrintDialog = true; this.zedGraphChannels.ZoomEvent += new ZedGraph.ZedGraphControl.ZoomEventHandler(this.ZoomEvent); @@ -91,7 +92,7 @@ private void InitializeComponent() 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(339, 7); - this.buttonCancel.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(108, 26); this.buttonCancel.TabIndex = 4; @@ -102,7 +103,7 @@ private void InitializeComponent() // this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonOK.Location = new System.Drawing.Point(217, 7); - this.buttonOK.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonOK.Margin = new System.Windows.Forms.Padding(2); this.buttonOK.Name = "buttonOK"; this.buttonOK.Size = new System.Drawing.Size(108, 26); this.buttonOK.TabIndex = 3; @@ -135,22 +136,22 @@ private void InitializeComponent() // dropDownOpenFile // this.dropDownOpenFile.Name = "dropDownOpenFile"; - this.dropDownOpenFile.Size = new System.Drawing.Size(141, 22); - this.dropDownOpenFile.Text = "Open File"; + this.dropDownOpenFile.Size = new System.Drawing.Size(265, 22); + this.dropDownOpenFile.Text = "Open Channel Configuration"; this.dropDownOpenFile.Click += new System.EventHandler(this.MenuItemOpenFile); // // dropDownSaveFile // this.dropDownSaveFile.Name = "dropDownSaveFile"; - this.dropDownSaveFile.Size = new System.Drawing.Size(141, 22); - this.dropDownSaveFile.Text = "Save File"; + this.dropDownSaveFile.Size = new System.Drawing.Size(265, 22); + this.dropDownSaveFile.Text = "Save Channel Configuration"; this.dropDownSaveFile.Click += new System.EventHandler(this.MenuItemSaveFile); // // dropDownLoadDefault // this.dropDownLoadDefault.Name = "dropDownLoadDefault"; - this.dropDownLoadDefault.Size = new System.Drawing.Size(141, 22); - this.dropDownLoadDefault.Text = "Load Default"; + this.dropDownLoadDefault.Size = new System.Drawing.Size(265, 22); + this.dropDownLoadDefault.Text = "Load Default Channel Configuration"; this.dropDownLoadDefault.Click += new System.EventHandler(this.MenuItemLoadDefaultConfig); // // ChannelConfigurationDialog @@ -160,8 +161,9 @@ private void InitializeComponent() this.ClientSize = new System.Drawing.Size(457, 466); this.Controls.Add(this.splitContainer1); this.Controls.Add(this.menuStrip); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip; - this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.Margin = new System.Windows.Forms.Padding(2); this.Name = "ChannelConfigurationDialog"; this.Text = "ChannelConfigurationDialog"; this.splitContainer1.Panel1.ResumeLayout(false); diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index 412cd26b..8dc78734 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -16,6 +16,8 @@ namespace OpenEphys.Onix1.Design /// public abstract partial class ChannelConfigurationDialog : Form { + internal event EventHandler OnResizeZedGraph; + /// /// Local variable that holds the channel configuration in memory until the user presses Okay /// @@ -896,6 +898,12 @@ private void ZedGraphChannels_Resize(object sender, EventArgs e) UpdateFontSize(); DrawScale(); RefreshZedGraph(); + OnResizeHandler(); + } + + private void OnResizeHandler() + { + OnResizeZedGraph?.Invoke(this, EventArgs.Empty); } /// diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.resx b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.resx index 7dd004c7..d7e7a3ce 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.resx +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.resx @@ -120,4 +120,1659 @@ 11, 11 + + + + AAABAA4AEBAQAAEABAAoAQAA5gAAABAQAAABAAgAaAUAAA4CAAAQEAAAAQAgAGgEAAB2BwAAICAQAAEA + BADoAgAA3gsAACAgAAABAAgAqAgAAMYOAAAgIAAAAQAgAKgQAABuFwAAMDAQAAEABABoBgAAFigAADAw + AAABAAgAqA4AAH4uAAAwMAAAAQAYAKgcAAAmPQAAMDAAAAEAIACoJQAAzlkAAEBAAAABABgAKDIAAHZ/ + AABAQAAAAQAgAChCAACesQAAAAAAAAEAGABpMQAAxvMAAAAAAAABACAAZ10AAC8lAQAoAAAAEAAAACAA + AAABAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAgAAAgICAAACAgADAwMAA//8AAAD/ + /wAAAP8A/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUiAAAAAAAFU5YAAA + AAVVADYgAAAFVQAGYlAAVTUAVTUjMAM1VVVTIDOABVAAAAYDRHAAVQAAYzhlAAAFUAY4AFcAAABVA0AA + NwAAAAUDAAZAAAAAAAAAA0AAAAAAAAACAAAAAAAAAAEAAAAAAAAAAAAA//8AAP/xAAD/wQAA/jEAAPjh + AADDAAAAgBEAAJ+hAADPAwAA5jMAAPJzAAD45wAA/+cAAP/vAAD/7wAA//8AACgAAAAQAAAAIAAAAAEA + CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI5YzAEWaLgCFpi8AJLioAL2exQA9QTAAO12VANK0 + LwAws5AAzarWAGZsPgA+Z9UAdLRpACbQ+wDdtS8Ak41jAEBr6QDAtkIA37YvAKmdbgA/auoAR27gANKu + MgA8zeMAu5vDAMiq0ABLXUwATaKmAHpvfACWlJUAqqmpALe2tgDGr2QAxKAqAHt6RQA+XowAHJXwALW0 + tACysbEAtLOzALe1swCrjzcAsp5eAK2ecQCQgU8ALk93AA09OADctDEAzqw0AN61LgDbrikA3bMsAMWs + XgC/gxAA1qYjALazrQCMYSEAnGUNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg5 + OgAAAAAAAAAAAAAAICA1NjcAAAAAAAAAACAgIAAAMjM0AAAAAAAAICAgAAAAExMwMQAAACYnKCAAACAp + KissLS4vAB0eHyAgICAgISIAIyQlAAAZGgAAAAAAABMAFRscDgAAAAoKAAAAABMUFRYXGAAAAAAACgoA + AA8QEQAAEg4AAAAAAAAKCgALDAAAAA0OAAAAAAAAAAUGBwAAAAgJAAAAAAAAAAAAAAAAAAADBAAAAAAA + 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 + ABMAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + //////+H///+B///+AP//8AD//8HA//4HgP/4HwB/wP8ifwP+AHwAAABgAABAYAH5wOH/8YD4/+MB/H/ + kAf4/wEH/H4DD/48Dg//HB4f/4h+H//A/h//4f4///f+P////H////x////8f////P////z///////// + //8oAAAAIAAAAEAAAAABAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULgAmmTwAIpQuACGX + NwBNnC4AIZQvACW/wACPqC8AJLmsANS0LwAxly4AI6+JAD5CNwB1oy4AIqNhACbQ+wCchaAALDMkAC02 + KQA8X6cAwbEvACOZPgAmz/cAzarWAMmn0QBmWUwAM0MoADhQRQA+aN0A37YvAFKdLgAlyN8Ano0tAEFS + KwA6V3AAP2rpAJ+qLwAkvLcAxqYuAFliLAA8X6QAP2rqANm1LwA1tJAA2bIvAIF8OAA/Z9AAfLVpAKyb + VgBDbOUAxbZAACbQ+gDCqFEAPszgAEFr5gCrlzoAf8OZADpUWgCAei0AzLE5AIhxjgCkiKsAt5e/AMKn + yQA6WX8AN0wrAEFXUAAsuPcAaVZtAHZpeQCbmZoAsK+vALe2tgBWaGgAPE8rADpt6QAcpPMAdGJ3AIB2 + ggCKiYkAhoWFAIWEhACTkpIAqKenALa1tQCynl4AsI8lALSSJgCxp4oAwLKGAKqRKwBIVSwAPWXVABlb + 5AAUg+0AJMf5ALOysgCrqqoArq2tALazrwCkizoAoIIiAKSMPgC2tLEAoYQpAJl9IgBZZocAIkmpAAY3 + aAALRk0Au5goALaVJwC4pGMAp44+AKKEIgCsnnMAgIqoABAyJAAGLB4A3LMuANayOgC0mSoAmIsqAN60 + LQDftS8A3rQuANOgHwDarSgA2aomANK0VADetS8AyY0RAMyTFgCwlUEAtYMTALJqBQDNlRcAs62dAJdy + HAB9RAEAo2kMAKSQawB6QgEAgUoFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASY6PkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASUmKi4yNAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASUlJaIaHiIkeAAAAAAAAAAAAAAAAAAAAAAAAAABJSUlJSUmCg4SFfx4AAAAA + AAAAAAAAAAAAAAAAAABJSUlJSQAAAB58f4CBHgAAAAAAAAAAAAAAAAAAAElJSUlJSQAAAAAeHnx9Hn4e + AAAAAAAAAAAAAAAAAElJSUlJSQAAAAAAHh4eHh4eHh4eAAAAAAAAAAAASUlJSUlJAAAAAAAAAAAeHgAe + eHkAensAAAAAAAAASUlJSUlJAAAAAAAAAAAAb3BxSXJzdHV2dwAAAAAAVWFIYmNVSUlJSUlJSUlJZGVm + Z2hoaWprbG1uAABOT1BRUlNUVUlJSUlJSUlJSUlWV1hZAFpbXF1eX2AAAEVFRkdISUlJSUlJSQAAAAAA + AB4eAAAASkscTE0QAAAAPT4/QAAAAAAAAAAAAAAAAAAeHh4AACpBQkNEEBAAAAAAABgYGAAAAAAAAAAA + AAAAHh4eAAAqKjo7PBAQAAAAAAAAABgYGAAAAAAAAAAAAAAeHgAqKio3OB45EBAAAAAAAAAAABgYGAAA + AAAAAAAAHh41KioqKgAeHjYQEAAAAAAAAAAAABgYGAAAAAAAAB4eMTIqKioAAB4zNBAAAAAAAAAAAAAA + ABgYGAAAAAAeLS4vKioAAAAeHjAQEAAAAAAAAAAAAAAAABgYGAAAACcoKSoqAAAAAB4rLBAAAAAAAAAA + AAAAAAAAABgYGAAhIiMkAAAAAAAAHiUmEAAAAAAAAAAAAAAAAAAAABgZGhscHQAAAAAAAAAeHyAQAAAA + AAAAAAAAAAAAAAAAABESExQAAAAAAAAAABUWFwAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAADg8Q + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoLDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + CAEJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////// + /4f///4H///4A///wAP//wcD//geA//gfAH/A/yJ/A/4AfAAAAGAAAEBgAfnA4f/xgPj/4wH8f+QB/j/ + AQf8fgMP/jwOD/8cHh//iH4f/8D+H//h/j//9/4////8f////H////x////8/////P///////////ygA + AAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsqqXDIxaEWuPWw1DAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AADwf//DAI8AAPg//4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/w + eAfgfwAA//gwH+B/AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/ + AAD/////g/8AAP////+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP// + /////wAA////////AAD///////8AACgAAAAwAAAAYAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIZQuAC2cQQA4nT8AI5xHACSVLgAhlzgAUp0uACGULwAlwMQAlqkvACS4qADXtS8AMZcuACOs + gQAm0PsA37YvAHmkLgAioVoAJtD6AMCwLwAmlS4AJs71AD1BNwA7QDQAVJ0uACXF1wCJd4sALDMkAC44 + LwA8YbUAoqsvACS6rwDNqtYAq5CwAC81KAAvOyYAOFJRAD9p4gDYtS8AN5guACOviQCbfXkAQEMmADZL + KwA3TCsAOll/AD9q6QB9pC4AIqNgAMypzQDHpS8AaGssADhMKwA3TC0APGGyAD9q6gDGsi8AJptCACbN + 8gDbtC8AkoYtADtPKwA4T0AAPmfWAFieMAAlx9wAu58uAFBbLAA5VWIAP2roAKWsLwAlvLcA0q4vAHFx + LQA8XZUA2bUvAD61jwDetS8Am4w1AEVpxACCtmkAwKZJAEpv3QDItj0AJ9D5ANKwPgBAaukA3rYvAELM + 3ACFw5IAP2rmAHV3SADUry8A2bc2ADpWaABTXiwAvaAuADtbjQA9UCsAioEvAIBqhQCKcpAAoISnAK+R + tgC7ocEAPWGxADhNLQA+ZtMAKcP5AHdlewBpVm0AeWp8AKGeoAC0s7MAt7a2ADlOLQA9YrkANHLrAB+y + 9gB2ZHoAcmV0AIKBgQCDgoIAh4aGAJqZmQCura0AyrFhANCpLADUrS0A2bEuANy0LgCymS4ATVorADtc + kgA+auoAElznABiU8QAlzfsAeWh8AIh+iQCRkJAAjYyMAImIiACGhYUAhYSEAISDgwCmpaUAtbS0ALe1 + tACnkU0AoYMiAKKEIgCkhSMArqB3AMCjRQC4mCkAXmEoADdSaQA9ZuIAJl/kAAlV5AARdusAIsH4ALGw + sACpqKgApKOjALKxsQC0sKUAoocxAKCCIgChhSsAtK+hAKubaACfgSIAbWU3ADJQpwAqTKoACECpAAY5 + bQAJQ1YAGXyMALCniwCggiMApY5DALa0sQCvpYYAoYUqAJudpgBEXqoAFD1/AAYsHgAMNCkAxaMyAMmk + KgDDnykAvZooAL6pZgC0rpwApockAKOFIwCigyIAsqqUAK6vtQBSX1oAEjgoANuyLgDXry0AzrFQAMak + NQCMgSgAaG4mAJ+RKwDftS8A37UuANuvKgDarSgA3bIsAN60LQDKjxMA1aQiANSiIADctjoAyY0RAM+Z + GgDNlRcAz5gZALi2sgDBpUgA1a4tAM6XGADDgQkAvnsIAMmOEgCzrp8Ao4csAKh/GQCtbwcArGADAMOC + CwCvo4AAnHocAINPBACARgEAnFwDANmrKAC3trUAp5NWAH9JAwB6QgEArnwXALWyqgCFVRgAilYLAI9b + DQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAA+/z5+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc3P29/j5 + +foAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNzc3Pw8fLz9PUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNzc3Nzc+rr7O3u79oAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAABzc3Nzc3Nz4+Tl5ufo6RAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc3Nz + c3Nzc3MA3hAQ3+Dh4hAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc3Nzc3Nzc3NzAAAAEBDa29rc + 3dUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNzc3Nzc3NzAAAAAAAQEBDX2BDZ2BAQAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAHNzc3Nzc3NzcwAAAAAAABAQEBDVEBAQ1hAQEAAAAAAAAAAAAAAAAAAAAAAAAABz + c3Nzc3Nzc3MAAAAAAAAAABAQEBAQEBAQABAQEAAAAAAAAAAAAAAAAAAAAABzc3Nzc3Nzc3MAAAAAAAAA + AAAAEBAQEAAQEBAQABAQEAAAAAAAAAAAAAAAAAAAc3Nzc3Nzc3NzAAAAAAAAAAAAAAAQEBAQAAAQzs/Q + ANHS09QAAAAAAAAAAAAAAHNzc3Nzc3NzAAAAAAAAAAAAAAAAAMHCw8TFc8bHyMnKy8y/v80AAAAAAAAA + AHNzc3Nzc3NzcwAAAAAAAABzc3Nzc3Nztreqqri5c7qqqru8vb6/v8AAAAAAAABzc3KkfqWmk6dzc3Nz + c3Nzc3Nzc3Nzc3Ooqaqqq6xzc62qrq+wsbKztLUAAACLjI2Oj5CRko2TlHNzc3Nzc3Nzc3Nzc3Nzc5WW + l5iZmnNzc5ucnZ6foKGiowAAAHhvb3l6e3x9fnNzc3Nzc3Nzc3Nzc3Nzc3NzAH+AgYKDAAAAAISFLYaH + iImKDwAAAG5vb29wcXJzc3Nzc3NzcwAAAAAAAAAAAAAAABAQEBAAAAAAAHQtLXV2dw8PAAAAAABlZmdo + aQAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAA4ai0ta2xtDw8PAAAAAAAAISEhISEAAAAAAAAAAAAA + AAAAAAAAAAAQEBAQAAAAADg4Yi1jZAAPDw8PAAAAAAAAACEhISEhAAAAAAAAAAAAAAAAAAAAAAAQEBAQ + AAA4ODg4X2BhEAAPDw8AAAAAAAAAAAAhISEhIQAAAAAAAAAAAAAAAAAAABAQEBAAADg4ODhbXF0QXg8P + Dw8AAAAAAAAAAAAAISEhISEAAAAAAAAAAAAAAAAAEBAQEAAAODg4ODgAWBAQWg8PDwAAAAAAAAAAAAAA + ACEhISEhAAAAAAAAAAAAAAAAEBAQVlc4ODg4OAAAEBBYWQ8PDwAAAAAAAAAAAAAAAAAhISEhIQAAAAAA + AAAAAAAQEBBSUzg4ODg4AAAAEBBUVQ8PAAAAAAAAAAAAAAAAAAAAACEhISEAAAAAAAAAABAQTk9QODg4 + OAAAAAAQEBBRDw8PAAAAAAAAAAAAAAAAAAAAAAAhISEhAAAAAAAAEBBJSks4ODg4AAAAAAAQEExNDw8P + AAAAAAAAAAAAAAAAAAAAAAAAISEhISEAAAAAEENERUY4ODgAAAAAAAAQEEdIDw8AAAAAAAAAAAAAAAAA + AAAAAAAAACEhISEhAAA8PT4/QDg4AAAAAAAAAAAQEEFCDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAhISEh + MjM0NTY3ODgAAAAAAAAAAAAQOTo7DwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISEhKissLS4vAAAAAAAA + AAAAABAQMDEPDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiIxwkJSYAAAAAAAAAAAAAABAnKCkPDwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbHBwdHgAAAAAAAAAAAAAAABAfASAPAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAFxgAAAAAAAAAAAAAAAAAABAZARoPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAABQVBhYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAEBEBEhMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA0BDg8AAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgEBCwAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwEICQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAABQEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAADAQEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////AAD///////8AAP//////nwAA//////4f + AAD/////8A8AAP/////ADwAA/////gAPAAD////4AAcAAP///+AQBwAA////AHAHAAD///wD4AcAAP// + 4A/AAwAA//+AP8AjAAD//AH/hCMAAP/wB/8MIQAA/8A//gABAAD+AP4AAAEAAPgAAAAAAQAAwAAAAAAD + AACAAAAQeAMAAIAA//D4BwAAwf//4eAHAADg///DwIcAAPB//8MAjwAA+D//hgAPAAD8H/8MEB8AAP4P + /wAwHwAA/wf+AHA/AAD/w/wB4D8AAP/h+APgPwAA//B4B+B/AAD/+DAf4H8AAP/8AD/g/wAA//4A/8D/ + AAD//wH/wP8AAP//g//B/wAA///P/8H/AAD/////w/8AAP////+D/wAA/////4P/AAD/////h/8AAP// + //+H/wAA/////4//AAD/////D/8AAP////+P/wAA////////AAD///////8AAP///////wAAKAAAADAA + AABgAAAAAQAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIpWC49bDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKdLiGULiGULyXAxAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + ACSVLiGULiGXOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAADidPyGULiGULiOcRwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULiGULi2cQQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////wAA//////// + AAD//////58AAP/////+HwAA//////APAAD/////wA8AAP////4ADwAA////+AAHAAD////gEAcAAP// + /wBwBwAA///8A+AHAAD//+APwAMAAP//gD/AIwAA//wB/4QjAAD/8Af/DCEAAP/AP/4AAQAA/gD+AAAB + AAD4AAAAAAEAAMAAAAAAAwAAgAAAEHgDAACAAP/w+AcAAMH//+HgBwAA4P//w8CHAADwf//DAI8AAPg/ + /4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/weAfgfwAA//gwH+B/ + AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/AAD/////g/8AAP// + //+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP///////wAA//////// + AAD///////8AACgAAAAwAAAAYAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + ogYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYyt7a2mre2tu+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tvm3traqt7a2Q7e2tgO3trYIt7a2ILe2tji3trZPt7a2aLe2toC3traXt7a2sLe2tsm3trbat7a27Le2 + tvqwp4v/oIIj/6CCIv+ggiL/pY5D/7a0sf+3trb/r6WG/6CCIv+ggiL/oYUq/5udpv9EXqr/FD1//wYs + Hv8GLB7/DDQp7WSTmw0AAAAAAAAAAAAAAAC3trYNt7a2XLe2tsa3trb+tLOz/7GwsP+ura3/qaio/6Sj + o/+mpaX/srGx9Le2tsC3tra/t7a21be2tu+3trb+t7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7Swpf+ihzH/oIIi/6CCIv+hhSv/tK+h/7e2tv+3trb/q5to/6CCIv+fgSL/bWU3/zJQ + p/8qTKr/CECp/wY5bf8JQ1b/GXyMigAAAAAAAAAAkoKVGXlofJOIfonkkZCQ/42MjP+JiIj/hoWF/4WE + hP+Eg4P/kZCQ/6alpf+1tLT/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7W0/6eRTf+hgyL/ooQi/6SFI/6uoHfgt7a2xLe2tq63traWwKNF4riY + Kf9eYSj/N1Jp/z1m4v8mX+T/CVXk/xF26/8iwfj5JtD7JgAAAAAAAAAAdmR6sWlWbf9pVm3/cmV0/4KB + gf+DgoL/h4aG/5qZmf+ura3/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb5t7a25be2ts23tra3t7a2n7e2toe3trZvyrFhudCpLP/UrS3/2bEu/9y0LqTfti8CAAAAAAAA + AAAAAAAAspku501aK/83TCv/O1yS/z5q6v8SXOf/GJTx/yXN+/8m0Pu4JtD7AgAAAAAAAAAAd2V7xmlW + bf9pVm3/aVZt/3lqfP+hnqD/tLOz/7e2tv+3trb+t7a287e2tua3trbYt7a2wLe2tqm3traQt7a2eLe2 + tmG3trZIt7a2Mbe2thi3trYGt7a2A7e2tgEAAAAAAAAAAAAAAADfti8z37Yv99+2L//fti//37Yv2d+2 + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2LwHXtS/DMZcu/yGU + Lv8jrIH/JtD7oQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + LwyWqS/qIZQu/yGULv8kuKj/JtD7OgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANW0LydSnS7/IZQu/yGUL/8lwMTOJtD7AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIClL1kklS7/IZQu/yGXOP8lx9xqAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADidP5QhlC7/IZQu/yOcR/Am0PsUAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADefRX8hlC7/IZQu/y2c + QaEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKr + YQgxnD9zLZo6fFeuZhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP///////wAA//////4PAAD/////+A8AAP// + ///ADwAA/////wAHAAD////4AAcAAP///+AABwAA////gAAHAAD///wAAAMAAP//8ADAAwAA//+AA4AD + AAD//gAPgAEAAP/4AH8AAQAA/8AD/gABAAD/AA/gAAAAAPwAAAAAAAAA4AAAAAABAACAAAAAAAEAAIAA + AAA4AQAAgAAA4GADAACAB//AwAMAAMB//8GABwAA4D//gAAHAADwH/8AAA8AAPgP/gAADwAA/Af+AAAP + AAD+A/wAIB8AAP8A+ADgHwAA/4BwAcA/AAD/4DADwD8AAP/wAA/APwAA//gAH8B/AAD//AA/wH8AAP/+ + AP/A/wAA//8B/4D/AAD//4P/gP8AAP//z/+B/wAA/////4H/AAD/////A/8AAP////8D/wAA/////wP/ + AAD/////B/8AAP////8H/wAA/////w//AAD/////D/8AAP///////wAA////////AAAoAAAAQAAAAIAA + AAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjV0YiVQKi1YLAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2n4Ve + ekIBekIBekIBh1IIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2squWjWEUekIBekIBekIBiVQJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAt7a2t7a2t7a2t7a2trOupIs9i14LekIBekIBekIBpG8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7W1qZddn4EhjF0HfEUBhUgBnloDz5kb37YvAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2r6SCoIMkoX8eomwIqGMErF8Cv3wI1qUj + 37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2tK+grpAzw58pzp0gw4IJ + unQGuXMGw4IJ268q37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uLaz0LBH + 3bQv37Yv0Joaw4IJyY0SxIMKxocN3rQu37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2AAAAAAAA37Yv37Yv37UvypATx4kO3LAryY4Ry5AT37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2AAAAAAAAAAAAAAAA37Yv37Yv37Yv3rQtxogO2Kgl37Yv0Zwc0Joa37Yv37Yv37YvAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv27Aq0Jsb37Yv37Yv2asn1aMh37Yv + 37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv264p3rQu37Yv + 37Yv3rUu268q37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv + 37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37Yv + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAA37Yv37Yv37Yv37YvAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA3rUv2rIu1q8t + 0qwuAAAAAAAAv6hesJMnYWclOlIjdXcnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA168t1a4t0KosyqUqxKAqw61ot7a2t7a2 + r51kp4gjpYYjo4Ujo4gvtbGpt7a2l5iXLEAjBiweBiweBy0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7WzqZNLpIUjoIIioIIioIIi + ppBKtrSwt7a2t7a2qJNToIIioIIioIIipIw+tbS1gIywMlCkCjAzBiweBiweBiweHkc/AAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2rqF6oIIi + oIIioIIioIIioocvtK+it7a2t7a2t7W0o4k2oIIioIIioIIino5aXXGsLUynHkamBjRgBiweBiweBi0g + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2tLOzsrGxrq2tq6qqp6amoqGhnJubnp2dq6qq + tLOzt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2sqyYoYUroIIioIIioIIioIIjr6WFt7a2t7a2t7a2tLCkoYUqoIIioIIigm8hPE5uLUynLk6rD0Os + BkGrBzt4CEBgF4SdAAAAAAAAAAAAAAAAAAAAg3SGkImRmZeYlZSUkI+Pi4qKiYiIh4aGhYSEg4KChIOD + j46OpKOjs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2trOtpY1CoIIioIIioIIioIIiqZZct7a1t7a2t7a2t7a2t6uHuZcouZgoeXEoN0gn + OV2zPGbgMWPiClXiCVXkDmzqILf3Js/7AAAAAAAAAAAAAAAAcmB2aVZtaVZtcmV0goCBg4KCg4KCg4KC + g4KChoWFmJeXrKurtrW1t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uKNitpUnvJkowp4pyKMqyqUsAAAAAAAAAAAAAAAAAAAAAAAA + zasvamwsOEwrN042PmfYP2rqH17oCVjmFIXuJMf6JtD7AAAAAAAAAAAAAAAAAAAAaVZtaVZtaVZtaVZt + cGJzgX+Bg4KCjIuLoJ+fs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3rYvV54uIZQuIZQuJcPP + JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwrEv + KZUuIZQuIZUxJs70JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve6QuIZQuIZQuIp9SJtD6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA17UvM5cuIZQuIZQuI6p6JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnaovIZQuIZQuIZQuJLWgAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV54uIZQuIZQuIZUwJcDDAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ5UuIZQu + IZQuIZc5JcXXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAARZ46IZQuIZQuIZQuIpxIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM5xAIZQuIZQuIZQuJ6BTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOaBHIZQuIZQuIpQvAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOJ9GI5UwAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/////////////////////////////////////////H/////////wP/// + /////4A////////+AD////////gAH///////wAAf//////8AAB//////+AAAD//////gAYAP/////4AP + AA/////8AD4AD/////AB/AAH////gAf8AAf///4AP/ggh///+AD/8CCD///AA//gYYP//wAf/+Dhg//4 + AH//wAAD/+AD//gAAAH/AA/AAAAAA/wAAAAAAAAD4AAAAAAAAAPAAAAAAA/AB8AAAAP8H4AHwAAP//g/ + AA/AP///8D4AD+A////wfAYP+B///+DwBB/8D///wOAEH/4H//+BwAQ//wP//4MAAD//gf//BgMAP//A + f/4EBwB//+A//gAOAH//8B/8AD4A///4D/gAfgD///wH8AD+Af///gPwA/4B////AeAH/AH////AwA/8 + A////+AAP/wD////8AB//Af////4Af/8B/////wD//gH/////gf/+A//////H//4D/////////gf//// + ////+B/////////4H/////////A/////////8D/////////wf/////////B/////////8H/////////g + /////////+D/////////4f/////////z//////////////////////////////////8oAAAAQAAAAIAA + AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKpUACs4ckEMObMAgAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyqpcwjV0YpYlU + CvSLVgvSmWcTMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dre2 + tli3trbBn4Ve/HpCAf96QgH/ekIB/4dSCM3FnzkGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYlt7a2g7e2tt63trb9squW/41hFP96QgH/ekIB/3pCAf+JVAn4yZ0oIwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2Bre2tkW3trast7a28re2tv+3trb/trOu/6SLPf+LXgv/ekIB/3pCAf96QgH/pG8P/9+2 + L1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYVt7a2b7e2ttW3trb+t7a2/7e2tv+3trb/t7W1/6mXXf+fgSH/jF0H/3xF + Af+FSAH/nloD/8+ZG//fti+iAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2BLe2tjm3trabt7a277e2tv+3trb/t7a2/7e2tv+3trb/t7a2/6+k + gv+ggyT/oX8e/6JsCP+oYwT/rF8C/798CP/WpSP/37Yv4N+2LwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYSt7a2Xre2tsO3trb4t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7SvoP+ukDP/w58p/86dIP/Dggn/unQG/7lzBv/Dggn/268q/9+2L/vfti88AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Are2tiS3traJt7a24re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2+Li2s7TQsEfr3bQv/9+2L//Qmhr/w4IJ/8mNEv/Egwr/xocN/960 + Lv/fti//37YvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2T7e2trO3trb6t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbgt7a2g7m2sSLfti9937Yv/9+2L//ftS//ypAT/8eJ + Dv/csCv/yY4R/8uQE//fti//37Yv/9+2L8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tiG3trZ2t7a22be2 + tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a297e2try3trZat7a2DAAAAADfti8/37Yv9t+2 + L//fti//3rQt/8aIDv/YqCX/37Yv/9GcHP/Qmhr/37Yv/9+2L//fti/637YvHAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2Obe2 + tqS3trbtt7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv63trbqt7a2lre2tjO3trYEAAAAAAAA + AADfti8W37Yv1d+2L//fti//37Yv/9uwKv/Qmxv/37Yv/9+2L//Zqyf/1aMh/9+2L//fti//37Yv/9+2 + L2MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgy3trZlt7a2xre2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2ttK3trZqt7a2FgAA + AAAAAAAAAAAAAAAAAADfti8C37Yvo9+2L//fti//37Yv/9+2L//brin23rQu/9+2L//fti//3rUu/duv + KvDfti//37Yv/9+2L//fti+p37YvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAC3trYBt7a2L7e2to+3trbut7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbyt7a2pre2 + tkC3trYBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvYt+2L/zfti//37Yv/9+2L//fti/p37YvrN+2 + L//fti//37Yv/9+2L/Dfti+m37Yv/9+2L//fti//37Yv6d+2Lw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAALe2tg63trZSt7a2u7e2tva3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/re2 + ttq3trZ8t7a2ILe2tgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvKN+2L+/fti//37Yv/9+2 + L//fti/837YvYN+2L7Xfti//37Yv/9+2L//fti/G37YvV9+2L/7fti//37Yv/9+2L//fti9DAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYCt7a2H7e2tn63trbZt7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb3t7a2ure2tlO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvDN+2 + L8Lfti//37Yv/9+2L//fti//37Yvod+2LxHfti/b37Yv/9+2L//fti//37YvlN+2Lxbfti/037Yv/9+2 + L//fti//37YvjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgK3trZBt7a2pre2tvS3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tuy3traPt7a2Lre2tgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2L4Xfti//37Yv/9+2L//fti//37Yv3d+2LxDfti8i37Yv9d+2L//fti//37Yv/9+2 + L2QAAAAA37Yvv9+2L//fti//37Yv/9+2L9Dfti8HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Fre2tmy3trbUt7a2/re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb9t7a2xbe2tmO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L0rfti/337Yv/9+2L//fti//37Yv9N+2Lz4AAAAA37YvS961 + L//asi7/1q8t/9KsLv3Esndyt7a2X7+oXrqwkyf/YWcl/zpSI/91dyf4sp0sKwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgS3trY0t7a2lre2tum3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tu23traht7a2O7e2tgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2CMasViTXry3f1a4t/9CqLP/KpSr/xKAq/8Ot + aNu3traft7a2tq+dZN+niCP/pYYj/6OFI/+jiC//tbGp/7e2tv+XmJf/LEAj/wYsHv8GLB7/By0e/yhO + On8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dbe2tli3tra/t7a2+Le2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb8t7a22re2tnW3trYeAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYFt7a2Cbe2tg23trYbt7a2Nbe2tky3trZlt7a2fLe2tpS3tratt7a2xLe1s9Spk0vspIUj/6CC + Iv+ggiL/oIIi/6aQSv+2tLD/t7a2/7e2tv+ok1P/oIIi/6CCIv+ggiL/pIw+/7W0tf+AjLD/MlCk/wow + M/8GLB7/Biwe/wYsHv8eRz+4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYjt7a2hLe2 + tuG3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tvu3trawt7a2Ure2tiC3trYvt7a2Rbe2 + tlq3trZ0t7a2i7e2tqK3tra6t7a2zbe2tuO3trb2t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+uoXr/oIIi/6CCIv+ggiL/oIIi/6KHL/+0r6L/t7a2/7e2tv+3tbT/o4k2/6CCIv+ggiL/oIIi/56O + Wv9dcaz/LUyn/x5Gpv8GNGD/Biwe/wYsHv8GLSD/G0xIcgAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Cbe2 + tkm3travt7a297e2tv+0s7P/srGx/66trf+rqqr/p6am/6Khof+cm5v/np2d/6uqqv60s7Pht7a25Le2 + tvW3trb4t7a2+7e2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+yrJj/oYUr/6CCIv+ggiL/oIIi/6CCI/+vpYX/t7a2/7e2tv+3trb/tLCk/6GF + Kv+ggiL/oIIi/4JvIf88Tm7/LUyn/y5Oq/8PQ6z/BkGr/wc7eP8IQGD/F4Sd4ym42xYAAAAAAAAAAAAA + AACTg5Ygg3SGgJCJkdOZl5j7lZSU/5CPj/+Lior/iYiI/4eGhv+FhIT/g4KC/4SDg/+Pjo7/pKOj/7Oy + sv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+2s63/pY1C/6CCIv+ggiL/oIIi/6CCIv+pllz6t7a18be2 + tui3trbgt7a2zberh825lyj/uZgo/3lxKP83SCf/OV2z/zxm4P8xY+L/ClXi/wlV5P8ObOr/ILf3/ybP + +5EAAAAAAAAAAAAAAACLeo4acmB23mlWbf9pVm3/cmV0/4KAgf+DgoL/g4KC/4OCgv+DgoL/hoWF/5iX + l/+sq6v/trW1/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/re2tvy3trb6t7a2+Le2tuy3trbWuKNi9baVJ/+8mSj/wp4p/8ij + Kv/KpSzUtaqGN7e2tiO3trYTt7a2BAAAAADfti90zasv/2psLP84TCv/N042/z5n2P8/aur/H17o/wlY + 5v8Uhe7/JMf6/ybQ+/wm0PsvAAAAAAAAAAAAAAAAjHqPcWlWbf9pVm3/aVZt/2lWbf9wYnP/gX+B/4OC + gv+Mi4v/oJ+f/7Oysv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb9t7a27Le2tta3trbDt7a2rre2tpa3trZ+t7a2Zre2tk+3trY4t7a2I7e2tg4AAAAA37YvTt+2 + L/zfti//37Yv/9+2L//fti/y37YvNwAAAAAAAAAAAAAAAAAAAAAAAAAAlIczp0pYK/83TCv/N0wr/zhR + Tv8/aeT/O2nq/w9g6P8cpPT/Js/7/ybQ+/8m0Pu9JtD7BAAAAAAAAAAAAAAAAI59kVVrWG/+aVZt/2lW + bf9pVm3/a1hv/31sgP+moqX/tbS0/7e2tv+3trb/t7a2/7e2tve3trbot7a227e2ts63tra3t7a2obe2 + toe3trZvt7a2Wbe2tkG3trYmt7a2Ebe2tgy3trYHt7a2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37YvH9+2L+Pfti//37Yv/9+2L//fti/+37YvcN+2LwEAAAAAAAAAAAAAAAA/auoMP2rqgzlR + SPs3TCv/N0wr/zdMK/86V27/P2rq/y567PchvPj/JtD7/ybQ+/8m0Pv+JtD7WQAAAAAAAAAAAAAAAAAA + AACjk6UJfmqBoHFddvx0X3n/hm+M/5p/of+mia3/s5q5/ruyvZu3trZht7a2Sre2tjG3trYit7a2F7e2 + tg23trYDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37YvCN+2L7Lfti//37Yv/9+2L//fti//37Yvst+2LwcAAAAAAAAAAAAA + AAA/auonP2rqvD9p4f43TC7/N0wr/zdMK/83TCv/O1yQ+D5v65YnyfrTJtD7/ybQ+/8m0Pv/JtD76CbQ + +wsAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gPCocuAyafS/c2q1v/Nqtb/zarW/82q1v/NqtbXzarWKwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L3Lfti/937Yv/9+2L//fti//37Yv5t+2 + LxwAAAAAAAAAAD9q6gE/aupSP2rq5z9q6v89ZMH/N0wr/zdMK/83TCv/RFQr/2Nxbmonzfs8JtD7+ybQ + +/8m0Pv/JtD7/ybQ+40AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1m7Nqtb4zarW/82q + 1v/Nqtb/zarW/82q1ubNqtY2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lznfti/z37Yv/9+2 + L//fti//37Yv+N+2L00AAAAAAAAAAD9q6g4/auqSP2rq/z9q6v8/aur/PF2Z/zdMK/83TCv/ZGgs/8uq + L/7fti8SJtD7oCbQ+/8m0Pv/JtD7/ybQ+/Qm0PstAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADNqtYCzarWXc2q1vTNqtb/zarW/82q1v/Nqtb/zarW682q1kMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + Lw3fti/W37Yv/9+2L//fti//37Yv/9+2L43fti8CAAAAAD9q6i8/aurKP2rq/j9q6v8/aur/P2rq/zpY + d/89UCv/jIIt/9myL//fti/cQMzeHibQ++sm0Pv/JtD7/ybQ+/8m0Pu6JtD7AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gHNqtZNzarW8s2q1v/Nqtb/zarW/82q1v/NqtbwzarWUM2q + 1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2LwTfti+Y37Yv/9+2L//fti//37Yv/9+2L8nfti8PP2rqBD9q6mI/aursP2rq/z9q + 6v8/aur/P2rq/z9p5v9NX1b/spou/961L//fti//37YvrifQ+m8m0Pv/JtD7/ybQ+/8m0Pv+JtD7VgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1kDNqtbtzarW/82q + 1v/Nqtb/zarW/82q1vfNqtZeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti9V37Yv+9+2L//fti//37Yv/9+2L/Lfti8sP2rqET9q + 6p4/aur+P2rq/z9q6v8/aur/P2rq/z9q6v9Lb9m+zaw299+2L//fti//37Yv/9y2MoEm0PvUJtD7/ybQ + +/8m0Pv/JtD73ybQ+xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAzarWNM2q1t7Nqtb/zarW/82q1v/Nqtb/zarW/M2q1nHNqtYCAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8m37Yv6N+2L//fti//37Yv/9+2 + L/3fti9oQGrpOj9q6tU/aur/P2rq/z9q6v8/aur/P2rq/z9q6vI/aupv0rA+Mt+2L//fti//37Yv/9+2 + L/6LwouOJtD7/SbQ+/8m0Pv/JtD7/ybQ+4UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNqtYtzarW1c2q1v/Nqtb/zarW/82q1v/Nqtb6zarWg82q + 1gUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8J37Yvvd+2 + L//fti//37Yv/9+2L//ZszawRGzkdD9q6vM/aur/P2rq/z9q6v8/aur/P2rq/z9q6tQ/auo5P2rqAd+2 + L2Lfti//37Yv/9+2L//etjD3RsvWxCbQ+/8m0Pv/JtD7/ybQ+/km0PsjAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1iDNqtbQzarW/82q + 1v/Nqtb/zarW/82q1vzNqtaRzarWBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve9+2L/3fti//37Yv/9+2L//NrT/wVHTRxT9q6v4/aur/P2rq/z9q6v8/aur/P2rq/D9q + 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 + Lv8hlC7/JcPP/ybQ++wm0PsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADfti94wrEv/ymVLv8hlC7/IZUx/ybO9P8m0PukJtD7AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37Yvp3ukLv8hlC7/IZQu/yKfUv8m0Pr+JtD7PwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvCNe1L9Ezly7/IZQu/yGULv8jqnr/JtD7zibQ + +wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lxmdqi/0IZQu/yGU + Lv8hlC7/JLWg/ybQ+20AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADUtC87V54u/yGULv8hlC7/IZUw/yXAw/Qm0PsRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAk6kvbyeVLv8hlC7/IZQu/yGXOf8lxdegAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWeOqshlC7/IZQu/yGULv8inEj6Js/4PQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAznEDQIZQu/yGU + Lv8hlC7/J6BT0CbQ+wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAOaBHnCGULv8hlC7/IpQv/zmhTHEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKrYQ44n0aRI5UwtT2hS35suH0JAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////////////////////////8f/// + //////g/////////wB////////4AH///////+AAf///////gAB///////wAAD//////8AAAP/////+AA + AA//////gAAAD/////4AAgAH////8AAMAAf////AAHgAA////gAB+AAD///4AAfwAAP//8AAP+AAA/// + AAD/4ACB//wAB//AQAH/4AAf/wAAAf+AAPAAAAAB/gAAAAAAAAHwAAAAAAAAAcAAAAAAAAADgAAAAAAA + gAOAAAAACA+AA4AAAA/wDgAHgAB//+AcAAfAH///4DAAD/AP///AYAAP8Af//4BAAA/4Af//AAAAH/4B + //8AAAAf/wB//gAAAD//gD/8AAAAP//AH/wABgA//+AP+AAOAH//8AfwADwAf//4A+AAfAD///wB4AD8 + AP///gDAA/wB////AAAH/AH///+AAA/4Af///+AAP/gD////8AB/+AP////4AP/4A/////wD//AH//// + /gf/8Af/////H//wD/////////AP////////8A/////////wH////////+Af////////4D/////////g + P////////+B/////////4H/////////gf////////+D/////////4P////////////////////////// + ////////iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAgAElEQVR42u29eZRk133f97n3 + bbV19To9K2YADDAABrsAgYsIQhRNygSphZJNndhiZFG2adOREx2dJMfSsSImIn0sKUpkRfQRHZ1IpkxZ + NBcllEGJMSlCYkhQIIhlsGOAGcz0LD29d+1vub/88bp7umd6qa6uXqrqfs9pDKan3qt6r97ve7/f3/3d + 3wULCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC4uOgLK3wKLT + 8S8e+6hcKF9iqjbD+dIYAFppsm6WG4qHGc4M8W8/+Mf2WV8F2t4Ci07H67NnmaxNU4kqS78TEWITM1uf + 41LlMh/8o3eLvVNWAVh0ET702R+WWlznzflz649yC2rgrpGT/Luf/Jx95i0BWHQ6fuqz75Op+hRTtWkS + SZp40BU5L0efX+CrP/uEfe6tBbDoZEzXp6lE1aaCH0AQwiSkkTT4+f/7p60dsARg0an4n7/638lUfWqF + 528GkYmITcx8WLI30RKARadirHyxZfdaCss8M37K3kRLABadilpcR6R1FS9YB2AJwKJj0YjDLZ/jN/7s + EcsClgAsehVxPMW/e+wesQRgYdFhyHqZLZ9jLkxnBCwBWFh0GHw3j1Jbm8qfjxLiJLYEYB8ni05D5J1E + tljDdr4u1AxEz5wQSwAWFh2EP/jALyuji6BatwIToVBN7L107S2w6ESIsw9QENdbOr6aQN3Q8yRgFYBF + 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 + GBgsBv8PNAlKsBafvBgAAAAASUVORK5CYII= + + \ No newline at end of file diff --git a/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs b/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs index 4722774d..9bc11b83 100644 --- a/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs @@ -28,6 +28,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(GenericDeviceDialog)); this.propertyGrid = new System.Windows.Forms.PropertyGrid(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.buttonCancel = new System.Windows.Forms.Button(); @@ -42,9 +43,9 @@ private void InitializeComponent() // this.propertyGrid.Dock = System.Windows.Forms.DockStyle.Fill; this.propertyGrid.Location = new System.Drawing.Point(0, 0); - this.propertyGrid.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.propertyGrid.Margin = new System.Windows.Forms.Padding(2); this.propertyGrid.Name = "propertyGrid"; - this.propertyGrid.Size = new System.Drawing.Size(252, 345); + this.propertyGrid.Size = new System.Drawing.Size(252, 347); this.propertyGrid.TabIndex = 0; // // splitContainer1 @@ -52,7 +53,7 @@ private void InitializeComponent() 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.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(2); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -65,7 +66,7 @@ private void InitializeComponent() this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); this.splitContainer1.Panel2.Controls.Add(this.buttonOK); this.splitContainer1.Size = new System.Drawing.Size(252, 386); - this.splitContainer1.SplitterDistance = 345; + this.splitContainer1.SplitterDistance = 347; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 1; // @@ -74,7 +75,7 @@ private void InitializeComponent() 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(135, 5); - this.buttonCancel.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(108, 25); this.buttonCancel.TabIndex = 6; @@ -85,7 +86,7 @@ private void InitializeComponent() // this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonOK.Location = new System.Drawing.Point(13, 5); - this.buttonOK.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonOK.Margin = new System.Windows.Forms.Padding(2); this.buttonOK.Name = "buttonOK"; this.buttonOK.Size = new System.Drawing.Size(108, 25); this.buttonOK.TabIndex = 5; @@ -99,7 +100,8 @@ private void InitializeComponent() this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(252, 386); this.Controls.Add(this.splitContainer1); - this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Margin = new System.Windows.Forms.Padding(2); this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "GenericDeviceDialog"; diff --git a/OpenEphys.Onix1.Design/GenericDeviceDialog.resx b/OpenEphys.Onix1.Design/GenericDeviceDialog.resx index 1af7de15..23badadd 100644 --- a/OpenEphys.Onix1.Design/GenericDeviceDialog.resx +++ b/OpenEphys.Onix1.Design/GenericDeviceDialog.resx @@ -117,4 +117,1659 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + AAABAA4AEBAQAAEABAAoAQAA5gAAABAQAAABAAgAaAUAAA4CAAAQEAAAAQAgAGgEAAB2BwAAICAQAAEA + BADoAgAA3gsAACAgAAABAAgAqAgAAMYOAAAgIAAAAQAgAKgQAABuFwAAMDAQAAEABABoBgAAFigAADAw + AAABAAgAqA4AAH4uAAAwMAAAAQAYAKgcAAAmPQAAMDAAAAEAIACoJQAAzlkAAEBAAAABABgAKDIAAHZ/ + AABAQAAAAQAgAChCAACesQAAAAAAAAEAGABpMQAAxvMAAAAAAAABACAAZ10AAC8lAQAoAAAAEAAAACAA + AAABAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAgAAAgICAAACAgADAwMAA//8AAAD/ + /wAAAP8A/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUiAAAAAAAFU5YAAA + AAVVADYgAAAFVQAGYlAAVTUAVTUjMAM1VVVTIDOABVAAAAYDRHAAVQAAYzhlAAAFUAY4AFcAAABVA0AA + NwAAAAUDAAZAAAAAAAAAA0AAAAAAAAACAAAAAAAAAAEAAAAAAAAAAAAA//8AAP/xAAD/wQAA/jEAAPjh + AADDAAAAgBEAAJ+hAADPAwAA5jMAAPJzAAD45wAA/+cAAP/vAAD/7wAA//8AACgAAAAQAAAAIAAAAAEA + CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI5YzAEWaLgCFpi8AJLioAL2exQA9QTAAO12VANK0 + LwAws5AAzarWAGZsPgA+Z9UAdLRpACbQ+wDdtS8Ak41jAEBr6QDAtkIA37YvAKmdbgA/auoAR27gANKu + MgA8zeMAu5vDAMiq0ABLXUwATaKmAHpvfACWlJUAqqmpALe2tgDGr2QAxKAqAHt6RQA+XowAHJXwALW0 + tACysbEAtLOzALe1swCrjzcAsp5eAK2ecQCQgU8ALk93AA09OADctDEAzqw0AN61LgDbrikA3bMsAMWs + XgC/gxAA1qYjALazrQCMYSEAnGUNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg5 + OgAAAAAAAAAAAAAAICA1NjcAAAAAAAAAACAgIAAAMjM0AAAAAAAAICAgAAAAExMwMQAAACYnKCAAACAp + KissLS4vAB0eHyAgICAgISIAIyQlAAAZGgAAAAAAABMAFRscDgAAAAoKAAAAABMUFRYXGAAAAAAACgoA + AA8QEQAAEg4AAAAAAAAKCgALDAAAAA0OAAAAAAAAAAUGBwAAAAgJAAAAAAAAAAAAAAAAAAADBAAAAAAA + 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 + ABMAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + //////+H///+B///+AP//8AD//8HA//4HgP/4HwB/wP8ifwP+AHwAAABgAABAYAH5wOH/8YD4/+MB/H/ + kAf4/wEH/H4DD/48Dg//HB4f/4h+H//A/h//4f4///f+P////H////x////8f////P////z///////// + //8oAAAAIAAAAEAAAAABAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULgAmmTwAIpQuACGX + NwBNnC4AIZQvACW/wACPqC8AJLmsANS0LwAxly4AI6+JAD5CNwB1oy4AIqNhACbQ+wCchaAALDMkAC02 + KQA8X6cAwbEvACOZPgAmz/cAzarWAMmn0QBmWUwAM0MoADhQRQA+aN0A37YvAFKdLgAlyN8Ano0tAEFS + KwA6V3AAP2rpAJ+qLwAkvLcAxqYuAFliLAA8X6QAP2rqANm1LwA1tJAA2bIvAIF8OAA/Z9AAfLVpAKyb + VgBDbOUAxbZAACbQ+gDCqFEAPszgAEFr5gCrlzoAf8OZADpUWgCAei0AzLE5AIhxjgCkiKsAt5e/AMKn + yQA6WX8AN0wrAEFXUAAsuPcAaVZtAHZpeQCbmZoAsK+vALe2tgBWaGgAPE8rADpt6QAcpPMAdGJ3AIB2 + ggCKiYkAhoWFAIWEhACTkpIAqKenALa1tQCynl4AsI8lALSSJgCxp4oAwLKGAKqRKwBIVSwAPWXVABlb + 5AAUg+0AJMf5ALOysgCrqqoArq2tALazrwCkizoAoIIiAKSMPgC2tLEAoYQpAJl9IgBZZocAIkmpAAY3 + aAALRk0Au5goALaVJwC4pGMAp44+AKKEIgCsnnMAgIqoABAyJAAGLB4A3LMuANayOgC0mSoAmIsqAN60 + LQDftS8A3rQuANOgHwDarSgA2aomANK0VADetS8AyY0RAMyTFgCwlUEAtYMTALJqBQDNlRcAs62dAJdy + HAB9RAEAo2kMAKSQawB6QgEAgUoFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASY6PkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASUmKi4yNAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASUlJaIaHiIkeAAAAAAAAAAAAAAAAAAAAAAAAAABJSUlJSUmCg4SFfx4AAAAA + AAAAAAAAAAAAAAAAAABJSUlJSQAAAB58f4CBHgAAAAAAAAAAAAAAAAAAAElJSUlJSQAAAAAeHnx9Hn4e + AAAAAAAAAAAAAAAAAElJSUlJSQAAAAAAHh4eHh4eHh4eAAAAAAAAAAAASUlJSUlJAAAAAAAAAAAeHgAe + eHkAensAAAAAAAAASUlJSUlJAAAAAAAAAAAAb3BxSXJzdHV2dwAAAAAAVWFIYmNVSUlJSUlJSUlJZGVm + Z2hoaWprbG1uAABOT1BRUlNUVUlJSUlJSUlJSUlWV1hZAFpbXF1eX2AAAEVFRkdISUlJSUlJSQAAAAAA + AB4eAAAASkscTE0QAAAAPT4/QAAAAAAAAAAAAAAAAAAeHh4AACpBQkNEEBAAAAAAABgYGAAAAAAAAAAA + AAAAHh4eAAAqKjo7PBAQAAAAAAAAABgYGAAAAAAAAAAAAAAeHgAqKio3OB45EBAAAAAAAAAAABgYGAAA + AAAAAAAAHh41KioqKgAeHjYQEAAAAAAAAAAAABgYGAAAAAAAAB4eMTIqKioAAB4zNBAAAAAAAAAAAAAA + ABgYGAAAAAAeLS4vKioAAAAeHjAQEAAAAAAAAAAAAAAAABgYGAAAACcoKSoqAAAAAB4rLBAAAAAAAAAA + AAAAAAAAABgYGAAhIiMkAAAAAAAAHiUmEAAAAAAAAAAAAAAAAAAAABgZGhscHQAAAAAAAAAeHyAQAAAA + AAAAAAAAAAAAAAAAABESExQAAAAAAAAAABUWFwAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAADg8Q + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoLDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + CAEJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////// + /4f///4H///4A///wAP//wcD//geA//gfAH/A/yJ/A/4AfAAAAGAAAEBgAfnA4f/xgPj/4wH8f+QB/j/ + AQf8fgMP/jwOD/8cHh//iH4f/8D+H//h/j//9/4////8f////H////x////8/////P///////////ygA + AAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsqqXDIxaEWuPWw1DAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AADwf//DAI8AAPg//4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/w + eAfgfwAA//gwH+B/AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/ + AAD/////g/8AAP////+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP// + /////wAA////////AAD///////8AACgAAAAwAAAAYAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIZQuAC2cQQA4nT8AI5xHACSVLgAhlzgAUp0uACGULwAlwMQAlqkvACS4qADXtS8AMZcuACOs + gQAm0PsA37YvAHmkLgAioVoAJtD6AMCwLwAmlS4AJs71AD1BNwA7QDQAVJ0uACXF1wCJd4sALDMkAC44 + LwA8YbUAoqsvACS6rwDNqtYAq5CwAC81KAAvOyYAOFJRAD9p4gDYtS8AN5guACOviQCbfXkAQEMmADZL + KwA3TCsAOll/AD9q6QB9pC4AIqNgAMypzQDHpS8AaGssADhMKwA3TC0APGGyAD9q6gDGsi8AJptCACbN + 8gDbtC8AkoYtADtPKwA4T0AAPmfWAFieMAAlx9wAu58uAFBbLAA5VWIAP2roAKWsLwAlvLcA0q4vAHFx + LQA8XZUA2bUvAD61jwDetS8Am4w1AEVpxACCtmkAwKZJAEpv3QDItj0AJ9D5ANKwPgBAaukA3rYvAELM + 3ACFw5IAP2rmAHV3SADUry8A2bc2ADpWaABTXiwAvaAuADtbjQA9UCsAioEvAIBqhQCKcpAAoISnAK+R + tgC7ocEAPWGxADhNLQA+ZtMAKcP5AHdlewBpVm0AeWp8AKGeoAC0s7MAt7a2ADlOLQA9YrkANHLrAB+y + 9gB2ZHoAcmV0AIKBgQCDgoIAh4aGAJqZmQCura0AyrFhANCpLADUrS0A2bEuANy0LgCymS4ATVorADtc + kgA+auoAElznABiU8QAlzfsAeWh8AIh+iQCRkJAAjYyMAImIiACGhYUAhYSEAISDgwCmpaUAtbS0ALe1 + tACnkU0AoYMiAKKEIgCkhSMArqB3AMCjRQC4mCkAXmEoADdSaQA9ZuIAJl/kAAlV5AARdusAIsH4ALGw + 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 + AAAAAAAAAAAAAAAAAAAAAAAAISEhISEAAAAAEENERUY4ODgAAAAAAAAQEEdIDw8AAAAAAAAAAAAAAAAA + AAAAAAAAACEhISEhAAA8PT4/QDg4AAAAAAAAAAAQEEFCDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAhISEh + MjM0NTY3ODgAAAAAAAAAAAAQOTo7DwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISEhKissLS4vAAAAAAAA + AAAAABAQMDEPDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiIxwkJSYAAAAAAAAAAAAAABAnKCkPDwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbHBwdHgAAAAAAAAAAAAAAABAfASAPAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAFxgAAAAAAAAAAAAAAAAAABAZARoPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAABQVBhYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAEBEBEhMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA0BDg8AAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgEBCwAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwEICQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAABQEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAADAQEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////AAD///////8AAP//////nwAA//////4f + AAD/////8A8AAP/////ADwAA/////gAPAAD////4AAcAAP///+AQBwAA////AHAHAAD///wD4AcAAP// + 4A/AAwAA//+AP8AjAAD//AH/hCMAAP/wB/8MIQAA/8A//gABAAD+AP4AAAEAAPgAAAAAAQAAwAAAAAAD + AACAAAAQeAMAAIAA//D4BwAAwf//4eAHAADg///DwIcAAPB//8MAjwAA+D//hgAPAAD8H/8MEB8AAP4P + /wAwHwAA/wf+AHA/AAD/w/wB4D8AAP/h+APgPwAA//B4B+B/AAD/+DAf4H8AAP/8AD/g/wAA//4A/8D/ + AAD//wH/wP8AAP//g//B/wAA///P/8H/AAD/////w/8AAP////+D/wAA/////4P/AAD/////h/8AAP// + //+H/wAA/////4//AAD/////D/8AAP////+P/wAA////////AAD///////8AAP///////wAAKAAAADAA + AABgAAAAAQAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIpWC49bDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKdLiGULiGULyXAxAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + ACSVLiGULiGXOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAADidPyGULiGULiOcRwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULiGULi2cQQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////wAA//////// + AAD//////58AAP/////+HwAA//////APAAD/////wA8AAP////4ADwAA////+AAHAAD////gEAcAAP// + /wBwBwAA///8A+AHAAD//+APwAMAAP//gD/AIwAA//wB/4QjAAD/8Af/DCEAAP/AP/4AAQAA/gD+AAAB + AAD4AAAAAAEAAMAAAAAAAwAAgAAAEHgDAACAAP/w+AcAAMH//+HgBwAA4P//w8CHAADwf//DAI8AAPg/ + /4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/weAfgfwAA//gwH+B/ + AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/AAD/////g/8AAP// + //+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP///////wAA//////// + AAD///////8AACgAAAAwAAAAYAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + ogYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYyt7a2mre2tu+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tvm3traqt7a2Q7e2tgO3trYIt7a2ILe2tji3trZPt7a2aLe2toC3traXt7a2sLe2tsm3trbat7a27Le2 + tvqwp4v/oIIj/6CCIv+ggiL/pY5D/7a0sf+3trb/r6WG/6CCIv+ggiL/oYUq/5udpv9EXqr/FD1//wYs + Hv8GLB7/DDQp7WSTmw0AAAAAAAAAAAAAAAC3trYNt7a2XLe2tsa3trb+tLOz/7GwsP+ura3/qaio/6Sj + o/+mpaX/srGx9Le2tsC3tra/t7a21be2tu+3trb+t7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7Swpf+ihzH/oIIi/6CCIv+hhSv/tK+h/7e2tv+3trb/q5to/6CCIv+fgSL/bWU3/zJQ + p/8qTKr/CECp/wY5bf8JQ1b/GXyMigAAAAAAAAAAkoKVGXlofJOIfonkkZCQ/42MjP+JiIj/hoWF/4WE + hP+Eg4P/kZCQ/6alpf+1tLT/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7W0/6eRTf+hgyL/ooQi/6SFI/6uoHfgt7a2xLe2tq63traWwKNF4riY + Kf9eYSj/N1Jp/z1m4v8mX+T/CVXk/xF26/8iwfj5JtD7JgAAAAAAAAAAdmR6sWlWbf9pVm3/cmV0/4KB + gf+DgoL/h4aG/5qZmf+ura3/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb5t7a25be2ts23tra3t7a2n7e2toe3trZvyrFhudCpLP/UrS3/2bEu/9y0LqTfti8CAAAAAAAA + AAAAAAAAspku501aK/83TCv/O1yS/z5q6v8SXOf/GJTx/yXN+/8m0Pu4JtD7AgAAAAAAAAAAd2V7xmlW + bf9pVm3/aVZt/3lqfP+hnqD/tLOz/7e2tv+3trb+t7a287e2tua3trbYt7a2wLe2tqm3traQt7a2eLe2 + tmG3trZIt7a2Mbe2thi3trYGt7a2A7e2tgEAAAAAAAAAAAAAAADfti8z37Yv99+2L//fti//37Yv2d+2 + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2LwHXtS/DMZcu/yGU + Lv8jrIH/JtD7oQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + LwyWqS/qIZQu/yGULv8kuKj/JtD7OgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANW0LydSnS7/IZQu/yGUL/8lwMTOJtD7AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIClL1kklS7/IZQu/yGXOP8lx9xqAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADidP5QhlC7/IZQu/yOcR/Am0PsUAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADefRX8hlC7/IZQu/y2c + QaEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKr + YQgxnD9zLZo6fFeuZhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP///////wAA//////4PAAD/////+A8AAP// + ///ADwAA/////wAHAAD////4AAcAAP///+AABwAA////gAAHAAD///wAAAMAAP//8ADAAwAA//+AA4AD + AAD//gAPgAEAAP/4AH8AAQAA/8AD/gABAAD/AA/gAAAAAPwAAAAAAAAA4AAAAAABAACAAAAAAAEAAIAA + AAA4AQAAgAAA4GADAACAB//AwAMAAMB//8GABwAA4D//gAAHAADwH/8AAA8AAPgP/gAADwAA/Af+AAAP + AAD+A/wAIB8AAP8A+ADgHwAA/4BwAcA/AAD/4DADwD8AAP/wAA/APwAA//gAH8B/AAD//AA/wH8AAP/+ + AP/A/wAA//8B/4D/AAD//4P/gP8AAP//z/+B/wAA/////4H/AAD/////A/8AAP////8D/wAA/////wP/ + AAD/////B/8AAP////8H/wAA/////w//AAD/////D/8AAP///////wAA////////AAAoAAAAQAAAAIAA + AAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjV0YiVQKi1YLAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2n4Ve + ekIBekIBekIBh1IIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2squWjWEUekIBekIBekIBiVQJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAt7a2t7a2t7a2t7a2trOupIs9i14LekIBekIBekIBpG8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7W1qZddn4EhjF0HfEUBhUgBnloDz5kb37YvAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2r6SCoIMkoX8eomwIqGMErF8Cv3wI1qUj + 37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2tK+grpAzw58pzp0gw4IJ + unQGuXMGw4IJ268q37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uLaz0LBH + 3bQv37Yv0Joaw4IJyY0SxIMKxocN3rQu37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2AAAAAAAA37Yv37Yv37UvypATx4kO3LAryY4Ry5AT37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2AAAAAAAAAAAAAAAA37Yv37Yv37Yv3rQtxogO2Kgl37Yv0Zwc0Joa37Yv37Yv37YvAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv27Aq0Jsb37Yv37Yv2asn1aMh37Yv + 37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv264p3rQu37Yv + 37Yv3rUu268q37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv + 37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37Yv + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAA37Yv37Yv37Yv37YvAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA3rUv2rIu1q8t + 0qwuAAAAAAAAv6hesJMnYWclOlIjdXcnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA168t1a4t0KosyqUqxKAqw61ot7a2t7a2 + r51kp4gjpYYjo4Ujo4gvtbGpt7a2l5iXLEAjBiweBiweBy0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7WzqZNLpIUjoIIioIIioIIi + ppBKtrSwt7a2t7a2qJNToIIioIIioIIipIw+tbS1gIywMlCkCjAzBiweBiweBiweHkc/AAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2rqF6oIIi + oIIioIIioIIioocvtK+it7a2t7a2t7W0o4k2oIIioIIioIIino5aXXGsLUynHkamBjRgBiweBiweBi0g + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2tLOzsrGxrq2tq6qqp6amoqGhnJubnp2dq6qq + tLOzt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2sqyYoYUroIIioIIioIIioIIjr6WFt7a2t7a2t7a2tLCkoYUqoIIioIIigm8hPE5uLUynLk6rD0Os + BkGrBzt4CEBgF4SdAAAAAAAAAAAAAAAAAAAAg3SGkImRmZeYlZSUkI+Pi4qKiYiIh4aGhYSEg4KChIOD + j46OpKOjs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2trOtpY1CoIIioIIioIIioIIiqZZct7a1t7a2t7a2t7a2t6uHuZcouZgoeXEoN0gn + OV2zPGbgMWPiClXiCVXkDmzqILf3Js/7AAAAAAAAAAAAAAAAcmB2aVZtaVZtcmV0goCBg4KCg4KCg4KC + g4KChoWFmJeXrKurtrW1t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uKNitpUnvJkowp4pyKMqyqUsAAAAAAAAAAAAAAAAAAAAAAAA + zasvamwsOEwrN042PmfYP2rqH17oCVjmFIXuJMf6JtD7AAAAAAAAAAAAAAAAAAAAaVZtaVZtaVZtaVZt + cGJzgX+Bg4KCjIuLoJ+fs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA + AAAAAAAAAAAAlIczSlgrN0wrN0wrOFFOP2nkO2nqD2DoHKT0Js/7JtD7JtD7AAAAAAAAAAAAAAAAAAAA + a1hvaVZtaVZtaVZta1hvfWyApqKltbS0t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv + AAAAAAAAAAAAAAAAAAAAAAAAP2rqOVFIN0wrN0wrN0wrOlduP2rqLnrsIbz4JtD7JtD7JtD7AAAAAAAA + AAAAAAAAAAAAAAAAfmqBcV12dF95hm+Mmn+hpomts5q5u7K9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv + 37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAP2rqP2nhN0wuN0wrN0wrN0wrO1yQPm/rJ8n6JtD7JtD7 + JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAwqHLyafSzarWzarWzarWzarWzarWAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAP2rqP2rqPWTBN0wrN0wrN0wrRFQrAAAA + AAAAJtD7JtD7JtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWzarW + zarWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAP2rqP2rqP2rqP2rqPF2ZN0wr + N0wrZGgsy6ovAAAAJtD7JtD7JtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + zarWzarWzarWzarWzarWzarWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAAP2rqP2rqP2rq + P2rqP2rqOlh3PVArjIIt2bIv37YvAAAAJtD7JtD7JtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWzarWzarWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA + P2rqP2rqP2rqP2rqP2rqP2nmTV9Wspou3rUv37Yv37YvAAAAJtD7JtD7JtD7JtD7AAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWzarWzarWAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv + AAAAAAAAP2rqP2rqP2rqP2rqP2rqP2rqP2rqS2/Zzaw237Yv37Yv37Yv3LYyJtD7JtD7JtD7JtD7JtD7 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWzarW + zarWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv + 37Yv37Yv37YvAAAAAAAAP2rqP2rqP2rqP2rqP2rqP2rqP2rqAAAAAAAA37Yv37Yv37Yv37Yvi8KLJtD7 + JtD7JtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + zarWzarWzarWzarWzarWzarWzarWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAA37Yv37Yv37Yv37Yv37Yv2bM2AAAAP2rqP2rqP2rqP2rqP2rqP2rqP2rqAAAAAAAAAAAA37Yv37Yv + 37Yv3rYwRsvWJtD7JtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWzarWzarWzarWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yvza0/VHTRP2rqP2rqP2rqP2rqP2rqP2rqP2rqAAAAAAAA + AAAA37Yv37Yv37Yv37Yvy7Y9KM/4JtD7JtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWzarWzarWzarWAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yvs5szUm+2P2rqP2rqP2rqP2rqP2rqP2rq + AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37YvibZlJtD7JtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWzarW + zarWzarWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv2bIvjIEtP12JP2rpP2rqP2rq + P2rqP2rqP2rqAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv17UvRbaPJtD7JtD7JtD7JtD7AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + zarWzarWzarWzarWzarWzarWzarWAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37YvyKcuZWksOVNX + P2nkP2rqP2rqP2rqP2rqP2rqAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37YvrK0vJry2JtD7JtD7 + JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWzarWzarWzarWAAAAAAAAAAAAAAAAAAAAAAAA37Yv3rUv + rJYuSVcrOE45PmXLP2rqP2rqP2rqP2rqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv + YKE0JcbYJtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWzarWzarWzarWAAAAAAAAAAAA + AAAA37Yv2LIvg3wtOE0rN0wuPF+iP2rqP2rqP2rqP2rqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 37Yv37Yv37YvxrIvK51FJszuJtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarW + zarWzarWAAAAAAAA37YvwaMuXmQsN0wrN0wrOlh0P2roP2rqP2rqP2rqAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAA37Yv37Yv37Yvg6YvIqNgJtD7JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAzarWzarWzarWzarWzarWzarV0ahRoY8tRlUrN0wrN0wrOFFJPmfaP2rqP2rqAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv3bYvO5kuI66HJtD7JtD7JtD7AAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAzarWzarWzarWzarWv5mlZlQrNkYpN0wrN0wrN00uPWO/P2rqP2rqAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37YvqawvIZQuJLquJtD7JtD7AAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzarWzarWvJ3ESEU7LDMkLDQkNEUpN0wsO1yP + P2rqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv3bYvXJ4uIZQu + JcXXJtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzarWhXOGLDMkLDMk + LDMkLjkmOVRfP2niAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv + 37YvxbEvKJUuIZlAJsztJtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAe2x8LDMkLDMkLDMkLzs3PWO/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAA37Yv37YvgaUvIZQuIqFcJs/3JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAOz80LDMkMzgrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv1LQvPZkuIZQuI6x/JtD7JtD7AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvoqsvIpQuIZQuJLioJtD7AAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3rYvV54uIZQuIZQuJcPP + JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwrEv + KZUuIZQuIZUxJs70JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve6QuIZQuIZQuIp9SJtD6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA17UvM5cuIZQuIZQuI6p6JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnaovIZQuIZQuIZQuJLWgAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV54uIZQuIZQuIZUwJcDDAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ5UuIZQu + IZQuIZc5JcXXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAARZ46IZQuIZQuIZQuIpxIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM5xAIZQuIZQuIZQuJ6BTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOaBHIZQuIZQuIpQvAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOJ9GI5UwAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/////////////////////////////////////////H/////////wP/// + /////4A////////+AD////////gAH///////wAAf//////8AAB//////+AAAD//////gAYAP/////4AP + AA/////8AD4AD/////AB/AAH////gAf8AAf///4AP/ggh///+AD/8CCD///AA//gYYP//wAf/+Dhg//4 + AH//wAAD/+AD//gAAAH/AA/AAAAAA/wAAAAAAAAD4AAAAAAAAAPAAAAAAA/AB8AAAAP8H4AHwAAP//g/ + AA/AP///8D4AD+A////wfAYP+B///+DwBB/8D///wOAEH/4H//+BwAQ//wP//4MAAD//gf//BgMAP//A + f/4EBwB//+A//gAOAH//8B/8AD4A///4D/gAfgD///wH8AD+Af///gPwA/4B////AeAH/AH////AwA/8 + A////+AAP/wD////8AB//Af////4Af/8B/////wD//gH/////gf/+A//////H//4D/////////gf//// + ////+B/////////4H/////////A/////////8D/////////wf/////////B/////////8H/////////g + /////////+D/////////4f/////////z//////////////////////////////////8oAAAAQAAAAIAA + AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKpUACs4ckEMObMAgAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyqpcwjV0YpYlU + CvSLVgvSmWcTMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dre2 + tli3trbBn4Ve/HpCAf96QgH/ekIB/4dSCM3FnzkGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYlt7a2g7e2tt63trb9squW/41hFP96QgH/ekIB/3pCAf+JVAn4yZ0oIwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2Bre2tkW3trast7a28re2tv+3trb/trOu/6SLPf+LXgv/ekIB/3pCAf96QgH/pG8P/9+2 + L1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYVt7a2b7e2ttW3trb+t7a2/7e2tv+3trb/t7W1/6mXXf+fgSH/jF0H/3xF + Af+FSAH/nloD/8+ZG//fti+iAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2BLe2tjm3trabt7a277e2tv+3trb/t7a2/7e2tv+3trb/t7a2/6+k + gv+ggyT/oX8e/6JsCP+oYwT/rF8C/798CP/WpSP/37Yv4N+2LwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYSt7a2Xre2tsO3trb4t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7SvoP+ukDP/w58p/86dIP/Dggn/unQG/7lzBv/Dggn/268q/9+2L/vfti88AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Are2tiS3traJt7a24re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2+Li2s7TQsEfr3bQv/9+2L//Qmhr/w4IJ/8mNEv/Egwr/xocN/960 + Lv/fti//37YvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2T7e2trO3trb6t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbgt7a2g7m2sSLfti9937Yv/9+2L//ftS//ypAT/8eJ + Dv/csCv/yY4R/8uQE//fti//37Yv/9+2L8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tiG3trZ2t7a22be2 + tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a297e2try3trZat7a2DAAAAADfti8/37Yv9t+2 + L//fti//3rQt/8aIDv/YqCX/37Yv/9GcHP/Qmhr/37Yv/9+2L//fti/637YvHAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2Obe2 + tqS3trbtt7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv63trbqt7a2lre2tjO3trYEAAAAAAAA + AADfti8W37Yv1d+2L//fti//37Yv/9uwKv/Qmxv/37Yv/9+2L//Zqyf/1aMh/9+2L//fti//37Yv/9+2 + L2MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgy3trZlt7a2xre2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2ttK3trZqt7a2FgAA + AAAAAAAAAAAAAAAAAADfti8C37Yvo9+2L//fti//37Yv/9+2L//brin23rQu/9+2L//fti//3rUu/duv + KvDfti//37Yv/9+2L//fti+p37YvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAC3trYBt7a2L7e2to+3trbut7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbyt7a2pre2 + tkC3trYBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvYt+2L/zfti//37Yv/9+2L//fti/p37YvrN+2 + L//fti//37Yv/9+2L/Dfti+m37Yv/9+2L//fti//37Yv6d+2Lw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAALe2tg63trZSt7a2u7e2tva3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/re2 + ttq3trZ8t7a2ILe2tgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvKN+2L+/fti//37Yv/9+2 + L//fti/837YvYN+2L7Xfti//37Yv/9+2L//fti/G37YvV9+2L/7fti//37Yv/9+2L//fti9DAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYCt7a2H7e2tn63trbZt7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb3t7a2ure2tlO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvDN+2 + L8Lfti//37Yv/9+2L//fti//37Yvod+2LxHfti/b37Yv/9+2L//fti//37YvlN+2Lxbfti/037Yv/9+2 + L//fti//37YvjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgK3trZBt7a2pre2tvS3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tuy3traPt7a2Lre2tgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2L4Xfti//37Yv/9+2L//fti//37Yv3d+2LxDfti8i37Yv9d+2L//fti//37Yv/9+2 + L2QAAAAA37Yvv9+2L//fti//37Yv/9+2L9Dfti8HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Fre2tmy3trbUt7a2/re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb9t7a2xbe2tmO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L0rfti/337Yv/9+2L//fti//37Yv9N+2Lz4AAAAA37YvS961 + L//asi7/1q8t/9KsLv3Esndyt7a2X7+oXrqwkyf/YWcl/zpSI/91dyf4sp0sKwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgS3trY0t7a2lre2tum3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tu23traht7a2O7e2tgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2CMasViTXry3f1a4t/9CqLP/KpSr/xKAq/8Ot + aNu3traft7a2tq+dZN+niCP/pYYj/6OFI/+jiC//tbGp/7e2tv+XmJf/LEAj/wYsHv8GLB7/By0e/yhO + On8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dbe2tli3tra/t7a2+Le2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb8t7a22re2tnW3trYeAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYFt7a2Cbe2tg23trYbt7a2Nbe2tky3trZlt7a2fLe2tpS3tratt7a2xLe1s9Spk0vspIUj/6CC + Iv+ggiL/oIIi/6aQSv+2tLD/t7a2/7e2tv+ok1P/oIIi/6CCIv+ggiL/pIw+/7W0tf+AjLD/MlCk/wow + M/8GLB7/Biwe/wYsHv8eRz+4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYjt7a2hLe2 + tuG3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tvu3trawt7a2Ure2tiC3trYvt7a2Rbe2 + tlq3trZ0t7a2i7e2tqK3tra6t7a2zbe2tuO3trb2t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+uoXr/oIIi/6CCIv+ggiL/oIIi/6KHL/+0r6L/t7a2/7e2tv+3tbT/o4k2/6CCIv+ggiL/oIIi/56O + Wv9dcaz/LUyn/x5Gpv8GNGD/Biwe/wYsHv8GLSD/G0xIcgAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Cbe2 + tkm3travt7a297e2tv+0s7P/srGx/66trf+rqqr/p6am/6Khof+cm5v/np2d/6uqqv60s7Pht7a25Le2 + tvW3trb4t7a2+7e2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+yrJj/oYUr/6CCIv+ggiL/oIIi/6CCI/+vpYX/t7a2/7e2tv+3trb/tLCk/6GF + Kv+ggiL/oIIi/4JvIf88Tm7/LUyn/y5Oq/8PQ6z/BkGr/wc7eP8IQGD/F4Sd4ym42xYAAAAAAAAAAAAA + AACTg5Ygg3SGgJCJkdOZl5j7lZSU/5CPj/+Lior/iYiI/4eGhv+FhIT/g4KC/4SDg/+Pjo7/pKOj/7Oy + sv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+2s63/pY1C/6CCIv+ggiL/oIIi/6CCIv+pllz6t7a18be2 + tui3trbgt7a2zberh825lyj/uZgo/3lxKP83SCf/OV2z/zxm4P8xY+L/ClXi/wlV5P8ObOr/ILf3/ybP + +5EAAAAAAAAAAAAAAACLeo4acmB23mlWbf9pVm3/cmV0/4KAgf+DgoL/g4KC/4OCgv+DgoL/hoWF/5iX + l/+sq6v/trW1/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/re2tvy3trb6t7a2+Le2tuy3trbWuKNi9baVJ/+8mSj/wp4p/8ij + Kv/KpSzUtaqGN7e2tiO3trYTt7a2BAAAAADfti90zasv/2psLP84TCv/N042/z5n2P8/aur/H17o/wlY + 5v8Uhe7/JMf6/ybQ+/wm0PsvAAAAAAAAAAAAAAAAjHqPcWlWbf9pVm3/aVZt/2lWbf9wYnP/gX+B/4OC + gv+Mi4v/oJ+f/7Oysv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb9t7a27Le2tta3trbDt7a2rre2tpa3trZ+t7a2Zre2tk+3trY4t7a2I7e2tg4AAAAA37YvTt+2 + L/zfti//37Yv/9+2L//fti/y37YvNwAAAAAAAAAAAAAAAAAAAAAAAAAAlIczp0pYK/83TCv/N0wr/zhR + Tv8/aeT/O2nq/w9g6P8cpPT/Js/7/ybQ+/8m0Pu9JtD7BAAAAAAAAAAAAAAAAI59kVVrWG/+aVZt/2lW + bf9pVm3/a1hv/31sgP+moqX/tbS0/7e2tv+3trb/t7a2/7e2tve3trbot7a227e2ts63tra3t7a2obe2 + toe3trZvt7a2Wbe2tkG3trYmt7a2Ebe2tgy3trYHt7a2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37YvH9+2L+Pfti//37Yv/9+2L//fti/+37YvcN+2LwEAAAAAAAAAAAAAAAA/auoMP2rqgzlR + SPs3TCv/N0wr/zdMK/86V27/P2rq/y567PchvPj/JtD7/ybQ+/8m0Pv+JtD7WQAAAAAAAAAAAAAAAAAA + AACjk6UJfmqBoHFddvx0X3n/hm+M/5p/of+mia3/s5q5/ruyvZu3trZht7a2Sre2tjG3trYit7a2F7e2 + tg23trYDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37YvCN+2L7Lfti//37Yv/9+2L//fti//37Yvst+2LwcAAAAAAAAAAAAA + AAA/auonP2rqvD9p4f43TC7/N0wr/zdMK/83TCv/O1yQ+D5v65YnyfrTJtD7/ybQ+/8m0Pv/JtD76CbQ + +wsAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gPCocuAyafS/c2q1v/Nqtb/zarW/82q1v/NqtbXzarWKwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L3Lfti/937Yv/9+2L//fti//37Yv5t+2 + LxwAAAAAAAAAAD9q6gE/aupSP2rq5z9q6v89ZMH/N0wr/zdMK/83TCv/RFQr/2Nxbmonzfs8JtD7+ybQ + +/8m0Pv/JtD7/ybQ+40AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1m7Nqtb4zarW/82q + 1v/Nqtb/zarW/82q1ubNqtY2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lznfti/z37Yv/9+2 + L//fti//37Yv+N+2L00AAAAAAAAAAD9q6g4/auqSP2rq/z9q6v8/aur/PF2Z/zdMK/83TCv/ZGgs/8uq + L/7fti8SJtD7oCbQ+/8m0Pv/JtD7/ybQ+/Qm0PstAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADNqtYCzarWXc2q1vTNqtb/zarW/82q1v/Nqtb/zarW682q1kMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + Lw3fti/W37Yv/9+2L//fti//37Yv/9+2L43fti8CAAAAAD9q6i8/aurKP2rq/j9q6v8/aur/P2rq/zpY + d/89UCv/jIIt/9myL//fti/cQMzeHibQ++sm0Pv/JtD7/ybQ+/8m0Pu6JtD7AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gHNqtZNzarW8s2q1v/Nqtb/zarW/82q1v/NqtbwzarWUM2q + 1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2LwTfti+Y37Yv/9+2L//fti//37Yv/9+2L8nfti8PP2rqBD9q6mI/aursP2rq/z9q + 6v8/aur/P2rq/z9p5v9NX1b/spou/961L//fti//37YvrifQ+m8m0Pv/JtD7/ybQ+/8m0Pv+JtD7VgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1kDNqtbtzarW/82q + 1v/Nqtb/zarW/82q1vfNqtZeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti9V37Yv+9+2L//fti//37Yv/9+2L/Lfti8sP2rqET9q + 6p4/aur+P2rq/z9q6v8/aur/P2rq/z9q6v9Lb9m+zaw299+2L//fti//37Yv/9y2MoEm0PvUJtD7/ybQ + +/8m0Pv/JtD73ybQ+xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAzarWNM2q1t7Nqtb/zarW/82q1v/Nqtb/zarW/M2q1nHNqtYCAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8m37Yv6N+2L//fti//37Yv/9+2 + L/3fti9oQGrpOj9q6tU/aur/P2rq/z9q6v8/aur/P2rq/z9q6vI/aupv0rA+Mt+2L//fti//37Yv/9+2 + L/6LwouOJtD7/SbQ+/8m0Pv/JtD7/ybQ+4UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNqtYtzarW1c2q1v/Nqtb/zarW/82q1v/Nqtb6zarWg82q + 1gUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8J37Yvvd+2 + L//fti//37Yv/9+2L//ZszawRGzkdD9q6vM/aur/P2rq/z9q6v8/aur/P2rq/z9q6tQ/auo5P2rqAd+2 + L2Lfti//37Yv/9+2L//etjD3RsvWxCbQ+/8m0Pv/JtD7/ybQ+/km0PsjAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1iDNqtbQzarW/82q + 1v/Nqtb/zarW/82q1vzNqtaRzarWBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve9+2L/3fti//37Yv/9+2L//NrT/wVHTRxT9q6v4/aur/P2rq/z9q6v8/aur/P2rq/D9q + 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 + Lv8hlC7/JcPP/ybQ++wm0PsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADfti94wrEv/ymVLv8hlC7/IZUx/ybO9P8m0PukJtD7AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37Yvp3ukLv8hlC7/IZQu/yKfUv8m0Pr+JtD7PwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvCNe1L9Ezly7/IZQu/yGULv8jqnr/JtD7zibQ + +wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lxmdqi/0IZQu/yGU + Lv8hlC7/JLWg/ybQ+20AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADUtC87V54u/yGULv8hlC7/IZUw/yXAw/Qm0PsRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAk6kvbyeVLv8hlC7/IZQu/yGXOf8lxdegAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWeOqshlC7/IZQu/yGULv8inEj6Js/4PQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAznEDQIZQu/yGU + Lv8hlC7/J6BT0CbQ+wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAOaBHnCGULv8hlC7/IpQv/zmhTHEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKrYQ44n0aRI5UwtT2hS35suH0JAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////////////////////////8f/// + //////g/////////wB////////4AH///////+AAf///////gAB///////wAAD//////8AAAP/////+AA + AA//////gAAAD/////4AAgAH////8AAMAAf////AAHgAA////gAB+AAD///4AAfwAAP//8AAP+AAA/// + AAD/4ACB//wAB//AQAH/4AAf/wAAAf+AAPAAAAAB/gAAAAAAAAHwAAAAAAAAAcAAAAAAAAADgAAAAAAA + gAOAAAAACA+AA4AAAA/wDgAHgAB//+AcAAfAH///4DAAD/AP///AYAAP8Af//4BAAA/4Af//AAAAH/4B + //8AAAAf/wB//gAAAD//gD/8AAAAP//AH/wABgA//+AP+AAOAH//8AfwADwAf//4A+AAfAD///wB4AD8 + AP///gDAA/wB////AAAH/AH///+AAA/4Af///+AAP/gD////8AB/+AP////4AP/4A/////wD//AH//// + /gf/8Af/////H//wD/////////AP////////8A/////////wH////////+Af////////4D/////////g + P////////+B/////////4H/////////gf////////+D/////////4P////////////////////////// + ////////iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAgAElEQVR42u29eZRk133f97n3 + bbV19To9K2YADDAABrsAgYsIQhRNygSphZJNndhiZFG2adOREx2dJMfSsSImIn0sKUpkRfQRHZ1IpkxZ + NBcllEGJMSlCYkhQIIhlsGOAGcz0LD29d+1vub/88bp7umd6qa6uXqrqfs9pDKan3qt6r97ve7/f3/3d + 3wULCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC4uOgLK3wKLT + 8S8e+6hcKF9iqjbD+dIYAFppsm6WG4qHGc4M8W8/+Mf2WV8F2t4Ci07H67NnmaxNU4kqS78TEWITM1uf + 41LlMh/8o3eLvVNWAVh0ET702R+WWlznzflz649yC2rgrpGT/Luf/Jx95i0BWHQ6fuqz75Op+hRTtWkS + SZp40BU5L0efX+CrP/uEfe6tBbDoZEzXp6lE1aaCH0AQwiSkkTT4+f/7p60dsARg0an4n7/638lUfWqF + 528GkYmITcx8WLI30RKARadirHyxZfdaCss8M37K3kRLABadilpcR6R1FS9YB2AJwKJj0YjDLZ/jN/7s + EcsClgAsehVxPMW/e+wesQRgYdFhyHqZLZ9jLkxnBCwBWFh0GHw3j1Jbm8qfjxLiJLYEYB8ni05D5J1E + tljDdr4u1AxEz5wQSwAWFh2EP/jALyuji6BatwIToVBN7L107S2w6ESIsw9QENdbOr6aQN3Q8yRgFYBF + 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 + GBgsBv8PNAlKsBafvBgAAAAASUVORK5CYII= + + \ No newline at end of file diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index f69189ca..13850c30 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -190,7 +190,7 @@ internal override void DrawScale() var curve = zedGraphChannels.GraphPane.AddCurve(ScalePointsTag, pointList, Color.Black, SymbolType.None); - curve.Line.Width = 4; + curve.Line.Width = zoomedOut ? 2 : 4; curve.Label.IsVisible = false; curve.Symbol.IsVisible = false; } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs index d8fdc335..9dc0040c 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.Designer.cs @@ -28,6 +28,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NeuropixelsV2eDialog)); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); @@ -66,7 +67,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, 24); - this.splitContainer1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(2); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -78,7 +79,7 @@ private void InitializeComponent() // this.splitContainer1.Panel2.Controls.Add(this.panel1); this.splitContainer1.Size = new System.Drawing.Size(843, 506); - this.splitContainer1.SplitterDistance = 475; + this.splitContainer1.SplitterDistance = 477; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 2; // @@ -86,10 +87,10 @@ private void InitializeComponent() // this.tabControlProbe.Dock = System.Windows.Forms.DockStyle.Fill; this.tabControlProbe.Location = new System.Drawing.Point(0, 0); - this.tabControlProbe.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.tabControlProbe.Margin = new System.Windows.Forms.Padding(2); this.tabControlProbe.Name = "tabControlProbe"; this.tabControlProbe.SelectedIndex = 0; - this.tabControlProbe.Size = new System.Drawing.Size(843, 475); + this.tabControlProbe.Size = new System.Drawing.Size(843, 477); this.tabControlProbe.TabIndex = 1; // // panel1 @@ -98,16 +99,16 @@ private void InitializeComponent() 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.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.panel1.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(843, 28); + this.panel1.Size = new System.Drawing.Size(843, 26); 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(753, 4); - this.buttonCancel.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonCancel.Location = new System.Drawing.Point(753, 2); + this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(83, 22); this.buttonCancel.TabIndex = 1; @@ -118,8 +119,8 @@ private void InitializeComponent() // 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(664, 4); - this.buttonOkay.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonOkay.Location = new System.Drawing.Point(664, 2); + this.buttonOkay.Margin = new System.Windows.Forms.Padding(2); this.buttonOkay.Name = "buttonOkay"; this.buttonOkay.Size = new System.Drawing.Size(83, 22); this.buttonOkay.TabIndex = 0; @@ -135,8 +136,9 @@ private void InitializeComponent() this.Controls.Add(this.splitContainer1); this.Controls.Add(this.menuStrip); this.DoubleBuffered = true; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip; - this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.Margin = new System.Windows.Forms.Padding(2); this.Name = "NeuropixelsV2eDialog"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "NeuropixelsV2eDialog"; diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx index 9d845151..ef157a47 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.resx @@ -120,4 +120,1659 @@ 17, 17 + + + + AAABAA4AEBAQAAEABAAoAQAA5gAAABAQAAABAAgAaAUAAA4CAAAQEAAAAQAgAGgEAAB2BwAAICAQAAEA + BADoAgAA3gsAACAgAAABAAgAqAgAAMYOAAAgIAAAAQAgAKgQAABuFwAAMDAQAAEABABoBgAAFigAADAw + AAABAAgAqA4AAH4uAAAwMAAAAQAYAKgcAAAmPQAAMDAAAAEAIACoJQAAzlkAAEBAAAABABgAKDIAAHZ/ + AABAQAAAAQAgAChCAACesQAAAAAAAAEAGABpMQAAxvMAAAAAAAABACAAZ10AAC8lAQAoAAAAEAAAACAA + AAABAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAgAAAgICAAACAgADAwMAA//8AAAD/ + /wAAAP8A/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUiAAAAAAAFU5YAAA + AAVVADYgAAAFVQAGYlAAVTUAVTUjMAM1VVVTIDOABVAAAAYDRHAAVQAAYzhlAAAFUAY4AFcAAABVA0AA + NwAAAAUDAAZAAAAAAAAAA0AAAAAAAAACAAAAAAAAAAEAAAAAAAAAAAAA//8AAP/xAAD/wQAA/jEAAPjh + AADDAAAAgBEAAJ+hAADPAwAA5jMAAPJzAAD45wAA/+cAAP/vAAD/7wAA//8AACgAAAAQAAAAIAAAAAEA + CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI5YzAEWaLgCFpi8AJLioAL2exQA9QTAAO12VANK0 + LwAws5AAzarWAGZsPgA+Z9UAdLRpACbQ+wDdtS8Ak41jAEBr6QDAtkIA37YvAKmdbgA/auoAR27gANKu + MgA8zeMAu5vDAMiq0ABLXUwATaKmAHpvfACWlJUAqqmpALe2tgDGr2QAxKAqAHt6RQA+XowAHJXwALW0 + tACysbEAtLOzALe1swCrjzcAsp5eAK2ecQCQgU8ALk93AA09OADctDEAzqw0AN61LgDbrikA3bMsAMWs + XgC/gxAA1qYjALazrQCMYSEAnGUNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg5 + OgAAAAAAAAAAAAAAICA1NjcAAAAAAAAAACAgIAAAMjM0AAAAAAAAICAgAAAAExMwMQAAACYnKCAAACAp + KissLS4vAB0eHyAgICAgISIAIyQlAAAZGgAAAAAAABMAFRscDgAAAAoKAAAAABMUFRYXGAAAAAAACgoA + AA8QEQAAEg4AAAAAAAAKCgALDAAAAA0OAAAAAAAAAAUGBwAAAAgJAAAAAAAAAAAAAAAAAAADBAAAAAAA + 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 + ABMAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + //////+H///+B///+AP//8AD//8HA//4HgP/4HwB/wP8ifwP+AHwAAABgAABAYAH5wOH/8YD4/+MB/H/ + kAf4/wEH/H4DD/48Dg//HB4f/4h+H//A/h//4f4///f+P////H////x////8f////P////z///////// + //8oAAAAIAAAAEAAAAABAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULgAmmTwAIpQuACGX + NwBNnC4AIZQvACW/wACPqC8AJLmsANS0LwAxly4AI6+JAD5CNwB1oy4AIqNhACbQ+wCchaAALDMkAC02 + KQA8X6cAwbEvACOZPgAmz/cAzarWAMmn0QBmWUwAM0MoADhQRQA+aN0A37YvAFKdLgAlyN8Ano0tAEFS + KwA6V3AAP2rpAJ+qLwAkvLcAxqYuAFliLAA8X6QAP2rqANm1LwA1tJAA2bIvAIF8OAA/Z9AAfLVpAKyb + VgBDbOUAxbZAACbQ+gDCqFEAPszgAEFr5gCrlzoAf8OZADpUWgCAei0AzLE5AIhxjgCkiKsAt5e/AMKn + yQA6WX8AN0wrAEFXUAAsuPcAaVZtAHZpeQCbmZoAsK+vALe2tgBWaGgAPE8rADpt6QAcpPMAdGJ3AIB2 + ggCKiYkAhoWFAIWEhACTkpIAqKenALa1tQCynl4AsI8lALSSJgCxp4oAwLKGAKqRKwBIVSwAPWXVABlb + 5AAUg+0AJMf5ALOysgCrqqoArq2tALazrwCkizoAoIIiAKSMPgC2tLEAoYQpAJl9IgBZZocAIkmpAAY3 + aAALRk0Au5goALaVJwC4pGMAp44+AKKEIgCsnnMAgIqoABAyJAAGLB4A3LMuANayOgC0mSoAmIsqAN60 + LQDftS8A3rQuANOgHwDarSgA2aomANK0VADetS8AyY0RAMyTFgCwlUEAtYMTALJqBQDNlRcAs62dAJdy + HAB9RAEAo2kMAKSQawB6QgEAgUoFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASY6PkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASUmKi4yNAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASUlJaIaHiIkeAAAAAAAAAAAAAAAAAAAAAAAAAABJSUlJSUmCg4SFfx4AAAAA + AAAAAAAAAAAAAAAAAABJSUlJSQAAAB58f4CBHgAAAAAAAAAAAAAAAAAAAElJSUlJSQAAAAAeHnx9Hn4e + AAAAAAAAAAAAAAAAAElJSUlJSQAAAAAAHh4eHh4eHh4eAAAAAAAAAAAASUlJSUlJAAAAAAAAAAAeHgAe + eHkAensAAAAAAAAASUlJSUlJAAAAAAAAAAAAb3BxSXJzdHV2dwAAAAAAVWFIYmNVSUlJSUlJSUlJZGVm + Z2hoaWprbG1uAABOT1BRUlNUVUlJSUlJSUlJSUlWV1hZAFpbXF1eX2AAAEVFRkdISUlJSUlJSQAAAAAA + AB4eAAAASkscTE0QAAAAPT4/QAAAAAAAAAAAAAAAAAAeHh4AACpBQkNEEBAAAAAAABgYGAAAAAAAAAAA + AAAAHh4eAAAqKjo7PBAQAAAAAAAAABgYGAAAAAAAAAAAAAAeHgAqKio3OB45EBAAAAAAAAAAABgYGAAA + AAAAAAAAHh41KioqKgAeHjYQEAAAAAAAAAAAABgYGAAAAAAAAB4eMTIqKioAAB4zNBAAAAAAAAAAAAAA + ABgYGAAAAAAeLS4vKioAAAAeHjAQEAAAAAAAAAAAAAAAABgYGAAAACcoKSoqAAAAAB4rLBAAAAAAAAAA + AAAAAAAAABgYGAAhIiMkAAAAAAAAHiUmEAAAAAAAAAAAAAAAAAAAABgZGhscHQAAAAAAAAAeHyAQAAAA + AAAAAAAAAAAAAAAAABESExQAAAAAAAAAABUWFwAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAADg8Q + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoLDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + CAEJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////// + /4f///4H///4A///wAP//wcD//geA//gfAH/A/yJ/A/4AfAAAAGAAAEBgAfnA4f/xgPj/4wH8f+QB/j/ + AQf8fgMP/jwOD/8cHh//iH4f/8D+H//h/j//9/4////8f////H////x////8/////P///////////ygA + AAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsqqXDIxaEWuPWw1DAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AADwf//DAI8AAPg//4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/w + eAfgfwAA//gwH+B/AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/ + AAD/////g/8AAP////+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP// + /////wAA////////AAD///////8AACgAAAAwAAAAYAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIZQuAC2cQQA4nT8AI5xHACSVLgAhlzgAUp0uACGULwAlwMQAlqkvACS4qADXtS8AMZcuACOs + gQAm0PsA37YvAHmkLgAioVoAJtD6AMCwLwAmlS4AJs71AD1BNwA7QDQAVJ0uACXF1wCJd4sALDMkAC44 + LwA8YbUAoqsvACS6rwDNqtYAq5CwAC81KAAvOyYAOFJRAD9p4gDYtS8AN5guACOviQCbfXkAQEMmADZL + KwA3TCsAOll/AD9q6QB9pC4AIqNgAMypzQDHpS8AaGssADhMKwA3TC0APGGyAD9q6gDGsi8AJptCACbN + 8gDbtC8AkoYtADtPKwA4T0AAPmfWAFieMAAlx9wAu58uAFBbLAA5VWIAP2roAKWsLwAlvLcA0q4vAHFx + LQA8XZUA2bUvAD61jwDetS8Am4w1AEVpxACCtmkAwKZJAEpv3QDItj0AJ9D5ANKwPgBAaukA3rYvAELM + 3ACFw5IAP2rmAHV3SADUry8A2bc2ADpWaABTXiwAvaAuADtbjQA9UCsAioEvAIBqhQCKcpAAoISnAK+R + tgC7ocEAPWGxADhNLQA+ZtMAKcP5AHdlewBpVm0AeWp8AKGeoAC0s7MAt7a2ADlOLQA9YrkANHLrAB+y + 9gB2ZHoAcmV0AIKBgQCDgoIAh4aGAJqZmQCura0AyrFhANCpLADUrS0A2bEuANy0LgCymS4ATVorADtc + kgA+auoAElznABiU8QAlzfsAeWh8AIh+iQCRkJAAjYyMAImIiACGhYUAhYSEAISDgwCmpaUAtbS0ALe1 + tACnkU0AoYMiAKKEIgCkhSMArqB3AMCjRQC4mCkAXmEoADdSaQA9ZuIAJl/kAAlV5AARdusAIsH4ALGw + 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 + AAAAAAAAAAAAAAAAAAAAAAAAISEhISEAAAAAEENERUY4ODgAAAAAAAAQEEdIDw8AAAAAAAAAAAAAAAAA + AAAAAAAAACEhISEhAAA8PT4/QDg4AAAAAAAAAAAQEEFCDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAhISEh + MjM0NTY3ODgAAAAAAAAAAAAQOTo7DwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISEhKissLS4vAAAAAAAA + AAAAABAQMDEPDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiIxwkJSYAAAAAAAAAAAAAABAnKCkPDwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbHBwdHgAAAAAAAAAAAAAAABAfASAPAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAFxgAAAAAAAAAAAAAAAAAABAZARoPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAABQVBhYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAEBEBEhMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA0BDg8AAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgEBCwAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwEICQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAABQEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAADAQEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////AAD///////8AAP//////nwAA//////4f + AAD/////8A8AAP/////ADwAA/////gAPAAD////4AAcAAP///+AQBwAA////AHAHAAD///wD4AcAAP// + 4A/AAwAA//+AP8AjAAD//AH/hCMAAP/wB/8MIQAA/8A//gABAAD+AP4AAAEAAPgAAAAAAQAAwAAAAAAD + AACAAAAQeAMAAIAA//D4BwAAwf//4eAHAADg///DwIcAAPB//8MAjwAA+D//hgAPAAD8H/8MEB8AAP4P + /wAwHwAA/wf+AHA/AAD/w/wB4D8AAP/h+APgPwAA//B4B+B/AAD/+DAf4H8AAP/8AD/g/wAA//4A/8D/ + AAD//wH/wP8AAP//g//B/wAA///P/8H/AAD/////w/8AAP////+D/wAA/////4P/AAD/////h/8AAP// + //+H/wAA/////4//AAD/////D/8AAP////+P/wAA////////AAD///////8AAP///////wAAKAAAADAA + AABgAAAAAQAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIpWC49bDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKdLiGULiGULyXAxAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + ACSVLiGULiGXOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAADidPyGULiGULiOcRwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULiGULi2cQQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////wAA//////// + AAD//////58AAP/////+HwAA//////APAAD/////wA8AAP////4ADwAA////+AAHAAD////gEAcAAP// + /wBwBwAA///8A+AHAAD//+APwAMAAP//gD/AIwAA//wB/4QjAAD/8Af/DCEAAP/AP/4AAQAA/gD+AAAB + AAD4AAAAAAEAAMAAAAAAAwAAgAAAEHgDAACAAP/w+AcAAMH//+HgBwAA4P//w8CHAADwf//DAI8AAPg/ + /4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/weAfgfwAA//gwH+B/ + AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/AAD/////g/8AAP// + //+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP///////wAA//////// + AAD///////8AACgAAAAwAAAAYAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + ogYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYyt7a2mre2tu+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tvm3traqt7a2Q7e2tgO3trYIt7a2ILe2tji3trZPt7a2aLe2toC3traXt7a2sLe2tsm3trbat7a27Le2 + tvqwp4v/oIIj/6CCIv+ggiL/pY5D/7a0sf+3trb/r6WG/6CCIv+ggiL/oYUq/5udpv9EXqr/FD1//wYs + Hv8GLB7/DDQp7WSTmw0AAAAAAAAAAAAAAAC3trYNt7a2XLe2tsa3trb+tLOz/7GwsP+ura3/qaio/6Sj + o/+mpaX/srGx9Le2tsC3tra/t7a21be2tu+3trb+t7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7Swpf+ihzH/oIIi/6CCIv+hhSv/tK+h/7e2tv+3trb/q5to/6CCIv+fgSL/bWU3/zJQ + p/8qTKr/CECp/wY5bf8JQ1b/GXyMigAAAAAAAAAAkoKVGXlofJOIfonkkZCQ/42MjP+JiIj/hoWF/4WE + hP+Eg4P/kZCQ/6alpf+1tLT/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7W0/6eRTf+hgyL/ooQi/6SFI/6uoHfgt7a2xLe2tq63traWwKNF4riY + Kf9eYSj/N1Jp/z1m4v8mX+T/CVXk/xF26/8iwfj5JtD7JgAAAAAAAAAAdmR6sWlWbf9pVm3/cmV0/4KB + gf+DgoL/h4aG/5qZmf+ura3/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb5t7a25be2ts23tra3t7a2n7e2toe3trZvyrFhudCpLP/UrS3/2bEu/9y0LqTfti8CAAAAAAAA + AAAAAAAAspku501aK/83TCv/O1yS/z5q6v8SXOf/GJTx/yXN+/8m0Pu4JtD7AgAAAAAAAAAAd2V7xmlW + bf9pVm3/aVZt/3lqfP+hnqD/tLOz/7e2tv+3trb+t7a287e2tua3trbYt7a2wLe2tqm3traQt7a2eLe2 + tmG3trZIt7a2Mbe2thi3trYGt7a2A7e2tgEAAAAAAAAAAAAAAADfti8z37Yv99+2L//fti//37Yv2d+2 + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2LwHXtS/DMZcu/yGU + Lv8jrIH/JtD7oQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + LwyWqS/qIZQu/yGULv8kuKj/JtD7OgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANW0LydSnS7/IZQu/yGUL/8lwMTOJtD7AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIClL1kklS7/IZQu/yGXOP8lx9xqAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADidP5QhlC7/IZQu/yOcR/Am0PsUAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADefRX8hlC7/IZQu/y2c + QaEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKr + YQgxnD9zLZo6fFeuZhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP///////wAA//////4PAAD/////+A8AAP// + ///ADwAA/////wAHAAD////4AAcAAP///+AABwAA////gAAHAAD///wAAAMAAP//8ADAAwAA//+AA4AD + AAD//gAPgAEAAP/4AH8AAQAA/8AD/gABAAD/AA/gAAAAAPwAAAAAAAAA4AAAAAABAACAAAAAAAEAAIAA + AAA4AQAAgAAA4GADAACAB//AwAMAAMB//8GABwAA4D//gAAHAADwH/8AAA8AAPgP/gAADwAA/Af+AAAP + AAD+A/wAIB8AAP8A+ADgHwAA/4BwAcA/AAD/4DADwD8AAP/wAA/APwAA//gAH8B/AAD//AA/wH8AAP/+ + AP/A/wAA//8B/4D/AAD//4P/gP8AAP//z/+B/wAA/////4H/AAD/////A/8AAP////8D/wAA/////wP/ + AAD/////B/8AAP////8H/wAA/////w//AAD/////D/8AAP///////wAA////////AAAoAAAAQAAAAIAA + AAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjV0YiVQKi1YLAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2n4Ve + ekIBekIBekIBh1IIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2squWjWEUekIBekIBekIBiVQJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAt7a2t7a2t7a2t7a2trOupIs9i14LekIBekIBekIBpG8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7W1qZddn4EhjF0HfEUBhUgBnloDz5kb37YvAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2r6SCoIMkoX8eomwIqGMErF8Cv3wI1qUj + 37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2tK+grpAzw58pzp0gw4IJ + unQGuXMGw4IJ268q37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uLaz0LBH + 3bQv37Yv0Joaw4IJyY0SxIMKxocN3rQu37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2AAAAAAAA37Yv37Yv37UvypATx4kO3LAryY4Ry5AT37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2AAAAAAAAAAAAAAAA37Yv37Yv37Yv3rQtxogO2Kgl37Yv0Zwc0Joa37Yv37Yv37YvAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv27Aq0Jsb37Yv37Yv2asn1aMh37Yv + 37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv264p3rQu37Yv + 37Yv3rUu268q37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv + 37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37Yv + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAA37Yv37Yv37Yv37YvAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA3rUv2rIu1q8t + 0qwuAAAAAAAAv6hesJMnYWclOlIjdXcnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA168t1a4t0KosyqUqxKAqw61ot7a2t7a2 + r51kp4gjpYYjo4Ujo4gvtbGpt7a2l5iXLEAjBiweBiweBy0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7WzqZNLpIUjoIIioIIioIIi + ppBKtrSwt7a2t7a2qJNToIIioIIioIIipIw+tbS1gIywMlCkCjAzBiweBiweBiweHkc/AAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2rqF6oIIi + oIIioIIioIIioocvtK+it7a2t7a2t7W0o4k2oIIioIIioIIino5aXXGsLUynHkamBjRgBiweBiweBi0g + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2tLOzsrGxrq2tq6qqp6amoqGhnJubnp2dq6qq + tLOzt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2sqyYoYUroIIioIIioIIioIIjr6WFt7a2t7a2t7a2tLCkoYUqoIIioIIigm8hPE5uLUynLk6rD0Os + BkGrBzt4CEBgF4SdAAAAAAAAAAAAAAAAAAAAg3SGkImRmZeYlZSUkI+Pi4qKiYiIh4aGhYSEg4KChIOD + j46OpKOjs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2trOtpY1CoIIioIIioIIioIIiqZZct7a1t7a2t7a2t7a2t6uHuZcouZgoeXEoN0gn + OV2zPGbgMWPiClXiCVXkDmzqILf3Js/7AAAAAAAAAAAAAAAAcmB2aVZtaVZtcmV0goCBg4KCg4KCg4KC + g4KChoWFmJeXrKurtrW1t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uKNitpUnvJkowp4pyKMqyqUsAAAAAAAAAAAAAAAAAAAAAAAA + zasvamwsOEwrN042PmfYP2rqH17oCVjmFIXuJMf6JtD7AAAAAAAAAAAAAAAAAAAAaVZtaVZtaVZtaVZt + cGJzgX+Bg4KCjIuLoJ+fs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3rYvV54uIZQuIZQuJcPP + JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwrEv + KZUuIZQuIZUxJs70JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve6QuIZQuIZQuIp9SJtD6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA17UvM5cuIZQuIZQuI6p6JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnaovIZQuIZQuIZQuJLWgAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV54uIZQuIZQuIZUwJcDDAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ5UuIZQu + IZQuIZc5JcXXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAARZ46IZQuIZQuIZQuIpxIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM5xAIZQuIZQuIZQuJ6BTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOaBHIZQuIZQuIpQvAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOJ9GI5UwAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/////////////////////////////////////////H/////////wP/// + /////4A////////+AD////////gAH///////wAAf//////8AAB//////+AAAD//////gAYAP/////4AP + AA/////8AD4AD/////AB/AAH////gAf8AAf///4AP/ggh///+AD/8CCD///AA//gYYP//wAf/+Dhg//4 + AH//wAAD/+AD//gAAAH/AA/AAAAAA/wAAAAAAAAD4AAAAAAAAAPAAAAAAA/AB8AAAAP8H4AHwAAP//g/ + AA/AP///8D4AD+A////wfAYP+B///+DwBB/8D///wOAEH/4H//+BwAQ//wP//4MAAD//gf//BgMAP//A + f/4EBwB//+A//gAOAH//8B/8AD4A///4D/gAfgD///wH8AD+Af///gPwA/4B////AeAH/AH////AwA/8 + A////+AAP/wD////8AB//Af////4Af/8B/////wD//gH/////gf/+A//////H//4D/////////gf//// + ////+B/////////4H/////////A/////////8D/////////wf/////////B/////////8H/////////g + /////////+D/////////4f/////////z//////////////////////////////////8oAAAAQAAAAIAA + AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKpUACs4ckEMObMAgAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyqpcwjV0YpYlU + CvSLVgvSmWcTMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dre2 + tli3trbBn4Ve/HpCAf96QgH/ekIB/4dSCM3FnzkGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYlt7a2g7e2tt63trb9squW/41hFP96QgH/ekIB/3pCAf+JVAn4yZ0oIwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2Bre2tkW3trast7a28re2tv+3trb/trOu/6SLPf+LXgv/ekIB/3pCAf96QgH/pG8P/9+2 + L1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYVt7a2b7e2ttW3trb+t7a2/7e2tv+3trb/t7W1/6mXXf+fgSH/jF0H/3xF + Af+FSAH/nloD/8+ZG//fti+iAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2BLe2tjm3trabt7a277e2tv+3trb/t7a2/7e2tv+3trb/t7a2/6+k + gv+ggyT/oX8e/6JsCP+oYwT/rF8C/798CP/WpSP/37Yv4N+2LwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYSt7a2Xre2tsO3trb4t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7SvoP+ukDP/w58p/86dIP/Dggn/unQG/7lzBv/Dggn/268q/9+2L/vfti88AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Are2tiS3traJt7a24re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2+Li2s7TQsEfr3bQv/9+2L//Qmhr/w4IJ/8mNEv/Egwr/xocN/960 + Lv/fti//37YvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2T7e2trO3trb6t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbgt7a2g7m2sSLfti9937Yv/9+2L//ftS//ypAT/8eJ + Dv/csCv/yY4R/8uQE//fti//37Yv/9+2L8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tiG3trZ2t7a22be2 + tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a297e2try3trZat7a2DAAAAADfti8/37Yv9t+2 + L//fti//3rQt/8aIDv/YqCX/37Yv/9GcHP/Qmhr/37Yv/9+2L//fti/637YvHAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2Obe2 + tqS3trbtt7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv63trbqt7a2lre2tjO3trYEAAAAAAAA + AADfti8W37Yv1d+2L//fti//37Yv/9uwKv/Qmxv/37Yv/9+2L//Zqyf/1aMh/9+2L//fti//37Yv/9+2 + L2MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgy3trZlt7a2xre2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2ttK3trZqt7a2FgAA + AAAAAAAAAAAAAAAAAADfti8C37Yvo9+2L//fti//37Yv/9+2L//brin23rQu/9+2L//fti//3rUu/duv + KvDfti//37Yv/9+2L//fti+p37YvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAC3trYBt7a2L7e2to+3trbut7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbyt7a2pre2 + tkC3trYBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvYt+2L/zfti//37Yv/9+2L//fti/p37YvrN+2 + L//fti//37Yv/9+2L/Dfti+m37Yv/9+2L//fti//37Yv6d+2Lw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAALe2tg63trZSt7a2u7e2tva3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/re2 + ttq3trZ8t7a2ILe2tgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvKN+2L+/fti//37Yv/9+2 + L//fti/837YvYN+2L7Xfti//37Yv/9+2L//fti/G37YvV9+2L/7fti//37Yv/9+2L//fti9DAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYCt7a2H7e2tn63trbZt7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb3t7a2ure2tlO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvDN+2 + L8Lfti//37Yv/9+2L//fti//37Yvod+2LxHfti/b37Yv/9+2L//fti//37YvlN+2Lxbfti/037Yv/9+2 + L//fti//37YvjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgK3trZBt7a2pre2tvS3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tuy3traPt7a2Lre2tgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2L4Xfti//37Yv/9+2L//fti//37Yv3d+2LxDfti8i37Yv9d+2L//fti//37Yv/9+2 + L2QAAAAA37Yvv9+2L//fti//37Yv/9+2L9Dfti8HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Fre2tmy3trbUt7a2/re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb9t7a2xbe2tmO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L0rfti/337Yv/9+2L//fti//37Yv9N+2Lz4AAAAA37YvS961 + L//asi7/1q8t/9KsLv3Esndyt7a2X7+oXrqwkyf/YWcl/zpSI/91dyf4sp0sKwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgS3trY0t7a2lre2tum3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tu23traht7a2O7e2tgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2CMasViTXry3f1a4t/9CqLP/KpSr/xKAq/8Ot + aNu3traft7a2tq+dZN+niCP/pYYj/6OFI/+jiC//tbGp/7e2tv+XmJf/LEAj/wYsHv8GLB7/By0e/yhO + On8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dbe2tli3tra/t7a2+Le2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb8t7a22re2tnW3trYeAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYFt7a2Cbe2tg23trYbt7a2Nbe2tky3trZlt7a2fLe2tpS3tratt7a2xLe1s9Spk0vspIUj/6CC + Iv+ggiL/oIIi/6aQSv+2tLD/t7a2/7e2tv+ok1P/oIIi/6CCIv+ggiL/pIw+/7W0tf+AjLD/MlCk/wow + M/8GLB7/Biwe/wYsHv8eRz+4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYjt7a2hLe2 + tuG3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tvu3trawt7a2Ure2tiC3trYvt7a2Rbe2 + tlq3trZ0t7a2i7e2tqK3tra6t7a2zbe2tuO3trb2t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+uoXr/oIIi/6CCIv+ggiL/oIIi/6KHL/+0r6L/t7a2/7e2tv+3tbT/o4k2/6CCIv+ggiL/oIIi/56O + Wv9dcaz/LUyn/x5Gpv8GNGD/Biwe/wYsHv8GLSD/G0xIcgAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Cbe2 + tkm3travt7a297e2tv+0s7P/srGx/66trf+rqqr/p6am/6Khof+cm5v/np2d/6uqqv60s7Pht7a25Le2 + tvW3trb4t7a2+7e2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+yrJj/oYUr/6CCIv+ggiL/oIIi/6CCI/+vpYX/t7a2/7e2tv+3trb/tLCk/6GF + Kv+ggiL/oIIi/4JvIf88Tm7/LUyn/y5Oq/8PQ6z/BkGr/wc7eP8IQGD/F4Sd4ym42xYAAAAAAAAAAAAA + AACTg5Ygg3SGgJCJkdOZl5j7lZSU/5CPj/+Lior/iYiI/4eGhv+FhIT/g4KC/4SDg/+Pjo7/pKOj/7Oy + sv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+2s63/pY1C/6CCIv+ggiL/oIIi/6CCIv+pllz6t7a18be2 + tui3trbgt7a2zberh825lyj/uZgo/3lxKP83SCf/OV2z/zxm4P8xY+L/ClXi/wlV5P8ObOr/ILf3/ybP + +5EAAAAAAAAAAAAAAACLeo4acmB23mlWbf9pVm3/cmV0/4KAgf+DgoL/g4KC/4OCgv+DgoL/hoWF/5iX + l/+sq6v/trW1/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/re2tvy3trb6t7a2+Le2tuy3trbWuKNi9baVJ/+8mSj/wp4p/8ij + Kv/KpSzUtaqGN7e2tiO3trYTt7a2BAAAAADfti90zasv/2psLP84TCv/N042/z5n2P8/aur/H17o/wlY + 5v8Uhe7/JMf6/ybQ+/wm0PsvAAAAAAAAAAAAAAAAjHqPcWlWbf9pVm3/aVZt/2lWbf9wYnP/gX+B/4OC + gv+Mi4v/oJ+f/7Oysv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb9t7a27Le2tta3trbDt7a2rre2tpa3trZ+t7a2Zre2tk+3trY4t7a2I7e2tg4AAAAA37YvTt+2 + L/zfti//37Yv/9+2L//fti/y37YvNwAAAAAAAAAAAAAAAAAAAAAAAAAAlIczp0pYK/83TCv/N0wr/zhR + Tv8/aeT/O2nq/w9g6P8cpPT/Js/7/ybQ+/8m0Pu9JtD7BAAAAAAAAAAAAAAAAI59kVVrWG/+aVZt/2lW + bf9pVm3/a1hv/31sgP+moqX/tbS0/7e2tv+3trb/t7a2/7e2tve3trbot7a227e2ts63tra3t7a2obe2 + toe3trZvt7a2Wbe2tkG3trYmt7a2Ebe2tgy3trYHt7a2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37YvH9+2L+Pfti//37Yv/9+2L//fti/+37YvcN+2LwEAAAAAAAAAAAAAAAA/auoMP2rqgzlR + SPs3TCv/N0wr/zdMK/86V27/P2rq/y567PchvPj/JtD7/ybQ+/8m0Pv+JtD7WQAAAAAAAAAAAAAAAAAA + AACjk6UJfmqBoHFddvx0X3n/hm+M/5p/of+mia3/s5q5/ruyvZu3trZht7a2Sre2tjG3trYit7a2F7e2 + tg23trYDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37YvCN+2L7Lfti//37Yv/9+2L//fti//37Yvst+2LwcAAAAAAAAAAAAA + AAA/auonP2rqvD9p4f43TC7/N0wr/zdMK/83TCv/O1yQ+D5v65YnyfrTJtD7/ybQ+/8m0Pv/JtD76CbQ + +wsAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gPCocuAyafS/c2q1v/Nqtb/zarW/82q1v/NqtbXzarWKwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L3Lfti/937Yv/9+2L//fti//37Yv5t+2 + LxwAAAAAAAAAAD9q6gE/aupSP2rq5z9q6v89ZMH/N0wr/zdMK/83TCv/RFQr/2Nxbmonzfs8JtD7+ybQ + +/8m0Pv/JtD7/ybQ+40AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1m7Nqtb4zarW/82q + 1v/Nqtb/zarW/82q1ubNqtY2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lznfti/z37Yv/9+2 + L//fti//37Yv+N+2L00AAAAAAAAAAD9q6g4/auqSP2rq/z9q6v8/aur/PF2Z/zdMK/83TCv/ZGgs/8uq + L/7fti8SJtD7oCbQ+/8m0Pv/JtD7/ybQ+/Qm0PstAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADNqtYCzarWXc2q1vTNqtb/zarW/82q1v/Nqtb/zarW682q1kMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + Lw3fti/W37Yv/9+2L//fti//37Yv/9+2L43fti8CAAAAAD9q6i8/aurKP2rq/j9q6v8/aur/P2rq/zpY + d/89UCv/jIIt/9myL//fti/cQMzeHibQ++sm0Pv/JtD7/ybQ+/8m0Pu6JtD7AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gHNqtZNzarW8s2q1v/Nqtb/zarW/82q1v/NqtbwzarWUM2q + 1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2LwTfti+Y37Yv/9+2L//fti//37Yv/9+2L8nfti8PP2rqBD9q6mI/aursP2rq/z9q + 6v8/aur/P2rq/z9p5v9NX1b/spou/961L//fti//37YvrifQ+m8m0Pv/JtD7/ybQ+/8m0Pv+JtD7VgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1kDNqtbtzarW/82q + 1v/Nqtb/zarW/82q1vfNqtZeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti9V37Yv+9+2L//fti//37Yv/9+2L/Lfti8sP2rqET9q + 6p4/aur+P2rq/z9q6v8/aur/P2rq/z9q6v9Lb9m+zaw299+2L//fti//37Yv/9y2MoEm0PvUJtD7/ybQ + +/8m0Pv/JtD73ybQ+xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAzarWNM2q1t7Nqtb/zarW/82q1v/Nqtb/zarW/M2q1nHNqtYCAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8m37Yv6N+2L//fti//37Yv/9+2 + L/3fti9oQGrpOj9q6tU/aur/P2rq/z9q6v8/aur/P2rq/z9q6vI/aupv0rA+Mt+2L//fti//37Yv/9+2 + L/6LwouOJtD7/SbQ+/8m0Pv/JtD7/ybQ+4UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNqtYtzarW1c2q1v/Nqtb/zarW/82q1v/Nqtb6zarWg82q + 1gUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8J37Yvvd+2 + L//fti//37Yv/9+2L//ZszawRGzkdD9q6vM/aur/P2rq/z9q6v8/aur/P2rq/z9q6tQ/auo5P2rqAd+2 + L2Lfti//37Yv/9+2L//etjD3RsvWxCbQ+/8m0Pv/JtD7/ybQ+/km0PsjAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1iDNqtbQzarW/82q + 1v/Nqtb/zarW/82q1vzNqtaRzarWBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve9+2L/3fti//37Yv/9+2L//NrT/wVHTRxT9q6v4/aur/P2rq/z9q6v8/aur/P2rq/D9q + 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 + Lv8hlC7/JcPP/ybQ++wm0PsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADfti94wrEv/ymVLv8hlC7/IZUx/ybO9P8m0PukJtD7AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37Yvp3ukLv8hlC7/IZQu/yKfUv8m0Pr+JtD7PwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvCNe1L9Ezly7/IZQu/yGULv8jqnr/JtD7zibQ + +wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lxmdqi/0IZQu/yGU + Lv8hlC7/JLWg/ybQ+20AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADUtC87V54u/yGULv8hlC7/IZUw/yXAw/Qm0PsRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAk6kvbyeVLv8hlC7/IZQu/yGXOf8lxdegAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWeOqshlC7/IZQu/yGULv8inEj6Js/4PQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAznEDQIZQu/yGU + Lv8hlC7/J6BT0CbQ+wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAOaBHnCGULv8hlC7/IpQv/zmhTHEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKrYQ44n0aRI5UwtT2hS35suH0JAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////////////////////////8f/// + //////g/////////wB////////4AH///////+AAf///////gAB///////wAAD//////8AAAP/////+AA + AA//////gAAAD/////4AAgAH////8AAMAAf////AAHgAA////gAB+AAD///4AAfwAAP//8AAP+AAA/// + AAD/4ACB//wAB//AQAH/4AAf/wAAAf+AAPAAAAAB/gAAAAAAAAHwAAAAAAAAAcAAAAAAAAADgAAAAAAA + gAOAAAAACA+AA4AAAA/wDgAHgAB//+AcAAfAH///4DAAD/AP///AYAAP8Af//4BAAA/4Af//AAAAH/4B + //8AAAAf/wB//gAAAD//gD/8AAAAP//AH/wABgA//+AP+AAOAH//8AfwADwAf//4A+AAfAD///wB4AD8 + AP///gDAA/wB////AAAH/AH///+AAA/4Af///+AAP/gD////8AB/+AP////4AP/4A/////wD//AH//// + /gf/8Af/////H//wD/////////AP////////8A/////////wH////////+Af////////4D/////////g + P////////+B/////////4H/////////gf////////+D/////////4P////////////////////////// + ////////iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAgAElEQVR42u29eZRk133f97n3 + bbV19To9K2YADDAABrsAgYsIQhRNygSphZJNndhiZFG2adOREx2dJMfSsSImIn0sKUpkRfQRHZ1IpkxZ + NBcllEGJMSlCYkhQIIhlsGOAGcz0LD29d+1vub/88bp7umd6qa6uXqrqfs9pDKan3qt6r97ve7/f3/3d + 3wULCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC4uOgLK3wKLT + 8S8e+6hcKF9iqjbD+dIYAFppsm6WG4qHGc4M8W8/+Mf2WV8F2t4Ci07H67NnmaxNU4kqS78TEWITM1uf + 41LlMh/8o3eLvVNWAVh0ET702R+WWlznzflz649yC2rgrpGT/Luf/Jx95i0BWHQ6fuqz75Op+hRTtWkS + SZp40BU5L0efX+CrP/uEfe6tBbDoZEzXp6lE1aaCH0AQwiSkkTT4+f/7p60dsARg0an4n7/638lUfWqF + 528GkYmITcx8WLI30RKARadirHyxZfdaCss8M37K3kRLABadilpcR6R1FS9YB2AJwKJj0YjDLZ/jN/7s + EcsClgAsehVxPMW/e+wesQRgYdFhyHqZLZ9jLkxnBCwBWFh0GHw3j1Jbm8qfjxLiJLYEYB8ni05D5J1E + tljDdr4u1AxEz5wQSwAWFh2EP/jALyuji6BatwIToVBN7L107S2w6ESIsw9QENdbOr6aQN3Q8yRgFYBF + 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 + GBgsBv8PNAlKsBafvBgAAAAASUVORK5CYII= + + \ No newline at end of file diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs index 77436f6f..cfecfc7f 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs @@ -28,6 +28,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NeuropixelsV2eHeadstageDialog)); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPageNeuropixelsV2e = new System.Windows.Forms.TabPage(); this.panelNeuropixelsV2e = new System.Windows.Forms.Panel(); @@ -54,20 +55,20 @@ private void InitializeComponent() 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.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.tabControl1.Margin = new System.Windows.Forms.Padding(2); this.tabControl1.Name = "tabControl1"; this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(863, 467); + this.tabControl1.Size = new System.Drawing.Size(863, 469); this.tabControl1.TabIndex = 0; // // tabPageNeuropixelsV2e // this.tabPageNeuropixelsV2e.Controls.Add(this.panelNeuropixelsV2e); this.tabPageNeuropixelsV2e.Location = new System.Drawing.Point(4, 22); - this.tabPageNeuropixelsV2e.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.tabPageNeuropixelsV2e.Margin = new System.Windows.Forms.Padding(2); this.tabPageNeuropixelsV2e.Name = "tabPageNeuropixelsV2e"; - this.tabPageNeuropixelsV2e.Padding = new System.Windows.Forms.Padding(2, 2, 2, 2); - this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(855, 441); + this.tabPageNeuropixelsV2e.Padding = new System.Windows.Forms.Padding(2); + this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(855, 443); this.tabPageNeuropixelsV2e.TabIndex = 0; this.tabPageNeuropixelsV2e.Text = "NeuropixelsV2e"; this.tabPageNeuropixelsV2e.UseVisualStyleBackColor = true; @@ -76,19 +77,19 @@ private void InitializeComponent() // this.panelNeuropixelsV2e.Dock = System.Windows.Forms.DockStyle.Fill; this.panelNeuropixelsV2e.Location = new System.Drawing.Point(2, 2); - this.panelNeuropixelsV2e.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.panelNeuropixelsV2e.Margin = new System.Windows.Forms.Padding(2); this.panelNeuropixelsV2e.Name = "panelNeuropixelsV2e"; - this.panelNeuropixelsV2e.Size = new System.Drawing.Size(851, 437); + this.panelNeuropixelsV2e.Size = new System.Drawing.Size(851, 439); this.panelNeuropixelsV2e.TabIndex = 0; // // tabPageBno055 // this.tabPageBno055.Controls.Add(this.panelBno055); this.tabPageBno055.Location = new System.Drawing.Point(4, 22); - this.tabPageBno055.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.tabPageBno055.Margin = new System.Windows.Forms.Padding(2); this.tabPageBno055.Name = "tabPageBno055"; - this.tabPageBno055.Padding = new System.Windows.Forms.Padding(2, 2, 2, 2); - this.tabPageBno055.Size = new System.Drawing.Size(855, 441); + this.tabPageBno055.Padding = new System.Windows.Forms.Padding(2); + this.tabPageBno055.Size = new System.Drawing.Size(855, 443); this.tabPageBno055.TabIndex = 1; this.tabPageBno055.Text = "Bno055"; this.tabPageBno055.UseVisualStyleBackColor = true; @@ -97,9 +98,9 @@ private void InitializeComponent() // this.panelBno055.Dock = System.Windows.Forms.DockStyle.Fill; this.panelBno055.Location = new System.Drawing.Point(2, 2); - this.panelBno055.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.panelBno055.Margin = new System.Windows.Forms.Padding(2); this.panelBno055.Name = "panelBno055"; - this.panelBno055.Size = new System.Drawing.Size(851, 437); + this.panelBno055.Size = new System.Drawing.Size(851, 439); this.panelBno055.TabIndex = 0; // // splitContainer1 @@ -107,7 +108,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, 24); - this.splitContainer1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(2); this.splitContainer1.Name = "splitContainer1"; this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; // @@ -120,7 +121,7 @@ private void InitializeComponent() this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); this.splitContainer1.Panel2.Controls.Add(this.buttonOkay); this.splitContainer1.Size = new System.Drawing.Size(863, 503); - this.splitContainer1.SplitterDistance = 467; + this.splitContainer1.SplitterDistance = 469; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 1; // @@ -129,7 +130,7 @@ private void InitializeComponent() 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(747, 3); - this.buttonCancel.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(108, 26); this.buttonCancel.TabIndex = 6; @@ -140,7 +141,7 @@ private void InitializeComponent() // this.buttonOkay.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonOkay.Location = new System.Drawing.Point(625, 3); - this.buttonOkay.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.buttonOkay.Margin = new System.Windows.Forms.Padding(2); this.buttonOkay.Name = "buttonOkay"; this.buttonOkay.Size = new System.Drawing.Size(108, 26); this.buttonOkay.TabIndex = 5; @@ -174,8 +175,9 @@ private void InitializeComponent() this.Controls.Add(this.splitContainer1); this.Controls.Add(this.menuStrip1); this.DoubleBuffered = true; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip1; - this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.Margin = new System.Windows.Forms.Padding(2); this.Name = "NeuropixelsV2eHeadstageDialog"; this.Text = "NeuropixelsV2eHeadstageDialog"; this.tabControl1.ResumeLayout(false); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.resx index d5494e30..88a9078e 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.resx +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.resx @@ -120,4 +120,1659 @@ 17, 17 + + + + AAABAA4AEBAQAAEABAAoAQAA5gAAABAQAAABAAgAaAUAAA4CAAAQEAAAAQAgAGgEAAB2BwAAICAQAAEA + BADoAgAA3gsAACAgAAABAAgAqAgAAMYOAAAgIAAAAQAgAKgQAABuFwAAMDAQAAEABABoBgAAFigAADAw + AAABAAgAqA4AAH4uAAAwMAAAAQAYAKgcAAAmPQAAMDAAAAEAIACoJQAAzlkAAEBAAAABABgAKDIAAHZ/ + AABAQAAAAQAgAChCAACesQAAAAAAAAEAGABpMQAAxvMAAAAAAAABACAAZ10AAC8lAQAoAAAAEAAAACAA + AAABAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAgAAAgICAAACAgADAwMAA//8AAAD/ + /wAAAP8A/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUiAAAAAAAFU5YAAA + AAVVADYgAAAFVQAGYlAAVTUAVTUjMAM1VVVTIDOABVAAAAYDRHAAVQAAYzhlAAAFUAY4AFcAAABVA0AA + NwAAAAUDAAZAAAAAAAAAA0AAAAAAAAACAAAAAAAAAAEAAAAAAAAAAAAA//8AAP/xAAD/wQAA/jEAAPjh + AADDAAAAgBEAAJ+hAADPAwAA5jMAAPJzAAD45wAA/+cAAP/vAAD/7wAA//8AACgAAAAQAAAAIAAAAAEA + CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI5YzAEWaLgCFpi8AJLioAL2exQA9QTAAO12VANK0 + LwAws5AAzarWAGZsPgA+Z9UAdLRpACbQ+wDdtS8Ak41jAEBr6QDAtkIA37YvAKmdbgA/auoAR27gANKu + MgA8zeMAu5vDAMiq0ABLXUwATaKmAHpvfACWlJUAqqmpALe2tgDGr2QAxKAqAHt6RQA+XowAHJXwALW0 + tACysbEAtLOzALe1swCrjzcAsp5eAK2ecQCQgU8ALk93AA09OADctDEAzqw0AN61LgDbrikA3bMsAMWs + XgC/gxAA1qYjALazrQCMYSEAnGUNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg5 + OgAAAAAAAAAAAAAAICA1NjcAAAAAAAAAACAgIAAAMjM0AAAAAAAAICAgAAAAExMwMQAAACYnKCAAACAp + KissLS4vAB0eHyAgICAgISIAIyQlAAAZGgAAAAAAABMAFRscDgAAAAoKAAAAABMUFRYXGAAAAAAACgoA + AA8QEQAAEg4AAAAAAAAKCgALDAAAAA0OAAAAAAAAAAUGBwAAAAgJAAAAAAAAAAAAAAAAAAADBAAAAAAA + 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 + ABMAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + //////+H///+B///+AP//8AD//8HA//4HgP/4HwB/wP8ifwP+AHwAAABgAABAYAH5wOH/8YD4/+MB/H/ + kAf4/wEH/H4DD/48Dg//HB4f/4h+H//A/h//4f4///f+P////H////x////8f////P////z///////// + //8oAAAAIAAAAEAAAAABAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULgAmmTwAIpQuACGX + NwBNnC4AIZQvACW/wACPqC8AJLmsANS0LwAxly4AI6+JAD5CNwB1oy4AIqNhACbQ+wCchaAALDMkAC02 + KQA8X6cAwbEvACOZPgAmz/cAzarWAMmn0QBmWUwAM0MoADhQRQA+aN0A37YvAFKdLgAlyN8Ano0tAEFS + KwA6V3AAP2rpAJ+qLwAkvLcAxqYuAFliLAA8X6QAP2rqANm1LwA1tJAA2bIvAIF8OAA/Z9AAfLVpAKyb + VgBDbOUAxbZAACbQ+gDCqFEAPszgAEFr5gCrlzoAf8OZADpUWgCAei0AzLE5AIhxjgCkiKsAt5e/AMKn + yQA6WX8AN0wrAEFXUAAsuPcAaVZtAHZpeQCbmZoAsK+vALe2tgBWaGgAPE8rADpt6QAcpPMAdGJ3AIB2 + ggCKiYkAhoWFAIWEhACTkpIAqKenALa1tQCynl4AsI8lALSSJgCxp4oAwLKGAKqRKwBIVSwAPWXVABlb + 5AAUg+0AJMf5ALOysgCrqqoArq2tALazrwCkizoAoIIiAKSMPgC2tLEAoYQpAJl9IgBZZocAIkmpAAY3 + aAALRk0Au5goALaVJwC4pGMAp44+AKKEIgCsnnMAgIqoABAyJAAGLB4A3LMuANayOgC0mSoAmIsqAN60 + LQDftS8A3rQuANOgHwDarSgA2aomANK0VADetS8AyY0RAMyTFgCwlUEAtYMTALJqBQDNlRcAs62dAJdy + HAB9RAEAo2kMAKSQawB6QgEAgUoFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASY6PkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASUmKi4yNAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASUlJaIaHiIkeAAAAAAAAAAAAAAAAAAAAAAAAAABJSUlJSUmCg4SFfx4AAAAA + AAAAAAAAAAAAAAAAAABJSUlJSQAAAB58f4CBHgAAAAAAAAAAAAAAAAAAAElJSUlJSQAAAAAeHnx9Hn4e + AAAAAAAAAAAAAAAAAElJSUlJSQAAAAAAHh4eHh4eHh4eAAAAAAAAAAAASUlJSUlJAAAAAAAAAAAeHgAe + eHkAensAAAAAAAAASUlJSUlJAAAAAAAAAAAAb3BxSXJzdHV2dwAAAAAAVWFIYmNVSUlJSUlJSUlJZGVm + Z2hoaWprbG1uAABOT1BRUlNUVUlJSUlJSUlJSUlWV1hZAFpbXF1eX2AAAEVFRkdISUlJSUlJSQAAAAAA + AB4eAAAASkscTE0QAAAAPT4/QAAAAAAAAAAAAAAAAAAeHh4AACpBQkNEEBAAAAAAABgYGAAAAAAAAAAA + AAAAHh4eAAAqKjo7PBAQAAAAAAAAABgYGAAAAAAAAAAAAAAeHgAqKio3OB45EBAAAAAAAAAAABgYGAAA + AAAAAAAAHh41KioqKgAeHjYQEAAAAAAAAAAAABgYGAAAAAAAAB4eMTIqKioAAB4zNBAAAAAAAAAAAAAA + ABgYGAAAAAAeLS4vKioAAAAeHjAQEAAAAAAAAAAAAAAAABgYGAAAACcoKSoqAAAAAB4rLBAAAAAAAAAA + AAAAAAAAABgYGAAhIiMkAAAAAAAAHiUmEAAAAAAAAAAAAAAAAAAAABgZGhscHQAAAAAAAAAeHyAQAAAA + AAAAAAAAAAAAAAAAABESExQAAAAAAAAAABUWFwAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAADg8Q + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoLDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + CAEJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////// + /4f///4H///4A///wAP//wcD//geA//gfAH/A/yJ/A/4AfAAAAGAAAEBgAfnA4f/xgPj/4wH8f+QB/j/ + AQf8fgMP/jwOD/8cHh//iH4f/8D+H//h/j//9/4////8f////H////x////8/////P///////////ygA + AAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsqqXDIxaEWuPWw1DAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AADwf//DAI8AAPg//4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/w + eAfgfwAA//gwH+B/AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/ + AAD/////g/8AAP////+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP// + /////wAA////////AAD///////8AACgAAAAwAAAAYAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIZQuAC2cQQA4nT8AI5xHACSVLgAhlzgAUp0uACGULwAlwMQAlqkvACS4qADXtS8AMZcuACOs + gQAm0PsA37YvAHmkLgAioVoAJtD6AMCwLwAmlS4AJs71AD1BNwA7QDQAVJ0uACXF1wCJd4sALDMkAC44 + LwA8YbUAoqsvACS6rwDNqtYAq5CwAC81KAAvOyYAOFJRAD9p4gDYtS8AN5guACOviQCbfXkAQEMmADZL + KwA3TCsAOll/AD9q6QB9pC4AIqNgAMypzQDHpS8AaGssADhMKwA3TC0APGGyAD9q6gDGsi8AJptCACbN + 8gDbtC8AkoYtADtPKwA4T0AAPmfWAFieMAAlx9wAu58uAFBbLAA5VWIAP2roAKWsLwAlvLcA0q4vAHFx + LQA8XZUA2bUvAD61jwDetS8Am4w1AEVpxACCtmkAwKZJAEpv3QDItj0AJ9D5ANKwPgBAaukA3rYvAELM + 3ACFw5IAP2rmAHV3SADUry8A2bc2ADpWaABTXiwAvaAuADtbjQA9UCsAioEvAIBqhQCKcpAAoISnAK+R + tgC7ocEAPWGxADhNLQA+ZtMAKcP5AHdlewBpVm0AeWp8AKGeoAC0s7MAt7a2ADlOLQA9YrkANHLrAB+y + 9gB2ZHoAcmV0AIKBgQCDgoIAh4aGAJqZmQCura0AyrFhANCpLADUrS0A2bEuANy0LgCymS4ATVorADtc + kgA+auoAElznABiU8QAlzfsAeWh8AIh+iQCRkJAAjYyMAImIiACGhYUAhYSEAISDgwCmpaUAtbS0ALe1 + tACnkU0AoYMiAKKEIgCkhSMArqB3AMCjRQC4mCkAXmEoADdSaQA9ZuIAJl/kAAlV5AARdusAIsH4ALGw + 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 + AAAAAAAAAAAAAAAAAAAAAAAAISEhISEAAAAAEENERUY4ODgAAAAAAAAQEEdIDw8AAAAAAAAAAAAAAAAA + AAAAAAAAACEhISEhAAA8PT4/QDg4AAAAAAAAAAAQEEFCDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAhISEh + MjM0NTY3ODgAAAAAAAAAAAAQOTo7DwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISEhKissLS4vAAAAAAAA + AAAAABAQMDEPDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiIxwkJSYAAAAAAAAAAAAAABAnKCkPDwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbHBwdHgAAAAAAAAAAAAAAABAfASAPAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAFxgAAAAAAAAAAAAAAAAAABAZARoPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAABQVBhYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAEBEBEhMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA0BDg8AAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgEBCwAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwEICQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAABQEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAADAQEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////AAD///////8AAP//////nwAA//////4f + AAD/////8A8AAP/////ADwAA/////gAPAAD////4AAcAAP///+AQBwAA////AHAHAAD///wD4AcAAP// + 4A/AAwAA//+AP8AjAAD//AH/hCMAAP/wB/8MIQAA/8A//gABAAD+AP4AAAEAAPgAAAAAAQAAwAAAAAAD + AACAAAAQeAMAAIAA//D4BwAAwf//4eAHAADg///DwIcAAPB//8MAjwAA+D//hgAPAAD8H/8MEB8AAP4P + /wAwHwAA/wf+AHA/AAD/w/wB4D8AAP/h+APgPwAA//B4B+B/AAD/+DAf4H8AAP/8AD/g/wAA//4A/8D/ + AAD//wH/wP8AAP//g//B/wAA///P/8H/AAD/////w/8AAP////+D/wAA/////4P/AAD/////h/8AAP// + //+H/wAA/////4//AAD/////D/8AAP////+P/wAA////////AAD///////8AAP///////wAAKAAAADAA + AABgAAAAAQAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIpWC49bDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKdLiGULiGULyXAxAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + ACSVLiGULiGXOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAADidPyGULiGULiOcRwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULiGULi2cQQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////wAA//////// + AAD//////58AAP/////+HwAA//////APAAD/////wA8AAP////4ADwAA////+AAHAAD////gEAcAAP// + /wBwBwAA///8A+AHAAD//+APwAMAAP//gD/AIwAA//wB/4QjAAD/8Af/DCEAAP/AP/4AAQAA/gD+AAAB + AAD4AAAAAAEAAMAAAAAAAwAAgAAAEHgDAACAAP/w+AcAAMH//+HgBwAA4P//w8CHAADwf//DAI8AAPg/ + /4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/weAfgfwAA//gwH+B/ + AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/AAD/////g/8AAP// + //+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP///////wAA//////// + AAD///////8AACgAAAAwAAAAYAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + ogYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYyt7a2mre2tu+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tvm3traqt7a2Q7e2tgO3trYIt7a2ILe2tji3trZPt7a2aLe2toC3traXt7a2sLe2tsm3trbat7a27Le2 + tvqwp4v/oIIj/6CCIv+ggiL/pY5D/7a0sf+3trb/r6WG/6CCIv+ggiL/oYUq/5udpv9EXqr/FD1//wYs + Hv8GLB7/DDQp7WSTmw0AAAAAAAAAAAAAAAC3trYNt7a2XLe2tsa3trb+tLOz/7GwsP+ura3/qaio/6Sj + o/+mpaX/srGx9Le2tsC3tra/t7a21be2tu+3trb+t7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7Swpf+ihzH/oIIi/6CCIv+hhSv/tK+h/7e2tv+3trb/q5to/6CCIv+fgSL/bWU3/zJQ + p/8qTKr/CECp/wY5bf8JQ1b/GXyMigAAAAAAAAAAkoKVGXlofJOIfonkkZCQ/42MjP+JiIj/hoWF/4WE + hP+Eg4P/kZCQ/6alpf+1tLT/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7W0/6eRTf+hgyL/ooQi/6SFI/6uoHfgt7a2xLe2tq63traWwKNF4riY + Kf9eYSj/N1Jp/z1m4v8mX+T/CVXk/xF26/8iwfj5JtD7JgAAAAAAAAAAdmR6sWlWbf9pVm3/cmV0/4KB + gf+DgoL/h4aG/5qZmf+ura3/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb5t7a25be2ts23tra3t7a2n7e2toe3trZvyrFhudCpLP/UrS3/2bEu/9y0LqTfti8CAAAAAAAA + AAAAAAAAspku501aK/83TCv/O1yS/z5q6v8SXOf/GJTx/yXN+/8m0Pu4JtD7AgAAAAAAAAAAd2V7xmlW + bf9pVm3/aVZt/3lqfP+hnqD/tLOz/7e2tv+3trb+t7a287e2tua3trbYt7a2wLe2tqm3traQt7a2eLe2 + tmG3trZIt7a2Mbe2thi3trYGt7a2A7e2tgEAAAAAAAAAAAAAAADfti8z37Yv99+2L//fti//37Yv2d+2 + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2LwHXtS/DMZcu/yGU + Lv8jrIH/JtD7oQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + LwyWqS/qIZQu/yGULv8kuKj/JtD7OgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANW0LydSnS7/IZQu/yGUL/8lwMTOJtD7AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIClL1kklS7/IZQu/yGXOP8lx9xqAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADidP5QhlC7/IZQu/yOcR/Am0PsUAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADefRX8hlC7/IZQu/y2c + QaEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKr + YQgxnD9zLZo6fFeuZhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP///////wAA//////4PAAD/////+A8AAP// + ///ADwAA/////wAHAAD////4AAcAAP///+AABwAA////gAAHAAD///wAAAMAAP//8ADAAwAA//+AA4AD + AAD//gAPgAEAAP/4AH8AAQAA/8AD/gABAAD/AA/gAAAAAPwAAAAAAAAA4AAAAAABAACAAAAAAAEAAIAA + AAA4AQAAgAAA4GADAACAB//AwAMAAMB//8GABwAA4D//gAAHAADwH/8AAA8AAPgP/gAADwAA/Af+AAAP + AAD+A/wAIB8AAP8A+ADgHwAA/4BwAcA/AAD/4DADwD8AAP/wAA/APwAA//gAH8B/AAD//AA/wH8AAP/+ + AP/A/wAA//8B/4D/AAD//4P/gP8AAP//z/+B/wAA/////4H/AAD/////A/8AAP////8D/wAA/////wP/ + AAD/////B/8AAP////8H/wAA/////w//AAD/////D/8AAP///////wAA////////AAAoAAAAQAAAAIAA + AAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjV0YiVQKi1YLAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2n4Ve + ekIBekIBekIBh1IIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2squWjWEUekIBekIBekIBiVQJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAt7a2t7a2t7a2t7a2trOupIs9i14LekIBekIBekIBpG8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7W1qZddn4EhjF0HfEUBhUgBnloDz5kb37YvAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2r6SCoIMkoX8eomwIqGMErF8Cv3wI1qUj + 37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2tK+grpAzw58pzp0gw4IJ + unQGuXMGw4IJ268q37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uLaz0LBH + 3bQv37Yv0Joaw4IJyY0SxIMKxocN3rQu37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2AAAAAAAA37Yv37Yv37UvypATx4kO3LAryY4Ry5AT37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2AAAAAAAAAAAAAAAA37Yv37Yv37Yv3rQtxogO2Kgl37Yv0Zwc0Joa37Yv37Yv37YvAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv27Aq0Jsb37Yv37Yv2asn1aMh37Yv + 37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv264p3rQu37Yv + 37Yv3rUu268q37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv + 37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37Yv + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAA37Yv37Yv37Yv37YvAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA3rUv2rIu1q8t + 0qwuAAAAAAAAv6hesJMnYWclOlIjdXcnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA168t1a4t0KosyqUqxKAqw61ot7a2t7a2 + r51kp4gjpYYjo4Ujo4gvtbGpt7a2l5iXLEAjBiweBiweBy0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7WzqZNLpIUjoIIioIIioIIi + ppBKtrSwt7a2t7a2qJNToIIioIIioIIipIw+tbS1gIywMlCkCjAzBiweBiweBiweHkc/AAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2rqF6oIIi + oIIioIIioIIioocvtK+it7a2t7a2t7W0o4k2oIIioIIioIIino5aXXGsLUynHkamBjRgBiweBiweBi0g + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2tLOzsrGxrq2tq6qqp6amoqGhnJubnp2dq6qq + tLOzt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2sqyYoYUroIIioIIioIIioIIjr6WFt7a2t7a2t7a2tLCkoYUqoIIioIIigm8hPE5uLUynLk6rD0Os + BkGrBzt4CEBgF4SdAAAAAAAAAAAAAAAAAAAAg3SGkImRmZeYlZSUkI+Pi4qKiYiIh4aGhYSEg4KChIOD + j46OpKOjs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2trOtpY1CoIIioIIioIIioIIiqZZct7a1t7a2t7a2t7a2t6uHuZcouZgoeXEoN0gn + OV2zPGbgMWPiClXiCVXkDmzqILf3Js/7AAAAAAAAAAAAAAAAcmB2aVZtaVZtcmV0goCBg4KCg4KCg4KC + g4KChoWFmJeXrKurtrW1t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uKNitpUnvJkowp4pyKMqyqUsAAAAAAAAAAAAAAAAAAAAAAAA + zasvamwsOEwrN042PmfYP2rqH17oCVjmFIXuJMf6JtD7AAAAAAAAAAAAAAAAAAAAaVZtaVZtaVZtaVZt + cGJzgX+Bg4KCjIuLoJ+fs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3rYvV54uIZQuIZQuJcPP + JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwrEv + KZUuIZQuIZUxJs70JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve6QuIZQuIZQuIp9SJtD6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA17UvM5cuIZQuIZQuI6p6JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnaovIZQuIZQuIZQuJLWgAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV54uIZQuIZQuIZUwJcDDAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ5UuIZQu + IZQuIZc5JcXXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAARZ46IZQuIZQuIZQuIpxIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM5xAIZQuIZQuIZQuJ6BTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOaBHIZQuIZQuIpQvAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOJ9GI5UwAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/////////////////////////////////////////H/////////wP/// + /////4A////////+AD////////gAH///////wAAf//////8AAB//////+AAAD//////gAYAP/////4AP + AA/////8AD4AD/////AB/AAH////gAf8AAf///4AP/ggh///+AD/8CCD///AA//gYYP//wAf/+Dhg//4 + AH//wAAD/+AD//gAAAH/AA/AAAAAA/wAAAAAAAAD4AAAAAAAAAPAAAAAAA/AB8AAAAP8H4AHwAAP//g/ + AA/AP///8D4AD+A////wfAYP+B///+DwBB/8D///wOAEH/4H//+BwAQ//wP//4MAAD//gf//BgMAP//A + f/4EBwB//+A//gAOAH//8B/8AD4A///4D/gAfgD///wH8AD+Af///gPwA/4B////AeAH/AH////AwA/8 + A////+AAP/wD////8AB//Af////4Af/8B/////wD//gH/////gf/+A//////H//4D/////////gf//// + ////+B/////////4H/////////A/////////8D/////////wf/////////B/////////8H/////////g + /////////+D/////////4f/////////z//////////////////////////////////8oAAAAQAAAAIAA + AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKpUACs4ckEMObMAgAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyqpcwjV0YpYlU + CvSLVgvSmWcTMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dre2 + tli3trbBn4Ve/HpCAf96QgH/ekIB/4dSCM3FnzkGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYlt7a2g7e2tt63trb9squW/41hFP96QgH/ekIB/3pCAf+JVAn4yZ0oIwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2Bre2tkW3trast7a28re2tv+3trb/trOu/6SLPf+LXgv/ekIB/3pCAf96QgH/pG8P/9+2 + L1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYVt7a2b7e2ttW3trb+t7a2/7e2tv+3trb/t7W1/6mXXf+fgSH/jF0H/3xF + Af+FSAH/nloD/8+ZG//fti+iAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2BLe2tjm3trabt7a277e2tv+3trb/t7a2/7e2tv+3trb/t7a2/6+k + gv+ggyT/oX8e/6JsCP+oYwT/rF8C/798CP/WpSP/37Yv4N+2LwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYSt7a2Xre2tsO3trb4t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7SvoP+ukDP/w58p/86dIP/Dggn/unQG/7lzBv/Dggn/268q/9+2L/vfti88AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Are2tiS3traJt7a24re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2+Li2s7TQsEfr3bQv/9+2L//Qmhr/w4IJ/8mNEv/Egwr/xocN/960 + Lv/fti//37YvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2T7e2trO3trb6t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbgt7a2g7m2sSLfti9937Yv/9+2L//ftS//ypAT/8eJ + Dv/csCv/yY4R/8uQE//fti//37Yv/9+2L8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tiG3trZ2t7a22be2 + tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a297e2try3trZat7a2DAAAAADfti8/37Yv9t+2 + L//fti//3rQt/8aIDv/YqCX/37Yv/9GcHP/Qmhr/37Yv/9+2L//fti/637YvHAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2Obe2 + tqS3trbtt7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv63trbqt7a2lre2tjO3trYEAAAAAAAA + AADfti8W37Yv1d+2L//fti//37Yv/9uwKv/Qmxv/37Yv/9+2L//Zqyf/1aMh/9+2L//fti//37Yv/9+2 + L2MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgy3trZlt7a2xre2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2ttK3trZqt7a2FgAA + AAAAAAAAAAAAAAAAAADfti8C37Yvo9+2L//fti//37Yv/9+2L//brin23rQu/9+2L//fti//3rUu/duv + KvDfti//37Yv/9+2L//fti+p37YvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAC3trYBt7a2L7e2to+3trbut7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbyt7a2pre2 + tkC3trYBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvYt+2L/zfti//37Yv/9+2L//fti/p37YvrN+2 + L//fti//37Yv/9+2L/Dfti+m37Yv/9+2L//fti//37Yv6d+2Lw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAALe2tg63trZSt7a2u7e2tva3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/re2 + ttq3trZ8t7a2ILe2tgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvKN+2L+/fti//37Yv/9+2 + L//fti/837YvYN+2L7Xfti//37Yv/9+2L//fti/G37YvV9+2L/7fti//37Yv/9+2L//fti9DAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYCt7a2H7e2tn63trbZt7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb3t7a2ure2tlO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvDN+2 + L8Lfti//37Yv/9+2L//fti//37Yvod+2LxHfti/b37Yv/9+2L//fti//37YvlN+2Lxbfti/037Yv/9+2 + L//fti//37YvjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgK3trZBt7a2pre2tvS3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tuy3traPt7a2Lre2tgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2L4Xfti//37Yv/9+2L//fti//37Yv3d+2LxDfti8i37Yv9d+2L//fti//37Yv/9+2 + L2QAAAAA37Yvv9+2L//fti//37Yv/9+2L9Dfti8HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Fre2tmy3trbUt7a2/re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb9t7a2xbe2tmO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L0rfti/337Yv/9+2L//fti//37Yv9N+2Lz4AAAAA37YvS961 + L//asi7/1q8t/9KsLv3Esndyt7a2X7+oXrqwkyf/YWcl/zpSI/91dyf4sp0sKwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgS3trY0t7a2lre2tum3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tu23traht7a2O7e2tgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2CMasViTXry3f1a4t/9CqLP/KpSr/xKAq/8Ot + aNu3traft7a2tq+dZN+niCP/pYYj/6OFI/+jiC//tbGp/7e2tv+XmJf/LEAj/wYsHv8GLB7/By0e/yhO + On8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dbe2tli3tra/t7a2+Le2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb8t7a22re2tnW3trYeAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYFt7a2Cbe2tg23trYbt7a2Nbe2tky3trZlt7a2fLe2tpS3tratt7a2xLe1s9Spk0vspIUj/6CC + Iv+ggiL/oIIi/6aQSv+2tLD/t7a2/7e2tv+ok1P/oIIi/6CCIv+ggiL/pIw+/7W0tf+AjLD/MlCk/wow + M/8GLB7/Biwe/wYsHv8eRz+4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYjt7a2hLe2 + tuG3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tvu3trawt7a2Ure2tiC3trYvt7a2Rbe2 + tlq3trZ0t7a2i7e2tqK3tra6t7a2zbe2tuO3trb2t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+uoXr/oIIi/6CCIv+ggiL/oIIi/6KHL/+0r6L/t7a2/7e2tv+3tbT/o4k2/6CCIv+ggiL/oIIi/56O + Wv9dcaz/LUyn/x5Gpv8GNGD/Biwe/wYsHv8GLSD/G0xIcgAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Cbe2 + tkm3travt7a297e2tv+0s7P/srGx/66trf+rqqr/p6am/6Khof+cm5v/np2d/6uqqv60s7Pht7a25Le2 + tvW3trb4t7a2+7e2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+yrJj/oYUr/6CCIv+ggiL/oIIi/6CCI/+vpYX/t7a2/7e2tv+3trb/tLCk/6GF + Kv+ggiL/oIIi/4JvIf88Tm7/LUyn/y5Oq/8PQ6z/BkGr/wc7eP8IQGD/F4Sd4ym42xYAAAAAAAAAAAAA + AACTg5Ygg3SGgJCJkdOZl5j7lZSU/5CPj/+Lior/iYiI/4eGhv+FhIT/g4KC/4SDg/+Pjo7/pKOj/7Oy + sv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+2s63/pY1C/6CCIv+ggiL/oIIi/6CCIv+pllz6t7a18be2 + tui3trbgt7a2zberh825lyj/uZgo/3lxKP83SCf/OV2z/zxm4P8xY+L/ClXi/wlV5P8ObOr/ILf3/ybP + +5EAAAAAAAAAAAAAAACLeo4acmB23mlWbf9pVm3/cmV0/4KAgf+DgoL/g4KC/4OCgv+DgoL/hoWF/5iX + l/+sq6v/trW1/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/re2tvy3trb6t7a2+Le2tuy3trbWuKNi9baVJ/+8mSj/wp4p/8ij + Kv/KpSzUtaqGN7e2tiO3trYTt7a2BAAAAADfti90zasv/2psLP84TCv/N042/z5n2P8/aur/H17o/wlY + 5v8Uhe7/JMf6/ybQ+/wm0PsvAAAAAAAAAAAAAAAAjHqPcWlWbf9pVm3/aVZt/2lWbf9wYnP/gX+B/4OC + gv+Mi4v/oJ+f/7Oysv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb9t7a27Le2tta3trbDt7a2rre2tpa3trZ+t7a2Zre2tk+3trY4t7a2I7e2tg4AAAAA37YvTt+2 + L/zfti//37Yv/9+2L//fti/y37YvNwAAAAAAAAAAAAAAAAAAAAAAAAAAlIczp0pYK/83TCv/N0wr/zhR + Tv8/aeT/O2nq/w9g6P8cpPT/Js/7/ybQ+/8m0Pu9JtD7BAAAAAAAAAAAAAAAAI59kVVrWG/+aVZt/2lW + bf9pVm3/a1hv/31sgP+moqX/tbS0/7e2tv+3trb/t7a2/7e2tve3trbot7a227e2ts63tra3t7a2obe2 + toe3trZvt7a2Wbe2tkG3trYmt7a2Ebe2tgy3trYHt7a2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37YvH9+2L+Pfti//37Yv/9+2L//fti/+37YvcN+2LwEAAAAAAAAAAAAAAAA/auoMP2rqgzlR + SPs3TCv/N0wr/zdMK/86V27/P2rq/y567PchvPj/JtD7/ybQ+/8m0Pv+JtD7WQAAAAAAAAAAAAAAAAAA + AACjk6UJfmqBoHFddvx0X3n/hm+M/5p/of+mia3/s5q5/ruyvZu3trZht7a2Sre2tjG3trYit7a2F7e2 + tg23trYDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37YvCN+2L7Lfti//37Yv/9+2L//fti//37Yvst+2LwcAAAAAAAAAAAAA + AAA/auonP2rqvD9p4f43TC7/N0wr/zdMK/83TCv/O1yQ+D5v65YnyfrTJtD7/ybQ+/8m0Pv/JtD76CbQ + +wsAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gPCocuAyafS/c2q1v/Nqtb/zarW/82q1v/NqtbXzarWKwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L3Lfti/937Yv/9+2L//fti//37Yv5t+2 + LxwAAAAAAAAAAD9q6gE/aupSP2rq5z9q6v89ZMH/N0wr/zdMK/83TCv/RFQr/2Nxbmonzfs8JtD7+ybQ + +/8m0Pv/JtD7/ybQ+40AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1m7Nqtb4zarW/82q + 1v/Nqtb/zarW/82q1ubNqtY2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lznfti/z37Yv/9+2 + L//fti//37Yv+N+2L00AAAAAAAAAAD9q6g4/auqSP2rq/z9q6v8/aur/PF2Z/zdMK/83TCv/ZGgs/8uq + L/7fti8SJtD7oCbQ+/8m0Pv/JtD7/ybQ+/Qm0PstAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADNqtYCzarWXc2q1vTNqtb/zarW/82q1v/Nqtb/zarW682q1kMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + Lw3fti/W37Yv/9+2L//fti//37Yv/9+2L43fti8CAAAAAD9q6i8/aurKP2rq/j9q6v8/aur/P2rq/zpY + d/89UCv/jIIt/9myL//fti/cQMzeHibQ++sm0Pv/JtD7/ybQ+/8m0Pu6JtD7AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gHNqtZNzarW8s2q1v/Nqtb/zarW/82q1v/NqtbwzarWUM2q + 1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2LwTfti+Y37Yv/9+2L//fti//37Yv/9+2L8nfti8PP2rqBD9q6mI/aursP2rq/z9q + 6v8/aur/P2rq/z9p5v9NX1b/spou/961L//fti//37YvrifQ+m8m0Pv/JtD7/ybQ+/8m0Pv+JtD7VgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1kDNqtbtzarW/82q + 1v/Nqtb/zarW/82q1vfNqtZeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti9V37Yv+9+2L//fti//37Yv/9+2L/Lfti8sP2rqET9q + 6p4/aur+P2rq/z9q6v8/aur/P2rq/z9q6v9Lb9m+zaw299+2L//fti//37Yv/9y2MoEm0PvUJtD7/ybQ + +/8m0Pv/JtD73ybQ+xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAzarWNM2q1t7Nqtb/zarW/82q1v/Nqtb/zarW/M2q1nHNqtYCAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8m37Yv6N+2L//fti//37Yv/9+2 + L/3fti9oQGrpOj9q6tU/aur/P2rq/z9q6v8/aur/P2rq/z9q6vI/aupv0rA+Mt+2L//fti//37Yv/9+2 + L/6LwouOJtD7/SbQ+/8m0Pv/JtD7/ybQ+4UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNqtYtzarW1c2q1v/Nqtb/zarW/82q1v/Nqtb6zarWg82q + 1gUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8J37Yvvd+2 + L//fti//37Yv/9+2L//ZszawRGzkdD9q6vM/aur/P2rq/z9q6v8/aur/P2rq/z9q6tQ/auo5P2rqAd+2 + L2Lfti//37Yv/9+2L//etjD3RsvWxCbQ+/8m0Pv/JtD7/ybQ+/km0PsjAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1iDNqtbQzarW/82q + 1v/Nqtb/zarW/82q1vzNqtaRzarWBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve9+2L/3fti//37Yv/9+2L//NrT/wVHTRxT9q6v4/aur/P2rq/z9q6v8/aur/P2rq/D9q + 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 + Lv8hlC7/JcPP/ybQ++wm0PsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADfti94wrEv/ymVLv8hlC7/IZUx/ybO9P8m0PukJtD7AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37Yvp3ukLv8hlC7/IZQu/yKfUv8m0Pr+JtD7PwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvCNe1L9Ezly7/IZQu/yGULv8jqnr/JtD7zibQ + +wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lxmdqi/0IZQu/yGU + Lv8hlC7/JLWg/ybQ+20AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADUtC87V54u/yGULv8hlC7/IZUw/yXAw/Qm0PsRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAk6kvbyeVLv8hlC7/IZQu/yGXOf8lxdegAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWeOqshlC7/IZQu/yGULv8inEj6Js/4PQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAznEDQIZQu/yGU + Lv8hlC7/J6BT0CbQ+wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAOaBHnCGULv8hlC7/IpQv/zmhTHEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKrYQ44n0aRI5UwtT2hS35suH0JAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////////////////////////8f/// + //////g/////////wB////////4AH///////+AAf///////gAB///////wAAD//////8AAAP/////+AA + AA//////gAAAD/////4AAgAH////8AAMAAf////AAHgAA////gAB+AAD///4AAfwAAP//8AAP+AAA/// + AAD/4ACB//wAB//AQAH/4AAf/wAAAf+AAPAAAAAB/gAAAAAAAAHwAAAAAAAAAcAAAAAAAAADgAAAAAAA + gAOAAAAACA+AA4AAAA/wDgAHgAB//+AcAAfAH///4DAAD/AP///AYAAP8Af//4BAAA/4Af//AAAAH/4B + //8AAAAf/wB//gAAAD//gD/8AAAAP//AH/wABgA//+AP+AAOAH//8AfwADwAf//4A+AAfAD///wB4AD8 + AP///gDAA/wB////AAAH/AH///+AAA/4Af///+AAP/gD////8AB/+AP////4AP/4A/////wD//AH//// + /gf/8Af/////H//wD/////////AP////////8A/////////wH////////+Af////////4D/////////g + P////////+B/////////4H/////////gf////////+D/////////4P////////////////////////// + ////////iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAgAElEQVR42u29eZRk133f97n3 + bbV19To9K2YADDAABrsAgYsIQhRNygSphZJNndhiZFG2adOREx2dJMfSsSImIn0sKUpkRfQRHZ1IpkxZ + NBcllEGJMSlCYkhQIIhlsGOAGcz0LD29d+1vub/88bp7umd6qa6uXqrqfs9pDKan3qt6r97ve7/f3/3d + 3wULCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC4uOgLK3wKLT + 8S8e+6hcKF9iqjbD+dIYAFppsm6WG4qHGc4M8W8/+Mf2WV8F2t4Ci07H67NnmaxNU4kqS78TEWITM1uf + 41LlMh/8o3eLvVNWAVh0ET702R+WWlznzflz649yC2rgrpGT/Luf/Jx95i0BWHQ6fuqz75Op+hRTtWkS + SZp40BU5L0efX+CrP/uEfe6tBbDoZEzXp6lE1aaCH0AQwiSkkTT4+f/7p60dsARg0an4n7/638lUfWqF + 528GkYmITcx8WLI30RKARadirHyxZfdaCss8M37K3kRLABadilpcR6R1FS9YB2AJwKJj0YjDLZ/jN/7s + EcsClgAsehVxPMW/e+wesQRgYdFhyHqZLZ9jLkxnBCwBWFh0GHw3j1Jbm8qfjxLiJLYEYB8ni05D5J1E + tljDdr4u1AxEz5wQSwAWFh2EP/jALyuji6BatwIToVBN7L107S2w6ESIsw9QENdbOr6aQN3Q8yRgFYBF + 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 + GBgsBv8PNAlKsBafvBgAAAAASUVORK5CYII= + + \ No newline at end of file diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs index 7a372fe1..b7e78591 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs @@ -36,6 +36,7 @@ private void InitializeComponent() System.Windows.Forms.Label label7; System.Windows.Forms.Label label6; System.Windows.Forms.Label labelPresets; + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NeuropixelsV2eProbeConfigurationDialog)); this.toolStripLabelProbeNumber = new System.Windows.Forms.ToolStripStatusLabel(); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -45,6 +46,7 @@ private void InitializeComponent() this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.splitContainer2 = new System.Windows.Forms.SplitContainer(); this.panelProbe = new System.Windows.Forms.Panel(); + this.panelTrackBar = new System.Windows.Forms.Panel(); this.trackBarProbePosition = new System.Windows.Forms.TrackBar(); this.panelChannelOptions = new System.Windows.Forms.Panel(); this.buttonClearCalibrationFile = new System.Windows.Forms.Button(); @@ -58,7 +60,6 @@ private void InitializeComponent() 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(); this.toolTip = new System.Windows.Forms.ToolTip(this.components); toolStripStatusLabelGain = new System.Windows.Forms.ToolStripStatusLabel(); toolStripStatusLabelProbe = new System.Windows.Forms.ToolStripStatusLabel(); @@ -78,6 +79,7 @@ private void InitializeComponent() this.splitContainer2.Panel2.SuspendLayout(); this.splitContainer2.SuspendLayout(); this.panelProbe.SuspendLayout(); + this.panelTrackBar.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).BeginInit(); this.panelChannelOptions.SuspendLayout(); this.panel1.SuspendLayout(); @@ -87,8 +89,8 @@ private void InitializeComponent() // toolStripStatusLabelGain.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); toolStripStatusLabelGain.Name = "toolStripStatusLabelGain"; - toolStripStatusLabelGain.Size = new System.Drawing.Size(38, 17); - toolStripStatusLabelGain.Text = "Gain: "; + toolStripStatusLabelGain.Size = new System.Drawing.Size(100, 17); + toolStripStatusLabelGain.Text = "Gain Correction: "; // // toolStripStatusLabelProbe // @@ -100,7 +102,7 @@ private void InitializeComponent() // probeCalibrationFile // probeCalibrationFile.AutoSize = true; - probeCalibrationFile.Location = new System.Drawing.Point(5, 9); + probeCalibrationFile.Location = new System.Drawing.Point(8, 9); probeCalibrationFile.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); probeCalibrationFile.MaximumSize = new System.Drawing.Size(133, 29); probeCalibrationFile.Name = "probeCalibrationFile"; @@ -111,7 +113,7 @@ private void InitializeComponent() // Reference // Reference.AutoSize = true; - Reference.Location = new System.Drawing.Point(12, 87); + Reference.Location = new System.Drawing.Point(8, 87); Reference.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); Reference.Name = "Reference"; Reference.Size = new System.Drawing.Size(57, 13); @@ -122,7 +124,7 @@ private void InitializeComponent() // label7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); label7.AutoSize = true; - label7.Location = new System.Drawing.Point(573, 0); + label7.Location = new System.Drawing.Point(1, 1); label7.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label7.Name = "label7"; label7.Size = new System.Drawing.Size(38, 13); @@ -133,7 +135,7 @@ private void InitializeComponent() // label6.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); label6.AutoSize = true; - label6.Location = new System.Drawing.Point(573, 442); + label6.Location = new System.Drawing.Point(4, 443); label6.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label6.Name = "label6"; label6.Size = new System.Drawing.Size(32, 13); @@ -143,7 +145,7 @@ private void InitializeComponent() // labelPresets // labelPresets.AutoSize = true; - labelPresets.Location = new System.Drawing.Point(12, 119); + labelPresets.Location = new System.Drawing.Point(8, 119); labelPresets.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); labelPresets.Name = "labelPresets"; labelPresets.Size = new System.Drawing.Size(46, 26); @@ -222,7 +224,7 @@ private void InitializeComponent() // this.splitContainer1.Panel2.Controls.Add(this.panel1); this.splitContainer1.Size = new System.Drawing.Size(834, 487); - this.splitContainer1.SplitterDistance = 455; + this.splitContainer1.SplitterDistance = 458; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 2; // @@ -241,34 +243,43 @@ private void InitializeComponent() // splitContainer2.Panel2 // this.splitContainer2.Panel2.Controls.Add(this.panelChannelOptions); - this.splitContainer2.Size = new System.Drawing.Size(834, 455); - this.splitContainer2.SplitterDistance = 619; + this.splitContainer2.Size = new System.Drawing.Size(834, 458); + this.splitContainer2.SplitterDistance = 622; this.splitContainer2.SplitterWidth = 3; this.splitContainer2.TabIndex = 1; // // panelProbe // - this.panelProbe.Controls.Add(label6); - this.panelProbe.Controls.Add(label7); - this.panelProbe.Controls.Add(this.trackBarProbePosition); + this.panelProbe.Controls.Add(this.panelTrackBar); this.panelProbe.Dock = System.Windows.Forms.DockStyle.Fill; this.panelProbe.Location = new System.Drawing.Point(0, 0); this.panelProbe.Margin = new System.Windows.Forms.Padding(2); this.panelProbe.Name = "panelProbe"; - this.panelProbe.Size = new System.Drawing.Size(619, 455); + this.panelProbe.Size = new System.Drawing.Size(622, 458); this.panelProbe.TabIndex = 1; // + // panelTrackBar + // + this.panelTrackBar.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.panelTrackBar.Controls.Add(label6); + this.panelTrackBar.Controls.Add(label7); + this.panelTrackBar.Controls.Add(this.trackBarProbePosition); + this.panelTrackBar.Location = new System.Drawing.Point(580, 1); + this.panelTrackBar.Name = "panelTrackBar"; + this.panelTrackBar.Size = new System.Drawing.Size(39, 456); + this.panelTrackBar.TabIndex = 30; + // // trackBarProbePosition // this.trackBarProbePosition.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); this.trackBarProbePosition.AutoSize = false; - this.trackBarProbePosition.Location = new System.Drawing.Point(568, 9); + this.trackBarProbePosition.Location = new System.Drawing.Point(2, 2); this.trackBarProbePosition.Margin = new System.Windows.Forms.Padding(2); this.trackBarProbePosition.Maximum = 100; this.trackBarProbePosition.Name = "trackBarProbePosition"; this.trackBarProbePosition.Orientation = System.Windows.Forms.Orientation.Vertical; - this.trackBarProbePosition.Size = new System.Drawing.Size(37, 444); + this.trackBarProbePosition.Size = new System.Drawing.Size(37, 452); this.trackBarProbePosition.TabIndex = 22; this.trackBarProbePosition.TickFrequency = 2; this.trackBarProbePosition.TickStyle = System.Windows.Forms.TickStyle.Both; @@ -293,7 +304,7 @@ private void InitializeComponent() this.panelChannelOptions.Location = new System.Drawing.Point(0, 0); this.panelChannelOptions.Margin = new System.Windows.Forms.Padding(2); this.panelChannelOptions.Name = "panelChannelOptions"; - this.panelChannelOptions.Size = new System.Drawing.Size(212, 455); + this.panelChannelOptions.Size = new System.Drawing.Size(209, 458); this.panelChannelOptions.TabIndex = 1; // // buttonClearCalibrationFile @@ -324,7 +335,7 @@ private void InitializeComponent() this.textBoxProbeCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.textBoxProbeCalibrationFile.Name = "textBoxProbeCalibrationFile"; this.textBoxProbeCalibrationFile.ReadOnly = true; - this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(190, 20); + this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(189, 20); this.textBoxProbeCalibrationFile.TabIndex = 33; this.textBoxProbeCalibrationFile.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; this.textBoxProbeCalibrationFile.TextChanged += new System.EventHandler(this.FileTextChanged); @@ -333,7 +344,7 @@ private void InitializeComponent() // this.comboBoxReference.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxReference.FormattingEnabled = true; - this.comboBoxReference.Location = new System.Drawing.Point(79, 85); + this.comboBoxReference.Location = new System.Drawing.Point(78, 83); this.comboBoxReference.Margin = new System.Windows.Forms.Padding(2); this.comboBoxReference.Name = "comboBoxReference"; this.comboBoxReference.Size = new System.Drawing.Size(119, 21); @@ -343,10 +354,10 @@ private void InitializeComponent() // this.comboBoxChannelPresets.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxChannelPresets.FormattingEnabled = true; - this.comboBoxChannelPresets.Location = new System.Drawing.Point(79, 124); + this.comboBoxChannelPresets.Location = new System.Drawing.Point(78, 122); this.comboBoxChannelPresets.Margin = new System.Windows.Forms.Padding(2); this.comboBoxChannelPresets.Name = "comboBoxChannelPresets"; - this.comboBoxChannelPresets.Size = new System.Drawing.Size(118, 21); + this.comboBoxChannelPresets.Size = new System.Drawing.Size(119, 21); this.comboBoxChannelPresets.TabIndex = 24; // // buttonEnableContacts @@ -364,10 +375,10 @@ private void InitializeComponent() // // buttonClearSelections // - this.buttonClearSelections.Location = new System.Drawing.Point(106, 163); + this.buttonClearSelections.Location = new System.Drawing.Point(103, 165); this.buttonClearSelections.Margin = new System.Windows.Forms.Padding(2); this.buttonClearSelections.Name = "buttonClearSelections"; - this.buttonClearSelections.Size = new System.Drawing.Size(94, 38); + this.buttonClearSelections.Size = new System.Drawing.Size(94, 36); this.buttonClearSelections.TabIndex = 19; this.buttonClearSelections.Text = "Deselect Contacts"; this.toolTip.SetToolTip(this.buttonClearSelections, "Remove selections from contacts in the probe view. Press this button to deselect " + @@ -377,7 +388,7 @@ private void InitializeComponent() // // buttonResetZoom // - this.buttonResetZoom.Location = new System.Drawing.Point(57, 212); + this.buttonResetZoom.Location = new System.Drawing.Point(57, 208); this.buttonResetZoom.Margin = new System.Windows.Forms.Padding(2); this.buttonResetZoom.Name = "buttonResetZoom"; this.buttonResetZoom.Size = new System.Drawing.Size(94, 36); @@ -396,14 +407,14 @@ private void InitializeComponent() this.panel1.Location = new System.Drawing.Point(0, 0); this.panel1.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(834, 29); + this.panel1.Size = new System.Drawing.Size(834, 26); this.panel1.TabIndex = 0; // // 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(744, 3); + this.buttonCancel.Location = new System.Drawing.Point(744, 0); this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(83, 22); @@ -415,7 +426,7 @@ private void InitializeComponent() // 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(655, 3); + this.buttonOkay.Location = new System.Drawing.Point(655, 0); this.buttonOkay.Margin = new System.Windows.Forms.Padding(2); this.buttonOkay.Name = "buttonOkay"; this.buttonOkay.Size = new System.Drawing.Size(83, 22); @@ -424,30 +435,16 @@ private void InitializeComponent() this.buttonOkay.UseVisualStyleBackColor = true; this.buttonOkay.Click += new System.EventHandler(this.ButtonClick); // - // linkLabelDocumentation - // - this.linkLabelDocumentation.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.linkLabelDocumentation.AutoSize = true; - this.linkLabelDocumentation.BackColor = System.Drawing.Color.GhostWhite; - this.linkLabelDocumentation.Location = new System.Drawing.Point(741, 517); - this.linkLabelDocumentation.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); - this.linkLabelDocumentation.Name = "linkLabelDocumentation"; - this.linkLabelDocumentation.Size = new System.Drawing.Size(79, 13); - this.linkLabelDocumentation.TabIndex = 3; - this.linkLabelDocumentation.TabStop = true; - this.linkLabelDocumentation.Text = "Documentation"; - this.linkLabelDocumentation.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkClicked); - // // NeuropixelsV2eProbeConfigurationDialog // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(834, 533); - this.Controls.Add(this.linkLabelDocumentation); this.Controls.Add(this.splitContainer1); this.Controls.Add(this.statusStrip); this.Controls.Add(this.menuStrip); this.DoubleBuffered = true; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip; this.Margin = new System.Windows.Forms.Padding(2); this.Name = "NeuropixelsV2eProbeConfigurationDialog"; @@ -466,7 +463,8 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); this.splitContainer2.ResumeLayout(false); this.panelProbe.ResumeLayout(false); - this.panelProbe.PerformLayout(); + this.panelTrackBar.ResumeLayout(false); + this.panelTrackBar.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.trackBarProbePosition)).EndInit(); this.panelChannelOptions.ResumeLayout(false); this.panelChannelOptions.PerformLayout(); @@ -485,7 +483,6 @@ private void InitializeComponent() private System.Windows.Forms.Button buttonCancel; private System.Windows.Forms.Button buttonOkay; private System.Windows.Forms.ToolStripStatusLabel probeSn; - private System.Windows.Forms.LinkLabel linkLabelDocumentation; private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; private System.Windows.Forms.SplitContainer splitContainer2; private System.Windows.Forms.ToolStripStatusLabel gain; @@ -502,5 +499,6 @@ private void InitializeComponent() private System.Windows.Forms.Button buttonResetZoom; private System.Windows.Forms.ToolStripStatusLabel toolStripLabelProbeNumber; private System.Windows.Forms.ToolTip toolTip; + private System.Windows.Forms.Panel panelTrackBar; } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs index 38ae1866..810d087c 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Windows.Forms; +using System.Drawing; namespace OpenEphys.Onix1.Design { @@ -120,6 +121,16 @@ private void FormShown(object sender, EventArgs e) ChannelConfiguration.Show(); ChannelConfiguration.ConnectResizeEventHandler(); + ChannelConfiguration.OnResizeZedGraph += ResizeTrackBar; + } + + private void ResizeTrackBar(object sender, EventArgs e) + { + if (sender is ChannelConfigurationDialog dialog) + { + panelTrackBar.Height = dialog.zedGraphChannels.Size.Height; + panelTrackBar.Location = new Point(panelProbe.Size.Width - panelTrackBar.Width, ChannelConfiguration.zedGraphChannels.Location.Y); + } } private void FileTextChanged(object sender, EventArgs e) @@ -546,18 +557,6 @@ private void OnFileLoadEvent(object sender, EventArgs e) CheckForExistingChannelPreset(); } - private void LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - try - { - System.Diagnostics.Process.Start("https://open-ephys.github.io/onix1-bonsai-docs/api/OpenEphys.Onix1.ConfigureNeuropixelsV2eHeadstage.html"); - } - catch (Exception) - { - MessageBox.Show("Unable to open documentation link."); - } - } - internal void ButtonClick(object sender, EventArgs e) { if (sender is Button button && button != null) diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx index 74e92246..46c4fa6a 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx @@ -147,13 +147,1659 @@ 274, 17 - - 274, 17 - - - 274, 17 - - - 274, 17 - + + + + AAABAA4AEBAQAAEABAAoAQAA5gAAABAQAAABAAgAaAUAAA4CAAAQEAAAAQAgAGgEAAB2BwAAICAQAAEA + BADoAgAA3gsAACAgAAABAAgAqAgAAMYOAAAgIAAAAQAgAKgQAABuFwAAMDAQAAEABABoBgAAFigAADAw + AAABAAgAqA4AAH4uAAAwMAAAAQAYAKgcAAAmPQAAMDAAAAEAIACoJQAAzlkAAEBAAAABABgAKDIAAHZ/ + AABAQAAAAQAgAChCAACesQAAAAAAAAEAGABpMQAAxvMAAAAAAAABACAAZ10AAC8lAQAoAAAAEAAAACAA + AAABAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAgAAAgICAAACAgADAwMAA//8AAAD/ + /wAAAP8A/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUiAAAAAAAFU5YAAA + AAVVADYgAAAFVQAGYlAAVTUAVTUjMAM1VVVTIDOABVAAAAYDRHAAVQAAYzhlAAAFUAY4AFcAAABVA0AA + NwAAAAUDAAZAAAAAAAAAA0AAAAAAAAACAAAAAAAAAAEAAAAAAAAAAAAA//8AAP/xAAD/wQAA/jEAAPjh + AADDAAAAgBEAAJ+hAADPAwAA5jMAAPJzAAD45wAA/+cAAP/vAAD/7wAA//8AACgAAAAQAAAAIAAAAAEA + CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI5YzAEWaLgCFpi8AJLioAL2exQA9QTAAO12VANK0 + LwAws5AAzarWAGZsPgA+Z9UAdLRpACbQ+wDdtS8Ak41jAEBr6QDAtkIA37YvAKmdbgA/auoAR27gANKu + MgA8zeMAu5vDAMiq0ABLXUwATaKmAHpvfACWlJUAqqmpALe2tgDGr2QAxKAqAHt6RQA+XowAHJXwALW0 + tACysbEAtLOzALe1swCrjzcAsp5eAK2ecQCQgU8ALk93AA09OADctDEAzqw0AN61LgDbrikA3bMsAMWs + XgC/gxAA1qYjALazrQCMYSEAnGUNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg5 + OgAAAAAAAAAAAAAAICA1NjcAAAAAAAAAACAgIAAAMjM0AAAAAAAAICAgAAAAExMwMQAAACYnKCAAACAp + KissLS4vAB0eHyAgICAgISIAIyQlAAAZGgAAAAAAABMAFRscDgAAAAoKAAAAABMUFRYXGAAAAAAACgoA + AA8QEQAAEg4AAAAAAAAKCgALDAAAAA0OAAAAAAAAAAUGBwAAAAgJAAAAAAAAAAAAAAAAAAADBAAAAAAA + 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 + ABMAAAAAAAAAAAAAAAAAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + //////+H///+B///+AP//8AD//8HA//4HgP/4HwB/wP8ifwP+AHwAAABgAABAYAH5wOH/8YD4/+MB/H/ + kAf4/wEH/H4DD/48Dg//HB4f/4h+H//A/h//4f4///f+P////H////x////8f////P////z///////// + //8oAAAAIAAAAEAAAAABAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULgAmmTwAIpQuACGX + NwBNnC4AIZQvACW/wACPqC8AJLmsANS0LwAxly4AI6+JAD5CNwB1oy4AIqNhACbQ+wCchaAALDMkAC02 + KQA8X6cAwbEvACOZPgAmz/cAzarWAMmn0QBmWUwAM0MoADhQRQA+aN0A37YvAFKdLgAlyN8Ano0tAEFS + KwA6V3AAP2rpAJ+qLwAkvLcAxqYuAFliLAA8X6QAP2rqANm1LwA1tJAA2bIvAIF8OAA/Z9AAfLVpAKyb + VgBDbOUAxbZAACbQ+gDCqFEAPszgAEFr5gCrlzoAf8OZADpUWgCAei0AzLE5AIhxjgCkiKsAt5e/AMKn + yQA6WX8AN0wrAEFXUAAsuPcAaVZtAHZpeQCbmZoAsK+vALe2tgBWaGgAPE8rADpt6QAcpPMAdGJ3AIB2 + ggCKiYkAhoWFAIWEhACTkpIAqKenALa1tQCynl4AsI8lALSSJgCxp4oAwLKGAKqRKwBIVSwAPWXVABlb + 5AAUg+0AJMf5ALOysgCrqqoArq2tALazrwCkizoAoIIiAKSMPgC2tLEAoYQpAJl9IgBZZocAIkmpAAY3 + aAALRk0Au5goALaVJwC4pGMAp44+AKKEIgCsnnMAgIqoABAyJAAGLB4A3LMuANayOgC0mSoAmIsqAN60 + LQDftS8A3rQuANOgHwDarSgA2aomANK0VADetS8AyY0RAMyTFgCwlUEAtYMTALJqBQDNlRcAs62dAJdy + HAB9RAEAo2kMAKSQawB6QgEAgUoFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASY6PkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASUmKi4yNAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAASUlJaIaHiIkeAAAAAAAAAAAAAAAAAAAAAAAAAABJSUlJSUmCg4SFfx4AAAAA + AAAAAAAAAAAAAAAAAABJSUlJSQAAAB58f4CBHgAAAAAAAAAAAAAAAAAAAElJSUlJSQAAAAAeHnx9Hn4e + AAAAAAAAAAAAAAAAAElJSUlJSQAAAAAAHh4eHh4eHh4eAAAAAAAAAAAASUlJSUlJAAAAAAAAAAAeHgAe + eHkAensAAAAAAAAASUlJSUlJAAAAAAAAAAAAb3BxSXJzdHV2dwAAAAAAVWFIYmNVSUlJSUlJSUlJZGVm + Z2hoaWprbG1uAABOT1BRUlNUVUlJSUlJSUlJSUlWV1hZAFpbXF1eX2AAAEVFRkdISUlJSUlJSQAAAAAA + AB4eAAAASkscTE0QAAAAPT4/QAAAAAAAAAAAAAAAAAAeHh4AACpBQkNEEBAAAAAAABgYGAAAAAAAAAAA + AAAAHh4eAAAqKjo7PBAQAAAAAAAAABgYGAAAAAAAAAAAAAAeHgAqKio3OB45EBAAAAAAAAAAABgYGAAA + AAAAAAAAHh41KioqKgAeHjYQEAAAAAAAAAAAABgYGAAAAAAAAB4eMTIqKioAAB4zNBAAAAAAAAAAAAAA + ABgYGAAAAAAeLS4vKioAAAAeHjAQEAAAAAAAAAAAAAAAABgYGAAAACcoKSoqAAAAAB4rLBAAAAAAAAAA + AAAAAAAAABgYGAAhIiMkAAAAAAAAHiUmEAAAAAAAAAAAAAAAAAAAABgZGhscHQAAAAAAAAAeHyAQAAAA + AAAAAAAAAAAAAAAAABESExQAAAAAAAAAABUWFwAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAADg8Q + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoLDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + CAEJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////// + /4f///4H///4A///wAP//wcD//geA//gfAH/A/yJ/A/4AfAAAAGAAAEBgAfnA4f/xgPj/4wH8f+QB/j/ + AQf8fgMP/jwOD/8cHh//iH4f/8D+H//h/j//9/4////8f////H////x////8/////P///////////ygA + AAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsqqXDIxaEWuPWw1DAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AADwf//DAI8AAPg//4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/w + eAfgfwAA//gwH+B/AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/ + AAD/////g/8AAP////+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP// + /////wAA////////AAD///////8AACgAAAAwAAAAYAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIZQuAC2cQQA4nT8AI5xHACSVLgAhlzgAUp0uACGULwAlwMQAlqkvACS4qADXtS8AMZcuACOs + gQAm0PsA37YvAHmkLgAioVoAJtD6AMCwLwAmlS4AJs71AD1BNwA7QDQAVJ0uACXF1wCJd4sALDMkAC44 + LwA8YbUAoqsvACS6rwDNqtYAq5CwAC81KAAvOyYAOFJRAD9p4gDYtS8AN5guACOviQCbfXkAQEMmADZL + KwA3TCsAOll/AD9q6QB9pC4AIqNgAMypzQDHpS8AaGssADhMKwA3TC0APGGyAD9q6gDGsi8AJptCACbN + 8gDbtC8AkoYtADtPKwA4T0AAPmfWAFieMAAlx9wAu58uAFBbLAA5VWIAP2roAKWsLwAlvLcA0q4vAHFx + LQA8XZUA2bUvAD61jwDetS8Am4w1AEVpxACCtmkAwKZJAEpv3QDItj0AJ9D5ANKwPgBAaukA3rYvAELM + 3ACFw5IAP2rmAHV3SADUry8A2bc2ADpWaABTXiwAvaAuADtbjQA9UCsAioEvAIBqhQCKcpAAoISnAK+R + tgC7ocEAPWGxADhNLQA+ZtMAKcP5AHdlewBpVm0AeWp8AKGeoAC0s7MAt7a2ADlOLQA9YrkANHLrAB+y + 9gB2ZHoAcmV0AIKBgQCDgoIAh4aGAJqZmQCura0AyrFhANCpLADUrS0A2bEuANy0LgCymS4ATVorADtc + kgA+auoAElznABiU8QAlzfsAeWh8AIh+iQCRkJAAjYyMAImIiACGhYUAhYSEAISDgwCmpaUAtbS0ALe1 + tACnkU0AoYMiAKKEIgCkhSMArqB3AMCjRQC4mCkAXmEoADdSaQA9ZuIAJl/kAAlV5AARdusAIsH4ALGw + sACpqKgApKOjALKxsQC0sKUAoocxAKCCIgChhSsAtK+hAKubaACfgSIAbWU3ADJQpwAqTKoACECpAAY5 + bQAJQ1YAGXyMALCniwCggiMApY5DALa0sQCvpYYAoYUqAJudpgBEXqoAFD1/AAYsHgAMNCkAxaMyAMmk + KgDDnykAvZooAL6pZgC0rpwApockAKOFIwCigyIAsqqUAK6vtQBSX1oAEjgoANuyLgDXry0AzrFQAMak + NQCMgSgAaG4mAJ+RKwDftS8A37UuANuvKgDarSgA3bIsAN60LQDKjxMA1aQiANSiIADctjoAyY0RAM+Z + GgDNlRcAz5gZALi2sgDBpUgA1a4tAM6XGADDgQkAvnsIAMmOEgCzrp8Ao4csAKh/GQCtbwcArGADAMOC + CwCvo4AAnHocAINPBACARgEAnFwDANmrKAC3trUAp5NWAH9JAwB6QgEArnwXALWyqgCFVRgAilYLAI9b + DQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAA+/z5+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc3P29/j5 + +foAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNzc3Pw8fLz9PUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNzc3Nzc+rr7O3u79oAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAABzc3Nzc3Nz4+Tl5ufo6RAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc3Nz + c3Nzc3MA3hAQ3+Dh4hAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc3Nzc3Nzc3NzAAAAEBDa29rc + 3dUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNzc3Nzc3NzAAAAAAAQEBDX2BDZ2BAQAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAHNzc3Nzc3NzcwAAAAAAABAQEBDVEBAQ1hAQEAAAAAAAAAAAAAAAAAAAAAAAAABz + c3Nzc3Nzc3MAAAAAAAAAABAQEBAQEBAQABAQEAAAAAAAAAAAAAAAAAAAAABzc3Nzc3Nzc3MAAAAAAAAA + AAAAEBAQEAAQEBAQABAQEAAAAAAAAAAAAAAAAAAAc3Nzc3Nzc3NzAAAAAAAAAAAAAAAQEBAQAAAQzs/Q + ANHS09QAAAAAAAAAAAAAAHNzc3Nzc3NzAAAAAAAAAAAAAAAAAMHCw8TFc8bHyMnKy8y/v80AAAAAAAAA + AHNzc3Nzc3NzcwAAAAAAAABzc3Nzc3Nztreqqri5c7qqqru8vb6/v8AAAAAAAABzc3KkfqWmk6dzc3Nz + c3Nzc3Nzc3Nzc3Ooqaqqq6xzc62qrq+wsbKztLUAAACLjI2Oj5CRko2TlHNzc3Nzc3Nzc3Nzc3Nzc5WW + l5iZmnNzc5ucnZ6foKGiowAAAHhvb3l6e3x9fnNzc3Nzc3Nzc3Nzc3Nzc3NzAH+AgYKDAAAAAISFLYaH + iImKDwAAAG5vb29wcXJzc3Nzc3NzcwAAAAAAAAAAAAAAABAQEBAAAAAAAHQtLXV2dw8PAAAAAABlZmdo + aQAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAA4ai0ta2xtDw8PAAAAAAAAISEhISEAAAAAAAAAAAAA + AAAAAAAAAAAQEBAQAAAAADg4Yi1jZAAPDw8PAAAAAAAAACEhISEhAAAAAAAAAAAAAAAAAAAAAAAQEBAQ + AAA4ODg4X2BhEAAPDw8AAAAAAAAAAAAhISEhIQAAAAAAAAAAAAAAAAAAABAQEBAAADg4ODhbXF0QXg8P + Dw8AAAAAAAAAAAAAISEhISEAAAAAAAAAAAAAAAAAEBAQEAAAODg4ODgAWBAQWg8PDwAAAAAAAAAAAAAA + ACEhISEhAAAAAAAAAAAAAAAAEBAQVlc4ODg4OAAAEBBYWQ8PDwAAAAAAAAAAAAAAAAAhISEhIQAAAAAA + AAAAAAAQEBBSUzg4ODg4AAAAEBBUVQ8PAAAAAAAAAAAAAAAAAAAAACEhISEAAAAAAAAAABAQTk9QODg4 + OAAAAAAQEBBRDw8PAAAAAAAAAAAAAAAAAAAAAAAhISEhAAAAAAAAEBBJSks4ODg4AAAAAAAQEExNDw8P + AAAAAAAAAAAAAAAAAAAAAAAAISEhISEAAAAAEENERUY4ODgAAAAAAAAQEEdIDw8AAAAAAAAAAAAAAAAA + AAAAAAAAACEhISEhAAA8PT4/QDg4AAAAAAAAAAAQEEFCDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAhISEh + MjM0NTY3ODgAAAAAAAAAAAAQOTo7DwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAISEhKissLS4vAAAAAAAA + AAAAABAQMDEPDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEiIxwkJSYAAAAAAAAAAAAAABAnKCkPDwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbHBwdHgAAAAAAAAAAAAAAABAfASAPAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAFxgAAAAAAAAAAAAAAAAAABAZARoPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAABQVBhYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAEBEBEhMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA0BDg8AAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgEBCwAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwEICQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAABQEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAADAQEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////AAD///////8AAP//////nwAA//////4f + AAD/////8A8AAP/////ADwAA/////gAPAAD////4AAcAAP///+AQBwAA////AHAHAAD///wD4AcAAP// + 4A/AAwAA//+AP8AjAAD//AH/hCMAAP/wB/8MIQAA/8A//gABAAD+AP4AAAEAAPgAAAAAAQAAwAAAAAAD + AACAAAAQeAMAAIAA//D4BwAAwf//4eAHAADg///DwIcAAPB//8MAjwAA+D//hgAPAAD8H/8MEB8AAP4P + /wAwHwAA/wf+AHA/AAD/w/wB4D8AAP/h+APgPwAA//B4B+B/AAD/+DAf4H8AAP/8AD/g/wAA//4A/8D/ + AAD//wH/wP8AAP//g//B/wAA///P/8H/AAD/////w/8AAP////+D/wAA/////4P/AAD/////h/8AAP// + //+H/wAA/////4//AAD/////D/8AAP////+P/wAA////////AAD///////8AAP///////wAAKAAAADAA + AABgAAAAAQAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIpWC49bDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALWyqoVVGHpCAXpCAQAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tre2tre2taeTVn9JA3pCAXpCAa58FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAALe2tre2tre2tre2tq+jgJx6HINPBIBGAZxcA9mrKAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tre2tre2tre2tre2tre2trOun6OHLKh/Ga1v + B6xgA8OCC960LQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tre2tre2tre2 + tre2tre2tre2tri2ssGlSNWuLc6XGMOBCb57CMmOEt+2L9+2LwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAALe2tre2tre2tre2tre2tre2tre2tre2tgAAANy2Ot+2L9+2L8mNEc+ZGs2VF8+YGd+2L9+2 + LwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tre2tre2tre2tre2tre2tre2tre2tre2tgAAAAAAAAAAAN+2 + L9+2L960LcqPE960LdWkItSiIN+2L9+2LwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tre2tre2tre2tre2tre2tre2 + tre2tgAAAAAAAAAAAAAAAAAAAN+2L9+2L9+2L9uvKtqtKN+2L92yLNqsKN+2L9+2LwAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tre2 + tre2tre2tre2tre2tre2tre2tre2tgAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L9+2L9+2L9+1L9+2L9+2 + L9+2L9+1Lt+2L9+2L9+2LwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAALe2tre2tre2tre2tre2tre2tre2tre2tre2tgAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAN+2L9+2L9+2L9+2L9+2L9+2L9+2L9+2LwAAAN+2L9+2L9+2LwAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tre2tre2tre2tre2tre2tre2tre2tre2tgAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L9+2L9+2LwAAAN+2L9+2L9+2L9+2LwAAAN+2L9+2 + L9+2LwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tre2tre2tre2tre2 + tre2tre2tre2tre2tgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L9+2L9+2LwAA + AAAAAN+2L9uyLtevLc6xUAAAAMakNYyBKGhuJp+RKwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAALe2tre2tre2tre2tre2tre2tre2tre2tgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAMWjMsmkKsOfKb2aKL6pZre2trSunKaHJKOFI6KDIrKqlK6vtVJfWgctHgYsHhI4KAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAALe2tre2tre2tre2tre2tre2tre2tre2tre2tgAAAAAAAAAAAAAAAAAA + AAAAAAAAALe2tre2tre2tre2tre2tre2tre2trCni6CCI6CCIqCCIqWOQ7a0sbe2tq+lhqCCIqCCIqGF + KpudpkReqhQ9fwYsHgYsHgw0KQAAAAAAAAAAAAAAAAAAAAAAALe2tre2trSzs7GwsK6tramoqKSjo6al + pbKxsbe2tre2tre2tre2tre2tre2tre2tre2tre2tre2tre2tre2tre2tre2tre2trSwpaKHMaCCIqCC + IqGFK7Svobe2tre2tqubaKCCIp+BIm1lNzJQpypMqghAqQY5bQlDVhl8jAAAAAAAAAAAAHlofIh+iZGQ + kI2MjImIiIaFhYWEhISDg5GQkKalpbW0tLe2tre2tre2tre2tre2tre2tre2tre2tre2tre2tre2tre2 + tre2tre2tre2tre1tKeRTaGDIqKEIqSFI66gd7e2tre2tre2tsCjRbiYKV5hKDdSaT1m4iZf5AlV5BF2 + 6yLB+AAAAAAAAAAAAHZkemlWbWlWbXJldIKBgYOCgoeGhpqZma6trbe2tre2tre2tre2tre2tre2tre2 + tre2tre2tre2tre2tre2tre2tre2tre2tre2tre2tgAAAMqxYdCpLNStLdmxLty0LgAAAAAAAAAAAAAA + ALKZLk1aKzdMKztckj5q6hJc5xiU8SXN+ybQ+wAAAAAAAAAAAHdle2lWbWlWbWlWbXlqfKGeoLSzs7e2 + tre2tre2tre2tre2tre2tre2tre2tgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + L9+2L9+2L9+2LwAAAAAAAAAAAAAAAAAAADlOLTdMKzdMKz1iuTRy6x+y9ibQ+ybQ+wAAAAAAAAAAAAAA + AAAAAIBqhYpykKCEp6+RtruhwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L9+2L9+2LwAAAAAAAAAAAAAAAD9q6j1hsTdMKzdMKzhNLT5m + 0ynD+SbQ+ybQ+ybQ+wAAAAAAAAAAAAAAAAAAAAAAAM2q1s2q1s2q1s2q1s2q1gAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L9+2L9+2LwAAAAAAAAAA + AAAAAD9q6j9q6jtbjTdMKz1QK4qBLwAAACbQ+ybQ+ybQ+ybQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q + 1s2q1s2q1s2q1s2q1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAN+2L9+2L9+2L9+2LwAAAAAAAD9q6j9q6j9q6j9q6jpWaFNeLL2gLt+2LwAAACbQ+ybQ+ybQ+wAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1s2q1s2q1s2q1s2q1gAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L9+2L9+2LwAAAAAAAD9q6j9q6j9q6j9q6j9q5nV3 + SNSvL9+2L9m3NibQ+ybQ+ybQ+ybQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1s2q1s2q + 1s2q1s2q1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L9+2L9+2LwAA + AAAAAD9q6j9q6j9q6j9q6j9q6gAAAN62L9+2L9+2L4XDkibQ+ybQ+ybQ+wAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAM2q1s2q1s2q1s2q1s2q1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2L9+2L9+2L9KwPkBq6T9q6j9q6j9q6j9q6j9q6gAAAAAAAN+2L9+2L962L0LM3CbQ + +ybQ+ybQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1s2q1s2q1s2q1s2q + 1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L9+2L8CmSUpv3T9q6j9q6j9q6j9q6j9q + 6gAAAAAAAAAAAN+2L9+2L8i2PSfQ+SbQ+ybQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAM2q1s2q1s2q1s2q1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L961 + L5uMNUVpxD9q6j9q6j9q6j9q6gAAAAAAAAAAAAAAAN+2L9+2L9+2L4K2aSbQ+ybQ+ybQ+wAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1s2q1s2q1s2q1gAAAAAA + AAAAAAAAAAAAAAAAAN+2L9+2L9KuL3FxLTxdlT9q6j9q6j9q6j9q6gAAAAAAAAAAAAAAAAAAAN+2L9+2 + L9m1Lz61jybQ+ybQ+ybQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAM2q1s2q1s2q1s2q1s2q1gAAAAAAAAAAAAAAAN+2L7ufLlBbLDlVYj9q6D9q6j9q6j9q + 6gAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L6WsLyW8tybQ+ybQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1s2q1s2q1s2q1s2q1gAAAAAAANu0 + L5KGLTtPKzhPQD5n1j9q6j9q6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L1ieMCXH3CbQ+ybQ + +wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAM2q1s2q1s2q1s2q1sypzcelL2hrLDhMKzdMLTxhsj9q6j9q6gAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAN+2L8ayLyabQibN8ibQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1s2q1s2q1pt9eUBDJjZLKzdMKzpZfz9q6QAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9+2L32kLiKjYCbQ+ybQ+wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q + 1quQsC81KCwzJC87JjhSUT9p4gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L9i1LzeY + LiOviSbQ+ybQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIl3iywzJCwzJC44LzxhtQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAN+2L6KrLyGULiS6rybQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1BNztANAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L1SdLiGULiXF1ybQ+wAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAMCwLyaVLiGXOCbO9QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L3mkLiGULiKhWibQ+gAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANe1LzGXLiGULiOs + gSbQ+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAJapLyGULiGULiS4qAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKdLiGULiGULyXAxAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + ACSVLiGULiGXOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAADidPyGULiGULiOcRwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGULiGULi2cQQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////wAA//////// + AAD//////58AAP/////+HwAA//////APAAD/////wA8AAP////4ADwAA////+AAHAAD////gEAcAAP// + /wBwBwAA///8A+AHAAD//+APwAMAAP//gD/AIwAA//wB/4QjAAD/8Af/DCEAAP/AP/4AAQAA/gD+AAAB + AAD4AAAAAAEAAMAAAAAAAwAAgAAAEHgDAACAAP/w+AcAAMH//+HgBwAA4P//w8CHAADwf//DAI8AAPg/ + /4YADwAA/B//DBAfAAD+D/8AMB8AAP8H/gBwPwAA/8P8AeA/AAD/4fgD4D8AAP/weAfgfwAA//gwH+B/ + AAD//AA/4P8AAP/+AP/A/wAA//8B/8D/AAD//4P/wf8AAP//z//B/wAA/////8P/AAD/////g/8AAP// + //+D/wAA/////4f/AAD/////h/8AAP////+P/wAA/////w//AAD/////j/8AAP///////wAA//////// + AAD///////8AACgAAAAwAAAAYAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + ogYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYyt7a2mre2tu+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tvm3traqt7a2Q7e2tgO3trYIt7a2ILe2tji3trZPt7a2aLe2toC3traXt7a2sLe2tsm3trbat7a27Le2 + tvqwp4v/oIIj/6CCIv+ggiL/pY5D/7a0sf+3trb/r6WG/6CCIv+ggiL/oYUq/5udpv9EXqr/FD1//wYs + Hv8GLB7/DDQp7WSTmw0AAAAAAAAAAAAAAAC3trYNt7a2XLe2tsa3trb+tLOz/7GwsP+ura3/qaio/6Sj + o/+mpaX/srGx9Le2tsC3tra/t7a21be2tu+3trb+t7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7Swpf+ihzH/oIIi/6CCIv+hhSv/tK+h/7e2tv+3trb/q5to/6CCIv+fgSL/bWU3/zJQ + p/8qTKr/CECp/wY5bf8JQ1b/GXyMigAAAAAAAAAAkoKVGXlofJOIfonkkZCQ/42MjP+JiIj/hoWF/4WE + hP+Eg4P/kZCQ/6alpf+1tLT/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7W0/6eRTf+hgyL/ooQi/6SFI/6uoHfgt7a2xLe2tq63traWwKNF4riY + Kf9eYSj/N1Jp/z1m4v8mX+T/CVXk/xF26/8iwfj5JtD7JgAAAAAAAAAAdmR6sWlWbf9pVm3/cmV0/4KB + gf+DgoL/h4aG/5qZmf+ura3/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb5t7a25be2ts23tra3t7a2n7e2toe3trZvyrFhudCpLP/UrS3/2bEu/9y0LqTfti8CAAAAAAAA + AAAAAAAAspku501aK/83TCv/O1yS/z5q6v8SXOf/GJTx/yXN+/8m0Pu4JtD7AgAAAAAAAAAAd2V7xmlW + bf9pVm3/aVZt/3lqfP+hnqD/tLOz/7e2tv+3trb+t7a287e2tua3trbYt7a2wLe2tqm3traQt7a2eLe2 + tmG3trZIt7a2Mbe2thi3trYGt7a2A7e2tgEAAAAAAAAAAAAAAADfti8z37Yv99+2L//fti//37Yv2d+2 + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2LwHXtS/DMZcu/yGU + Lv8jrIH/JtD7oQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + LwyWqS/qIZQu/yGULv8kuKj/JtD7OgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANW0LydSnS7/IZQu/yGUL/8lwMTOJtD7AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIClL1kklS7/IZQu/yGXOP8lx9xqAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADidP5QhlC7/IZQu/yOcR/Am0PsUAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADefRX8hlC7/IZQu/y2c + QaEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKr + YQgxnD9zLZo6fFeuZhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP///////wAA//////4PAAD/////+A8AAP// + ///ADwAA/////wAHAAD////4AAcAAP///+AABwAA////gAAHAAD///wAAAMAAP//8ADAAwAA//+AA4AD + AAD//gAPgAEAAP/4AH8AAQAA/8AD/gABAAD/AA/gAAAAAPwAAAAAAAAA4AAAAAABAACAAAAAAAEAAIAA + AAA4AQAAgAAA4GADAACAB//AwAMAAMB//8GABwAA4D//gAAHAADwH/8AAA8AAPgP/gAADwAA/Af+AAAP + AAD+A/wAIB8AAP8A+ADgHwAA/4BwAcA/AAD/4DADwD8AAP/wAA/APwAA//gAH8B/AAD//AA/wH8AAP/+ + AP/A/wAA//8B/4D/AAD//4P/gP8AAP//z/+B/wAA/////4H/AAD/////A/8AAP////8D/wAA/////wP/ + AAD/////B/8AAP////8H/wAA/////w//AAD/////D/8AAP///////wAA////////AAAoAAAAQAAAAIAA + AAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjV0YiVQKi1YLAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2n4Ve + ekIBekIBekIBh1IIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2squWjWEUekIBekIBekIBiVQJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAt7a2t7a2t7a2t7a2trOupIs9i14LekIBekIBekIBpG8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7W1qZddn4EhjF0HfEUBhUgBnloDz5kb37YvAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2r6SCoIMkoX8eomwIqGMErF8Cv3wI1qUj + 37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2tK+grpAzw58pzp0gw4IJ + unQGuXMGw4IJ268q37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uLaz0LBH + 3bQv37Yv0Joaw4IJyY0SxIMKxocN3rQu37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2AAAAAAAA37Yv37Yv37UvypATx4kO3LAryY4Ry5AT37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2AAAAAAAAAAAAAAAA37Yv37Yv37Yv3rQtxogO2Kgl37Yv0Zwc0Joa37Yv37Yv37YvAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv27Aq0Jsb37Yv37Yv2asn1aMh37Yv + 37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv264p3rQu37Yv + 37Yv3rUu268q37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv + 37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37YvAAAA37Yv37Yv37Yv37Yv37Yv + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37Yv37YvAAAAAAAA37Yv37Yv37Yv37YvAAAAAAAA37Yv + 37Yv37Yv37Yv37YvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA3rUv2rIu1q8t + 0qwuAAAAAAAAv6hesJMnYWclOlIjdXcnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA168t1a4t0KosyqUqxKAqw61ot7a2t7a2 + r51kp4gjpYYjo4Ujo4gvtbGpt7a2l5iXLEAjBiweBiweBy0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7WzqZNLpIUjoIIioIIioIIi + ppBKtrSwt7a2t7a2qJNToIIioIIioIIipIw+tbS1gIywMlCkCjAzBiweBiweBiweHkc/AAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2AAAAAAAA + AAAAAAAAAAAAAAAAt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2rqF6oIIi + oIIioIIioIIioocvtK+it7a2t7a2t7W0o4k2oIIioIIioIIino5aXXGsLUynHkamBjRgBiweBiweBi0g + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2t7a2t7a2tLOzsrGxrq2tq6qqp6amoqGhnJubnp2dq6qq + tLOzt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2sqyYoYUroIIioIIioIIioIIjr6WFt7a2t7a2t7a2tLCkoYUqoIIioIIigm8hPE5uLUynLk6rD0Os + BkGrBzt4CEBgF4SdAAAAAAAAAAAAAAAAAAAAg3SGkImRmZeYlZSUkI+Pi4qKiYiIh4aGhYSEg4KChIOD + j46OpKOjs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2trOtpY1CoIIioIIioIIioIIiqZZct7a1t7a2t7a2t7a2t6uHuZcouZgoeXEoN0gn + OV2zPGbgMWPiClXiCVXkDmzqILf3Js/7AAAAAAAAAAAAAAAAcmB2aVZtaVZtcmV0goCBg4KCg4KCg4KC + g4KChoWFmJeXrKurtrW1t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2uKNitpUnvJkowp4pyKMqyqUsAAAAAAAAAAAAAAAAAAAAAAAA + zasvamwsOEwrN042PmfYP2rqH17oCVjmFIXuJMf6JtD7AAAAAAAAAAAAAAAAAAAAaVZtaVZtaVZtaVZt + cGJzgX+Bg4KCjIuLoJ+fs7Kyt7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2t7a2 + t7a2t7a2t7a2t7a2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37Yv37Yv37Yv37Yv37YvAAAAAAAAAAAA + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3rYvV54uIZQuIZQuJcPP + JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwrEv + KZUuIZQuIZUxJs70JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve6QuIZQuIZQuIp9SJtD6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA17UvM5cuIZQuIZQuI6p6JtD7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnaovIZQuIZQuIZQuJLWgAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV54uIZQuIZQuIZUwJcDDAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ5UuIZQu + IZQuIZc5JcXXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAARZ46IZQuIZQuIZQuIpxIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM5xAIZQuIZQuIZQuJ6BTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOaBHIZQuIZQuIpQvAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOJ9GI5UwAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/////////////////////////////////////////H/////////wP/// + /////4A////////+AD////////gAH///////wAAf//////8AAB//////+AAAD//////gAYAP/////4AP + AA/////8AD4AD/////AB/AAH////gAf8AAf///4AP/ggh///+AD/8CCD///AA//gYYP//wAf/+Dhg//4 + AH//wAAD/+AD//gAAAH/AA/AAAAAA/wAAAAAAAAD4AAAAAAAAAPAAAAAAA/AB8AAAAP8H4AHwAAP//g/ + AA/AP///8D4AD+A////wfAYP+B///+DwBB/8D///wOAEH/4H//+BwAQ//wP//4MAAD//gf//BgMAP//A + f/4EBwB//+A//gAOAH//8B/8AD4A///4D/gAfgD///wH8AD+Af///gPwA/4B////AeAH/AH////AwA/8 + A////+AAP/wD////8AB//Af////4Af/8B/////wD//gH/////gf/+A//////H//4D/////////gf//// + ////+B/////////4H/////////A/////////8D/////////wf/////////B/////////8H/////////g + /////////+D/////////4f/////////z//////////////////////////////////8oAAAAQAAAAIAA + AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKpUACs4ckEMObMAgAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyqpcwjV0YpYlU + CvSLVgvSmWcTMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dre2 + tli3trbBn4Ve/HpCAf96QgH/ekIB/4dSCM3FnzkGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYlt7a2g7e2tt63trb9squW/41hFP96QgH/ekIB/3pCAf+JVAn4yZ0oIwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAt7a2Bre2tkW3trast7a28re2tv+3trb/trOu/6SLPf+LXgv/ekIB/3pCAf96QgH/pG8P/9+2 + L1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYVt7a2b7e2ttW3trb+t7a2/7e2tv+3trb/t7W1/6mXXf+fgSH/jF0H/3xF + Af+FSAH/nloD/8+ZG//fti+iAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAt7a2BLe2tjm3trabt7a277e2tv+3trb/t7a2/7e2tv+3trb/t7a2/6+k + gv+ggyT/oX8e/6JsCP+oYwT/rF8C/798CP/WpSP/37Yv4N+2LwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYSt7a2Xre2tsO3trb4t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7SvoP+ukDP/w58p/86dIP/Dggn/unQG/7lzBv/Dggn/268q/9+2L/vfti88AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Are2tiS3traJt7a24re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2+Li2s7TQsEfr3bQv/9+2L//Qmhr/w4IJ/8mNEv/Egwr/xocN/960 + Lv/fti//37YvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2T7e2trO3trb6t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbgt7a2g7m2sSLfti9937Yv/9+2L//ftS//ypAT/8eJ + Dv/csCv/yY4R/8uQE//fti//37Yv/9+2L8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tiG3trZ2t7a22be2 + tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a297e2try3trZat7a2DAAAAADfti8/37Yv9t+2 + L//fti//3rQt/8aIDv/YqCX/37Yv/9GcHP/Qmhr/37Yv/9+2L//fti/637YvHAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYHt7a2Obe2 + tqS3trbtt7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv63trbqt7a2lre2tjO3trYEAAAAAAAA + AADfti8W37Yv1d+2L//fti//37Yv/9uwKv/Qmxv/37Yv/9+2L//Zqyf/1aMh/9+2L//fti//37Yv/9+2 + L2MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2 + tgy3trZlt7a2xre2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2ttK3trZqt7a2FgAA + AAAAAAAAAAAAAAAAAADfti8C37Yvo9+2L//fti//37Yv/9+2L//brin23rQu/9+2L//fti//3rUu/duv + KvDfti//37Yv/9+2L//fti+p37YvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAC3trYBt7a2L7e2to+3trbut7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trbyt7a2pre2 + tkC3trYBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvYt+2L/zfti//37Yv/9+2L//fti/p37YvrN+2 + L//fti//37Yv/9+2L/Dfti+m37Yv/9+2L//fti//37Yv6d+2Lw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAALe2tg63trZSt7a2u7e2tva3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/re2 + ttq3trZ8t7a2ILe2tgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvKN+2L+/fti//37Yv/9+2 + L//fti/837YvYN+2L7Xfti//37Yv/9+2L//fti/G37YvV9+2L/7fti//37Yv/9+2L//fti9DAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC3trYCt7a2H7e2tn63trbZt7a2/re2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb3t7a2ure2tlO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvDN+2 + L8Lfti//37Yv/9+2L//fti//37Yvod+2LxHfti/b37Yv/9+2L//fti//37YvlN+2Lxbfti/037Yv/9+2 + L//fti//37YvjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgK3trZBt7a2pre2tvS3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tuy3traPt7a2Lre2tgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2L4Xfti//37Yv/9+2L//fti//37Yv3d+2LxDfti8i37Yv9d+2L//fti//37Yv/9+2 + L2QAAAAA37Yvv9+2L//fti//37Yv/9+2L9Dfti8HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Fre2tmy3trbUt7a2/re2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb9t7a2xbe2tmO3trYNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L0rfti/337Yv/9+2L//fti//37Yv9N+2Lz4AAAAA37YvS961 + L//asi7/1q8t/9KsLv3Esndyt7a2X7+oXrqwkyf/YWcl/zpSI/91dyf4sp0sKwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALe2tgS3trY0t7a2lre2tum3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tu23traht7a2O7e2tgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2CMasViTXry3f1a4t/9CqLP/KpSr/xKAq/8Ot + aNu3traft7a2tq+dZN+niCP/pYYj/6OFI/+jiC//tbGp/7e2tv+XmJf/LEAj/wYsHv8GLB7/By0e/yhO + On8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Dbe2tli3tra/t7a2+Le2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb8t7a22re2tnW3trYeAAAAAAAAAAAAAAAAAAAAALe2 + tgK3trYFt7a2Cbe2tg23trYbt7a2Nbe2tky3trZlt7a2fLe2tpS3tratt7a2xLe1s9Spk0vspIUj/6CC + Iv+ggiL/oIIi/6aQSv+2tLD/t7a2/7e2tv+ok1P/oIIi/6CCIv+ggiL/pIw+/7W0tf+AjLD/MlCk/wow + M/8GLB7/Biwe/wYsHv8eRz+4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3trYjt7a2hLe2 + tuG3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tvu3trawt7a2Ure2tiC3trYvt7a2Rbe2 + tlq3trZ0t7a2i7e2tqK3tra6t7a2zbe2tuO3trb2t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+uoXr/oIIi/6CCIv+ggiL/oIIi/6KHL/+0r6L/t7a2/7e2tv+3tbT/o4k2/6CCIv+ggiL/oIIi/56O + Wv9dcaz/LUyn/x5Gpv8GNGD/Biwe/wYsHv8GLSD/G0xIcgAAAAAAAAAAAAAAAAAAAAAAAAAAt7a2Cbe2 + tkm3travt7a297e2tv+0s7P/srGx/66trf+rqqr/p6am/6Khof+cm5v/np2d/6uqqv60s7Pht7a25Le2 + tvW3trb4t7a2+7e2tv23trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+yrJj/oYUr/6CCIv+ggiL/oIIi/6CCI/+vpYX/t7a2/7e2tv+3trb/tLCk/6GF + Kv+ggiL/oIIi/4JvIf88Tm7/LUyn/y5Oq/8PQ6z/BkGr/wc7eP8IQGD/F4Sd4ym42xYAAAAAAAAAAAAA + AACTg5Ygg3SGgJCJkdOZl5j7lZSU/5CPj/+Lior/iYiI/4eGhv+FhIT/g4KC/4SDg/+Pjo7/pKOj/7Oy + sv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+2s63/pY1C/6CCIv+ggiL/oIIi/6CCIv+pllz6t7a18be2 + tui3trbgt7a2zberh825lyj/uZgo/3lxKP83SCf/OV2z/zxm4P8xY+L/ClXi/wlV5P8ObOr/ILf3/ybP + +5EAAAAAAAAAAAAAAACLeo4acmB23mlWbf9pVm3/cmV0/4KAgf+DgoL/g4KC/4OCgv+DgoL/hoWF/5iX + l/+sq6v/trW1/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb/t7a2/7e2tv+3trb/t7a2/re2tvy3trb6t7a2+Le2tuy3trbWuKNi9baVJ/+8mSj/wp4p/8ij + Kv/KpSzUtaqGN7e2tiO3trYTt7a2BAAAAADfti90zasv/2psLP84TCv/N042/z5n2P8/aur/H17o/wlY + 5v8Uhe7/JMf6/ybQ+/wm0PsvAAAAAAAAAAAAAAAAjHqPcWlWbf9pVm3/aVZt/2lWbf9wYnP/gX+B/4OC + gv+Mi4v/oJ+f/7Oysv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2tv+3trb/t7a2/7e2 + tv+3trb9t7a27Le2tta3trbDt7a2rre2tpa3trZ+t7a2Zre2tk+3trY4t7a2I7e2tg4AAAAA37YvTt+2 + L/zfti//37Yv/9+2L//fti/y37YvNwAAAAAAAAAAAAAAAAAAAAAAAAAAlIczp0pYK/83TCv/N0wr/zhR + Tv8/aeT/O2nq/w9g6P8cpPT/Js/7/ybQ+/8m0Pu9JtD7BAAAAAAAAAAAAAAAAI59kVVrWG/+aVZt/2lW + bf9pVm3/a1hv/31sgP+moqX/tbS0/7e2tv+3trb/t7a2/7e2tve3trbot7a227e2ts63tra3t7a2obe2 + toe3trZvt7a2Wbe2tkG3trYmt7a2Ebe2tgy3trYHt7a2AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37YvH9+2L+Pfti//37Yv/9+2L//fti/+37YvcN+2LwEAAAAAAAAAAAAAAAA/auoMP2rqgzlR + SPs3TCv/N0wr/zdMK/86V27/P2rq/y567PchvPj/JtD7/ybQ+/8m0Pv+JtD7WQAAAAAAAAAAAAAAAAAA + AACjk6UJfmqBoHFddvx0X3n/hm+M/5p/of+mia3/s5q5/ruyvZu3trZht7a2Sre2tjG3trYit7a2F7e2 + tg23trYDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37YvCN+2L7Lfti//37Yv/9+2L//fti//37Yvst+2LwcAAAAAAAAAAAAA + AAA/auonP2rqvD9p4f43TC7/N0wr/zdMK/83TCv/O1yQ+D5v65YnyfrTJtD7/ybQ+/8m0Pv/JtD76CbQ + +wsAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gPCocuAyafS/c2q1v/Nqtb/zarW/82q1v/NqtbXzarWKwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2L3Lfti/937Yv/9+2L//fti//37Yv5t+2 + LxwAAAAAAAAAAD9q6gE/aupSP2rq5z9q6v89ZMH/N0wr/zdMK/83TCv/RFQr/2Nxbmonzfs8JtD7+ybQ + +/8m0Pv/JtD7/ybQ+40AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1m7Nqtb4zarW/82q + 1v/Nqtb/zarW/82q1ubNqtY2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lznfti/z37Yv/9+2 + L//fti//37Yv+N+2L00AAAAAAAAAAD9q6g4/auqSP2rq/z9q6v8/aur/PF2Z/zdMK/83TCv/ZGgs/8uq + L/7fti8SJtD7oCbQ+/8m0Pv/JtD7/ybQ+/Qm0PstAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADNqtYCzarWXc2q1vTNqtb/zarW/82q1v/Nqtb/zarW682q1kMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2 + Lw3fti/W37Yv/9+2L//fti//37Yv/9+2L43fti8CAAAAAD9q6i8/aurKP2rq/j9q6v8/aur/P2rq/zpY + d/89UCv/jIIt/9myL//fti/cQMzeHibQ++sm0Pv/JtD7/ybQ+/8m0Pu6JtD7AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1gHNqtZNzarW8s2q1v/Nqtb/zarW/82q1v/NqtbwzarWUM2q + 1gEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAN+2LwTfti+Y37Yv/9+2L//fti//37Yv/9+2L8nfti8PP2rqBD9q6mI/aursP2rq/z9q + 6v8/aur/P2rq/z9p5v9NX1b/spou/961L//fti//37YvrifQ+m8m0Pv/JtD7/ybQ+/8m0Pv+JtD7VgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1kDNqtbtzarW/82q + 1v/Nqtb/zarW/82q1vfNqtZeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti9V37Yv+9+2L//fti//37Yv/9+2L/Lfti8sP2rqET9q + 6p4/aur+P2rq/z9q6v8/aur/P2rq/z9q6v9Lb9m+zaw299+2L//fti//37Yv/9y2MoEm0PvUJtD7/ybQ + +/8m0Pv/JtD73ybQ+xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAzarWNM2q1t7Nqtb/zarW/82q1v/Nqtb/zarW/M2q1nHNqtYCAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8m37Yv6N+2L//fti//37Yv/9+2 + L/3fti9oQGrpOj9q6tU/aur/P2rq/z9q6v8/aur/P2rq/z9q6vI/aupv0rA+Mt+2L//fti//37Yv/9+2 + L/6LwouOJtD7/SbQ+/8m0Pv/JtD7/ybQ+4UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNqtYtzarW1c2q1v/Nqtb/zarW/82q1v/Nqtb6zarWg82q + 1gUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfti8J37Yvvd+2 + L//fti//37Yv/9+2L//ZszawRGzkdD9q6vM/aur/P2rq/z9q6v8/aur/P2rq/z9q6tQ/auo5P2rqAd+2 + L2Lfti//37Yv/9+2L//etjD3RsvWxCbQ+/8m0Pv/JtD7/ybQ+/km0PsjAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM2q1iDNqtbQzarW/82q + 1v/Nqtb/zarW/82q1vzNqtaRzarWBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA37Yve9+2L/3fti//37Yv/9+2L//NrT/wVHTRxT9q6v4/aur/P2rq/z9q6v8/aur/P2rq/D9q + 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 + Lv8hlC7/JcPP/ybQ++wm0PsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADfti94wrEv/ymVLv8hlC7/IZUx/ybO9P8m0PukJtD7AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA37Yvp3ukLv8hlC7/IZQu/yKfUv8m0Pr+JtD7PwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA37YvCNe1L9Ezly7/IZQu/yGULv8jqnr/JtD7zibQ + +wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN+2Lxmdqi/0IZQu/yGU + Lv8hlC7/JLWg/ybQ+20AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADUtC87V54u/yGULv8hlC7/IZUw/yXAw/Qm0PsRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAk6kvbyeVLv8hlC7/IZQu/yGXOf8lxdegAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWeOqshlC7/IZQu/yGULv8inEj6Js/4PQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAznEDQIZQu/yGU + Lv8hlC7/J6BT0CbQ+wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAOaBHnCGULv8hlC7/IpQv/zmhTHEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFKrYQ44n0aRI5UwtT2hS35suH0JAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////////////////////////8f/// + //////g/////////wB////////4AH///////+AAf///////gAB///////wAAD//////8AAAP/////+AA + AA//////gAAAD/////4AAgAH////8AAMAAf////AAHgAA////gAB+AAD///4AAfwAAP//8AAP+AAA/// + AAD/4ACB//wAB//AQAH/4AAf/wAAAf+AAPAAAAAB/gAAAAAAAAHwAAAAAAAAAcAAAAAAAAADgAAAAAAA + gAOAAAAACA+AA4AAAA/wDgAHgAB//+AcAAfAH///4DAAD/AP///AYAAP8Af//4BAAA/4Af//AAAAH/4B + //8AAAAf/wB//gAAAD//gD/8AAAAP//AH/wABgA//+AP+AAOAH//8AfwADwAf//4A+AAfAD///wB4AD8 + AP///gDAA/wB////AAAH/AH///+AAA/4Af///+AAP/gD////8AB/+AP////4AP/4A/////wD//AH//// + /gf/8Af/////H//wD/////////AP////////8A/////////wH////////+Af////////4D/////////g + P////////+B/////////4H/////////gf////////+D/////////4P////////////////////////// + ////////iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAgAElEQVR42u29eZRk133f97n3 + bbV19To9K2YADDAABrsAgYsIQhRNygSphZJNndhiZFG2adOREx2dJMfSsSImIn0sKUpkRfQRHZ1IpkxZ + NBcllEGJMSlCYkhQIIhlsGOAGcz0LD29d+1vub/88bp7umd6qa6uXqrqfs9pDKan3qt6r97ve7/f3/3d + 3wULCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC4uOgLK3wKLT + 8S8e+6hcKF9iqjbD+dIYAFppsm6WG4qHGc4M8W8/+Mf2WV8F2t4Ci07H67NnmaxNU4kqS78TEWITM1uf + 41LlMh/8o3eLvVNWAVh0ET702R+WWlznzflz649yC2rgrpGT/Luf/Jx95i0BWHQ6fuqz75Op+hRTtWkS + SZp40BU5L0efX+CrP/uEfe6tBbDoZEzXp6lE1aaCH0AQwiSkkTT4+f/7p60dsARg0an4n7/638lUfWqF + 528GkYmITcx8WLI30RKARadirHyxZfdaCss8M37K3kRLABadilpcR6R1FS9YB2AJwKJj0YjDLZ/jN/7s + EcsClgAsehVxPMW/e+wesQRgYdFhyHqZLZ9jLkxnBCwBWFh0GHw3j1Jbm8qfjxLiJLYEYB8ni05D5J1E + tljDdr4u1AxEz5wQSwAWFh2EP/jALyuji6BatwIToVBN7L107S2w6ESIsw9QENdbOr6aQN3Q8yRgFYBF + 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 + GBgsBv8PNAlKsBafvBgAAAAASUVORK5CYII= + + \ No newline at end of file diff --git a/OpenEphys.Onix1.Design/icon.ico b/OpenEphys.Onix1.Design/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..383c503499745262edf13ef272f9a8ba8128750b GIT binary patch literal 98966 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 Pf}&n90QUXgHS7Na1TER@ literal 0 HcmV?d00001 From 97245a0a20512e7d40c06ca369d6a37a0a580262 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 16 Aug 2024 11:45:12 -0400 Subject: [PATCH 24/35] Correctly pack non-string resources --- OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj index ae3173ed..60381030 100644 --- a/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj +++ b/OpenEphys.Onix1.Design/OpenEphys.Onix1.Design.csproj @@ -6,8 +6,8 @@ Bonsai Rx Open Ephys Onix Design net472 true - true x64 + true From 23e2cdccb7e150818740be62e67d0279043f8268 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 16 Aug 2024 13:17:53 -0400 Subject: [PATCH 25/35] Ensure changes are reflected when opening configuration file - Reuse base class method instead of reimplementing it --- OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs | 2 +- .../NeuropixelsV2eChannelConfigurationDialog.cs | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index 8dc78734..b525a29f 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -328,7 +328,7 @@ private bool IsProbeCentered(ProbeEdge probeEdge, ScaleEdge scaleEdge) probeEdge.Bottom <= scaleEdge.Bottom && probeEdge.Top >= scaleEdge.Top); } - private static double CalculateScaleRange(Scale scale) + internal static double CalculateScaleRange(Scale scale) { return scale.Max - scale.Min; } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 13850c30..496cece7 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -79,6 +79,7 @@ internal override void OpenFile() newConfiguration.Validate(); ProbeConfiguration = new(newConfiguration, ProbeConfiguration.Reference, ProbeConfiguration.Probe); + ChannelConfiguration = ProbeConfiguration.ChannelConfiguration; DrawProbeGroup(); RefreshZedGraph(); @@ -139,15 +140,13 @@ internal override void DrawScale() var zoomedOut = fontSize <= 2; fontSize = zoomedOut ? 8 : fontSize * 4; - var majorTickOffset = MajorTickLength + GetXRange(zedGraphChannels) * 0.015; + var majorTickOffset = MajorTickLength + CalculateScaleRange(zedGraphChannels.GraphPane.XAxis.Scale) * 0.015; majorTickOffset = majorTickOffset > 50 ? 50 : majorTickOffset; var x = GetProbeContourMaxX(zedGraphChannels.GraphPane.GraphObjList) + 50; var minY = GetProbeContourMinY(zedGraphChannels.GraphPane.GraphObjList); var maxY = GetProbeContourMaxY(zedGraphChannels.GraphPane.GraphObjList); - zedGraphChannels.GraphPane.CurveList.Clear(); - PointPairList pointList = new(); var countMajorTicks = 0; @@ -195,11 +194,6 @@ internal override void DrawScale() 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 (ProbeConfiguration == null || ProbeConfiguration.ChannelMap == null) From fe9db480fa945d835aea0be3b74c4946ad3d318b Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 16 Aug 2024 14:26:44 -0400 Subject: [PATCH 26/35] Small update to comments and property names --- OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs | 3 --- OpenEphys.Onix1.Design/ContactTag.cs | 2 +- OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs | 2 +- .../NeuropixelsV2eChannelConfigurationDialog.cs | 7 +------ OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs | 5 ----- .../NeuropixelsV2eHeadstageDialog.cs | 10 +++++----- .../NeuropixelsV2eHeadstageEditor.cs | 2 +- .../NeuropixelsV2eProbeConfigurationDialog.cs | 7 +------ 8 files changed, 10 insertions(+), 28 deletions(-) diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index b525a29f..a1d03900 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -18,9 +18,6 @@ public abstract partial class ChannelConfigurationDialog : Form { internal event EventHandler OnResizeZedGraph; - ///

- /// Local variable that holds the channel configuration in memory until the user presses Okay - /// internal ProbeGroup ChannelConfiguration; internal readonly List ReferenceContacts = new(); diff --git a/OpenEphys.Onix1.Design/ContactTag.cs b/OpenEphys.Onix1.Design/ContactTag.cs index 312a4db9..e8da0f42 100644 --- a/OpenEphys.Onix1.Design/ContactTag.cs +++ b/OpenEphys.Onix1.Design/ContactTag.cs @@ -18,7 +18,7 @@ public class ContactTag /// /// Gets the contact index of this contact. /// - public int ContactIndex; + public int ContactIndex { get; } /// /// Gets the string defining the probe and contact index for this contact. diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs index d9366abd..a5009e11 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eBno055Dialog.cs @@ -21,7 +21,7 @@ public ConfigureNeuropixelsV2eBno055 ConfigureNode /// Initializes a new instance of with the given /// object. /// - /// A object that contains configuration settings + /// A object that contains configuration settings. public NeuropixelsV2eBno055Dialog(ConfigureNeuropixelsV2eBno055 configureNode) { InitializeComponent(); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 496cece7..2e8da79f 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -9,7 +9,7 @@ namespace OpenEphys.Onix1.Design { /// - /// Partial class to create a GUI for . + /// Partial class to create a channel configuration GUI for . /// public partial class NeuropixelsV2eChannelConfigurationDialog : ChannelConfigurationDialog { @@ -20,11 +20,6 @@ public partial class NeuropixelsV2eChannelConfigurationDialog : ChannelConfigura /// Public object that is manipulated by /// . /// - /// - /// When a is passed to - /// , it is copied and stored in this - /// variable so that any modifications made to configuration settings can be easily reversed. - /// public NeuropixelsV2QuadShankProbeConfiguration ProbeConfiguration; /// diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs index b8f1bf61..35939111 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs @@ -17,11 +17,6 @@ public partial class NeuropixelsV2eDialog : Form /// Public object that is manipulated by /// . /// - /// - /// When a is passed to - /// , it is copied and stored in this - /// variable so that any modifications made to configuration settings can be easily reversed. - /// public ConfigureNeuropixelsV2e ConfigureNode { get; set; } /// diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs index 118436f5..277efe84 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs @@ -15,7 +15,7 @@ public partial class NeuropixelsV2eHeadstageDialog : Form /// /// A that configures a . /// - public readonly NeuropixelsV2eBno055Dialog ConfigureBno055; + public readonly NeuropixelsV2eBno055Dialog DialogBno055; /// /// Initializes a new instance of a . @@ -38,7 +38,7 @@ public NeuropixelsV2eHeadstageDialog(ConfigureNeuropixelsV2e configureNeuropixel this.AddMenuItemsFromDialogToFileOption(DialogNeuropixelsV2e, "NeuropixelsV2e"); DialogNeuropixelsV2e.Show(); - ConfigureBno055 = new(configureBno055) + DialogBno055 = new(configureBno055) { TopLevel = false, FormBorderStyle = FormBorderStyle.None, @@ -46,9 +46,9 @@ public NeuropixelsV2eHeadstageDialog(ConfigureNeuropixelsV2e configureNeuropixel Parent = this }; - panelBno055.Controls.Add(ConfigureBno055); - ConfigureBno055.Show(); - ConfigureBno055.Invalidate(); + panelBno055.Controls.Add(DialogBno055); + DialogBno055.Show(); + DialogBno055.Invalidate(); } private void ButtonClick(object sender, System.EventArgs e) diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs index 057ece97..0390c2b9 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs @@ -22,7 +22,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.DialogBno055.ConfigureNode.Enable; configureHeadstage.NeuropixelsV2e.Enable = editorDialog.DialogNeuropixelsV2e.ConfigureNode.Enable; configureHeadstage.NeuropixelsV2e.ProbeConfigurationA = editorDialog.DialogNeuropixelsV2e.ConfigureNode.ProbeConfigurationA; diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs index 810d087c..94da9184 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -51,18 +51,13 @@ private enum ChannelPreset /// Public object that is manipulated by /// . /// - /// - /// When a is passed to - /// , it is copied and stored in this - /// variable so that any modifications made to configuration settings can be easily reversed. - /// public NeuropixelsV2QuadShankProbeConfiguration ProbeConfiguration { get; set; } /// /// Initializes a new instance of . /// /// A object holding the current configuration settings. - /// String containing the path to the gain calibration file for this probe. + /// String containing the path to the calibration file for this probe. public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2QuadShankProbeConfiguration configuration, string calibrationFile) { InitializeComponent(); From 4e1ae1a9de0420dba7ddc8353159bd573c5de34f Mon Sep 17 00:00:00 2001 From: jonnew Date: Tue, 20 Aug 2024 13:01:40 -0400 Subject: [PATCH 27/35] Edits to neuropixels 2.0 GUI - Similify some GUI elements - Remove file parsing, SN and Gain display (not because they are a bad idea but because parsing is redundant with that done in the main library. To do it correctly, the main library should have a file template the compare against in a static method that can be accesssed from the GUI). --- .../ChannelConfigurationDialog.cs | 8 +- OpenEphys.Onix1.Design/DesignHelper.cs | 2 +- ...europixelsV2eChannelConfigurationDialog.cs | 4 +- ...elsV2eProbeConfigurationDialog.Designer.cs | 152 ++++++------------ .../NeuropixelsV2eProbeConfigurationDialog.cs | 40 ----- ...europixelsV2eProbeConfigurationDialog.resx | 6 - 6 files changed, 55 insertions(+), 157 deletions(-) diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index a1d03900..a0d82ea3 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -668,7 +668,7 @@ internal virtual void HighlightSelectedContacts() } } - internal readonly Color DisabledContactTextColor = Color.Black; + internal readonly Color DisabledContactTextColor = Color.Gray; internal readonly Color EnabledContactTextColor = Color.White; internal virtual void UpdateContactLabels() @@ -683,7 +683,7 @@ internal virtual void DrawContactLabels() zedGraphChannels.GraphPane.GraphObjList.RemoveAll(obj => obj is TextObj && obj.Tag is ContactTag); - var fontSize = CalculateFontSize(); + var fontSize = CalculateFontSize(0.5); int probeNumber = 0; @@ -748,7 +748,7 @@ internal void UpdateFontSize() } } - internal virtual float CalculateFontSize() + internal virtual float CalculateFontSize(double scale = 1.0) { float rangeY = (float)(zedGraphChannels.GraphPane.YAxis.Scale.Max - zedGraphChannels.GraphPane.YAxis.Scale.Min); @@ -759,7 +759,7 @@ internal virtual float CalculateFontSize() fontSize = fontSize < 1f ? 0.001f : fontSize; fontSize = fontSize > 100f ? 100f : fontSize; - return fontSize; + return (float)scale * fontSize; } internal float ContactSize() diff --git a/OpenEphys.Onix1.Design/DesignHelper.cs b/OpenEphys.Onix1.Design/DesignHelper.cs index d6e8b94e..e8832316 100644 --- a/OpenEphys.Onix1.Design/DesignHelper.cs +++ b/OpenEphys.Onix1.Design/DesignHelper.cs @@ -16,7 +16,7 @@ public static T DeserializeString(string channelLayout) public static void SerializeObject(object _object, string filepath) { - var stringJson = JsonConvert.SerializeObject(_object); + var stringJson = JsonConvert.SerializeObject(_object, Formatting.Indented); File.WriteAllText(filepath, stringJson); } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 2e8da79f..254f9329 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -184,7 +184,9 @@ internal override void DrawScale() var curve = zedGraphChannels.GraphPane.AddCurve(ScalePointsTag, pointList, Color.Black, SymbolType.None); - curve.Line.Width = zoomedOut ? 2 : 4; + const float scaleBarWidth = 1; + + curve.Line.Width = scaleBarWidth; curve.Label.IsVisible = false; curve.Symbol.IsVisible = false; } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs index b7e78591..ed7e8539 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs @@ -29,27 +29,21 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelGain; - System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelProbe; System.Windows.Forms.Label probeCalibrationFile; System.Windows.Forms.Label Reference; System.Windows.Forms.Label label7; System.Windows.Forms.Label label6; System.Windows.Forms.Label labelPresets; System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NeuropixelsV2eProbeConfigurationDialog)); - this.toolStripLabelProbeNumber = 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.probeSn = new System.Windows.Forms.ToolStripStatusLabel(); - this.gain = new System.Windows.Forms.ToolStripStatusLabel(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.splitContainer2 = new System.Windows.Forms.SplitContainer(); this.panelProbe = new System.Windows.Forms.Panel(); this.panelTrackBar = new System.Windows.Forms.Panel(); this.trackBarProbePosition = new System.Windows.Forms.TrackBar(); this.panelChannelOptions = new System.Windows.Forms.Panel(); - this.buttonClearCalibrationFile = new System.Windows.Forms.Button(); this.buttonChooseCalibrationFile = new System.Windows.Forms.Button(); this.textBoxProbeCalibrationFile = new System.Windows.Forms.TextBox(); this.comboBoxReference = new System.Windows.Forms.ComboBox(); @@ -61,15 +55,12 @@ private void InitializeComponent() this.buttonCancel = new System.Windows.Forms.Button(); this.buttonOkay = new System.Windows.Forms.Button(); this.toolTip = new System.Windows.Forms.ToolTip(this.components); - toolStripStatusLabelGain = new System.Windows.Forms.ToolStripStatusLabel(); - toolStripStatusLabelProbe = new System.Windows.Forms.ToolStripStatusLabel(); probeCalibrationFile = new System.Windows.Forms.Label(); Reference = new System.Windows.Forms.Label(); label7 = new System.Windows.Forms.Label(); label6 = new System.Windows.Forms.Label(); labelPresets = 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(); @@ -85,20 +76,6 @@ private void InitializeComponent() this.panel1.SuspendLayout(); this.SuspendLayout(); // - // toolStripStatusLabelGain - // - toolStripStatusLabelGain.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); - toolStripStatusLabelGain.Name = "toolStripStatusLabelGain"; - toolStripStatusLabelGain.Size = new System.Drawing.Size(100, 17); - toolStripStatusLabelGain.Text = "Gain Correction: "; - // - // toolStripStatusLabelProbe - // - toolStripStatusLabelProbe.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); - toolStripStatusLabelProbe.Name = "toolStripStatusLabelProbe"; - toolStripStatusLabelProbe.Size = new System.Drawing.Size(29, 17); - toolStripStatusLabelProbe.Text = "SN: "; - // // probeCalibrationFile // probeCalibrationFile.AutoSize = true; @@ -106,19 +83,19 @@ private void InitializeComponent() probeCalibrationFile.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); probeCalibrationFile.MaximumSize = new System.Drawing.Size(133, 29); probeCalibrationFile.Name = "probeCalibrationFile"; - probeCalibrationFile.Size = new System.Drawing.Size(106, 13); + probeCalibrationFile.Size = new System.Drawing.Size(109, 13); probeCalibrationFile.TabIndex = 32; - probeCalibrationFile.Text = "Probe Calibration File"; + probeCalibrationFile.Text = "Probe Calibration File:"; // // Reference // Reference.AutoSize = true; - Reference.Location = new System.Drawing.Point(8, 87); + Reference.Location = new System.Drawing.Point(8, 62); Reference.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); Reference.Name = "Reference"; - Reference.Size = new System.Drawing.Size(57, 13); + Reference.Size = new System.Drawing.Size(60, 13); Reference.TabIndex = 30; - Reference.Text = "Reference"; + Reference.Text = "Reference:"; // // label7 // @@ -145,19 +122,12 @@ private void InitializeComponent() // labelPresets // labelPresets.AutoSize = true; - labelPresets.Location = new System.Drawing.Point(8, 119); + labelPresets.Location = new System.Drawing.Point(8, 94); labelPresets.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); labelPresets.Name = "labelPresets"; - labelPresets.Size = new System.Drawing.Size(46, 26); + labelPresets.Size = new System.Drawing.Size(49, 26); labelPresets.TabIndex = 23; - labelPresets.Text = "Channel\r\nPresets"; - // - // toolStripLabelProbeNumber - // - this.toolStripLabelProbeNumber.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); - this.toolStripLabelProbeNumber.Name = "toolStripLabelProbeNumber"; - this.toolStripLabelProbeNumber.Size = new System.Drawing.Size(40, 17); - this.toolStripLabelProbeNumber.Text = "Probe"; + labelPresets.Text = "Channel \nPresets:"; // // menuStrip // @@ -180,12 +150,6 @@ private void InitializeComponent() // statusStrip // this.statusStrip.ImageScalingSize = new System.Drawing.Size(24, 24); - this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.toolStripLabelProbeNumber, - toolStripStatusLabelProbe, - this.probeSn, - toolStripStatusLabelGain, - this.gain}); this.statusStrip.Location = new System.Drawing.Point(0, 511); this.statusStrip.Name = "statusStrip"; this.statusStrip.Padding = new System.Windows.Forms.Padding(1, 0, 9, 0); @@ -193,20 +157,6 @@ private void InitializeComponent() this.statusStrip.TabIndex = 1; this.statusStrip.Text = "statusStrip1"; // - // probeSn - // - this.probeSn.AutoSize = false; - this.probeSn.Name = "probeSn"; - this.probeSn.Size = new System.Drawing.Size(120, 17); - this.probeSn.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // gain - // - this.gain.AutoSize = false; - this.gain.Name = "gain"; - this.gain.Size = new System.Drawing.Size(120, 17); - this.gain.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // // splitContainer1 // this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; @@ -224,7 +174,7 @@ private void InitializeComponent() // this.splitContainer1.Panel2.Controls.Add(this.panel1); this.splitContainer1.Size = new System.Drawing.Size(834, 487); - this.splitContainer1.SplitterDistance = 458; + this.splitContainer1.SplitterDistance = 459; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 2; // @@ -243,8 +193,8 @@ private void InitializeComponent() // splitContainer2.Panel2 // this.splitContainer2.Panel2.Controls.Add(this.panelChannelOptions); - this.splitContainer2.Size = new System.Drawing.Size(834, 458); - this.splitContainer2.SplitterDistance = 622; + this.splitContainer2.Size = new System.Drawing.Size(834, 459); + this.splitContainer2.SplitterDistance = 624; this.splitContainer2.SplitterWidth = 3; this.splitContainer2.TabIndex = 1; // @@ -255,7 +205,7 @@ private void InitializeComponent() this.panelProbe.Location = new System.Drawing.Point(0, 0); this.panelProbe.Margin = new System.Windows.Forms.Padding(2); this.panelProbe.Name = "panelProbe"; - this.panelProbe.Size = new System.Drawing.Size(622, 458); + this.panelProbe.Size = new System.Drawing.Size(624, 459); this.panelProbe.TabIndex = 1; // // panelTrackBar @@ -264,7 +214,7 @@ private void InitializeComponent() this.panelTrackBar.Controls.Add(label6); this.panelTrackBar.Controls.Add(label7); this.panelTrackBar.Controls.Add(this.trackBarProbePosition); - this.panelTrackBar.Location = new System.Drawing.Point(580, 1); + this.panelTrackBar.Location = new System.Drawing.Point(582, -11); this.panelTrackBar.Name = "panelTrackBar"; this.panelTrackBar.Size = new System.Drawing.Size(39, 456); this.panelTrackBar.TabIndex = 30; @@ -289,7 +239,6 @@ private void InitializeComponent() // panelChannelOptions // this.panelChannelOptions.BackColor = System.Drawing.SystemColors.ControlLightLight; - this.panelChannelOptions.Controls.Add(this.buttonClearCalibrationFile); this.panelChannelOptions.Controls.Add(this.buttonChooseCalibrationFile); this.panelChannelOptions.Controls.Add(this.textBoxProbeCalibrationFile); this.panelChannelOptions.Controls.Add(probeCalibrationFile); @@ -304,70 +253,65 @@ private void InitializeComponent() this.panelChannelOptions.Location = new System.Drawing.Point(0, 0); this.panelChannelOptions.Margin = new System.Windows.Forms.Padding(2); this.panelChannelOptions.Name = "panelChannelOptions"; - this.panelChannelOptions.Size = new System.Drawing.Size(209, 458); + this.panelChannelOptions.Size = new System.Drawing.Size(207, 459); this.panelChannelOptions.TabIndex = 1; // - // buttonClearCalibrationFile - // - this.buttonClearCalibrationFile.Location = new System.Drawing.Point(103, 45); - this.buttonClearCalibrationFile.Margin = new System.Windows.Forms.Padding(2); - this.buttonClearCalibrationFile.Name = "buttonClearCalibrationFile"; - this.buttonClearCalibrationFile.Size = new System.Drawing.Size(94, 21); - this.buttonClearCalibrationFile.TabIndex = 35; - this.buttonClearCalibrationFile.Text = "Clear"; - this.buttonClearCalibrationFile.UseVisualStyleBackColor = true; - this.buttonClearCalibrationFile.Click += new System.EventHandler(this.ButtonClick); - // // buttonChooseCalibrationFile // - this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(8, 45); + this.buttonChooseCalibrationFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(172, 24); this.buttonChooseCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.buttonChooseCalibrationFile.Name = "buttonChooseCalibrationFile"; - this.buttonChooseCalibrationFile.Size = new System.Drawing.Size(94, 21); + this.buttonChooseCalibrationFile.Size = new System.Drawing.Size(28, 20); this.buttonChooseCalibrationFile.TabIndex = 34; - this.buttonChooseCalibrationFile.Text = "Choose"; + this.buttonChooseCalibrationFile.Text = "..."; this.buttonChooseCalibrationFile.UseVisualStyleBackColor = true; this.buttonChooseCalibrationFile.Click += new System.EventHandler(this.ButtonClick); // // textBoxProbeCalibrationFile // - this.textBoxProbeCalibrationFile.Location = new System.Drawing.Point(8, 24); + this.textBoxProbeCalibrationFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBoxProbeCalibrationFile.Location = new System.Drawing.Point(11, 24); this.textBoxProbeCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.textBoxProbeCalibrationFile.Name = "textBoxProbeCalibrationFile"; - this.textBoxProbeCalibrationFile.ReadOnly = true; - this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(189, 20); + this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(157, 20); this.textBoxProbeCalibrationFile.TabIndex = 33; - this.textBoxProbeCalibrationFile.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; - this.textBoxProbeCalibrationFile.TextChanged += new System.EventHandler(this.FileTextChanged); // // comboBoxReference // + this.comboBoxReference.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.comboBoxReference.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxReference.FormattingEnabled = true; - this.comboBoxReference.Location = new System.Drawing.Point(78, 83); + this.comboBoxReference.Location = new System.Drawing.Point(78, 58); this.comboBoxReference.Margin = new System.Windows.Forms.Padding(2); this.comboBoxReference.Name = "comboBoxReference"; - this.comboBoxReference.Size = new System.Drawing.Size(119, 21); + this.comboBoxReference.Size = new System.Drawing.Size(118, 21); this.comboBoxReference.TabIndex = 31; // // comboBoxChannelPresets // + this.comboBoxChannelPresets.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.comboBoxChannelPresets.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxChannelPresets.FormattingEnabled = true; - this.comboBoxChannelPresets.Location = new System.Drawing.Point(78, 122); + this.comboBoxChannelPresets.Location = new System.Drawing.Point(78, 97); this.comboBoxChannelPresets.Margin = new System.Windows.Forms.Padding(2); this.comboBoxChannelPresets.Name = "comboBoxChannelPresets"; - this.comboBoxChannelPresets.Size = new System.Drawing.Size(119, 21); + this.comboBoxChannelPresets.Size = new System.Drawing.Size(118, 21); this.comboBoxChannelPresets.TabIndex = 24; // // buttonEnableContacts // - this.buttonEnableContacts.Location = new System.Drawing.Point(8, 165); + this.buttonEnableContacts.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.buttonEnableContacts.Location = new System.Drawing.Point(11, 138); this.buttonEnableContacts.Margin = new System.Windows.Forms.Padding(2); this.buttonEnableContacts.Name = "buttonEnableContacts"; - this.buttonEnableContacts.Size = new System.Drawing.Size(94, 36); + this.buttonEnableContacts.Size = new System.Drawing.Size(185, 36); this.buttonEnableContacts.TabIndex = 20; - this.buttonEnableContacts.Text = "Enable Selected Contacts"; + this.buttonEnableContacts.Text = "Enable Selected Electrodes"; this.toolTip.SetToolTip(this.buttonEnableContacts, "Click and drag to select contacts in the probe view. \r\nPress this button to enabl" + "e the selected contacts."); this.buttonEnableContacts.UseVisualStyleBackColor = true; @@ -375,12 +319,14 @@ private void InitializeComponent() // // buttonClearSelections // - this.buttonClearSelections.Location = new System.Drawing.Point(103, 165); + this.buttonClearSelections.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.buttonClearSelections.Location = new System.Drawing.Point(11, 178); this.buttonClearSelections.Margin = new System.Windows.Forms.Padding(2); this.buttonClearSelections.Name = "buttonClearSelections"; - this.buttonClearSelections.Size = new System.Drawing.Size(94, 36); + this.buttonClearSelections.Size = new System.Drawing.Size(185, 36); this.buttonClearSelections.TabIndex = 19; - this.buttonClearSelections.Text = "Deselect Contacts"; + this.buttonClearSelections.Text = "Clear Electrode Selection"; this.toolTip.SetToolTip(this.buttonClearSelections, "Remove selections from contacts in the probe view. Press this button to deselect " + "contacts.\r\nNote that this does not disable contacts, but simply deselects them."); this.buttonClearSelections.UseVisualStyleBackColor = true; @@ -388,10 +334,12 @@ private void InitializeComponent() // // buttonResetZoom // - this.buttonResetZoom.Location = new System.Drawing.Point(57, 208); + this.buttonResetZoom.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.buttonResetZoom.Location = new System.Drawing.Point(11, 218); this.buttonResetZoom.Margin = new System.Windows.Forms.Padding(2); this.buttonResetZoom.Name = "buttonResetZoom"; - this.buttonResetZoom.Size = new System.Drawing.Size(94, 36); + this.buttonResetZoom.Size = new System.Drawing.Size(185, 36); this.buttonResetZoom.TabIndex = 4; this.buttonResetZoom.Text = "Reset Zoom"; this.toolTip.SetToolTip(this.buttonResetZoom, "Reset the zoom in the probe view so that the probe is zoomed out and centered.\r\nP" + @@ -407,14 +355,14 @@ private void InitializeComponent() this.panel1.Location = new System.Drawing.Point(0, 0); this.panel1.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(834, 26); + this.panel1.Size = new System.Drawing.Size(834, 25); this.panel1.TabIndex = 0; // // 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(744, 0); + this.buttonCancel.Location = new System.Drawing.Point(744, -1); this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(83, 22); @@ -426,7 +374,7 @@ private void InitializeComponent() // 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(655, 0); + this.buttonOkay.Location = new System.Drawing.Point(655, -1); this.buttonOkay.Margin = new System.Windows.Forms.Padding(2); this.buttonOkay.Name = "buttonOkay"; this.buttonOkay.Size = new System.Drawing.Size(83, 22); @@ -452,8 +400,6 @@ private void InitializeComponent() this.Text = "NeuropixelsV2eProbeConfigurationDialog"; 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(); @@ -482,13 +428,10 @@ private void InitializeComponent() private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Button buttonCancel; private System.Windows.Forms.Button buttonOkay; - private System.Windows.Forms.ToolStripStatusLabel probeSn; private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; private System.Windows.Forms.SplitContainer splitContainer2; - private System.Windows.Forms.ToolStripStatusLabel gain; private System.Windows.Forms.Panel panelProbe; private System.Windows.Forms.Panel panelChannelOptions; - private System.Windows.Forms.Button buttonClearCalibrationFile; private System.Windows.Forms.Button buttonChooseCalibrationFile; internal System.Windows.Forms.TextBox textBoxProbeCalibrationFile; private System.Windows.Forms.ComboBox comboBoxReference; @@ -497,7 +440,6 @@ private void InitializeComponent() private System.Windows.Forms.Button buttonEnableContacts; private System.Windows.Forms.Button buttonClearSelections; private System.Windows.Forms.Button buttonResetZoom; - private System.Windows.Forms.ToolStripStatusLabel toolStripLabelProbeNumber; private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.Panel panelTrackBar; } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs index 94da9184..866dca9c 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -67,8 +67,6 @@ public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2QuadShankProbeConfigu textBoxProbeCalibrationFile.Text = calibrationFile; - toolStripLabelProbeNumber.Text = GetProbeName(ProbeConfiguration.Probe); - ChannelConfiguration = new(ProbeConfiguration) { TopLevel = false, @@ -128,39 +126,6 @@ private void ResizeTrackBar(object sender, EventArgs e) } } - private void FileTextChanged(object sender, EventArgs e) - { - if (sender is TextBox textBox && textBox != null && textBox.Name == nameof(textBoxProbeCalibrationFile)) - { - ParseGainCalibrationFile(textBox.Text, probeSn, gain); - } - } - - private void ParseGainCalibrationFile(string filename, ToolStripStatusLabel probeSN, ToolStripStatusLabel gainLabel) - { - if (filename != null && filename != "") - { - if (File.Exists(filename)) - { - using StreamReader gainCalibrationFile = new(filename); - - probeSN.Text = ulong.Parse(gainCalibrationFile.ReadLine()).ToString(); - gainLabel.Text = double.Parse(gainCalibrationFile.ReadLine()).ToString(); - } - else - { - probeSN.Text = ""; - gainLabel.Text = ""; - } - } - - else - { - probeSN.Text = ""; - gainLabel.Text = ""; - } - } - private void SelectedIndexChanged(object sender, EventArgs e) { var comboBox = sender as ComboBox; @@ -601,11 +566,6 @@ internal void ButtonClick(object sender, EventArgs e) ChannelConfiguration.UpdateContactLabels(); ChannelConfiguration.RefreshZedGraph(); } - else if (button.Name == nameof(buttonClearCalibrationFile)) - { - textBoxProbeCalibrationFile.Text = ""; - panelProbe.Visible = false; - } } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx index 46c4fa6a..47aa9537 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx @@ -117,12 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - False - - - False - False From 40113880f89d67e3c6afa9d8aa6d1cf7e9cb3ab8 Mon Sep 17 00:00:00 2001 From: jonnew Date: Tue, 20 Aug 2024 13:24:37 -0400 Subject: [PATCH 28/35] Use CV.XorS instead of for loop in twos-comp to offset binary - Tested in hardware appears to work well --- OpenEphys.Onix1/BreakoutAnalogOutput.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/OpenEphys.Onix1/BreakoutAnalogOutput.cs b/OpenEphys.Onix1/BreakoutAnalogOutput.cs index cbcf0918..15911bc9 100644 --- a/OpenEphys.Onix1/BreakoutAnalogOutput.cs +++ b/OpenEphys.Onix1/BreakoutAnalogOutput.cs @@ -89,12 +89,9 @@ public override unsafe IObservable Process(IObservable source) var dataSize = outputBuffer.Step * outputBuffer.Rows; // twos-complement to offset binary - var dataPtr = (short *)outputBuffer.Data.ToPointer(); const short Mask = -32768; - for (int i = 0; i < dataSize / sizeof(short); i++) - *(dataPtr + i) ^= Mask; - - device.Write(outputBuffer.Data, dataSize); + CV.XorS(outputBuffer, new Scalar(Mask, 0, 0), outputBuffer); + device.Write(outputBuffer.Data, dataSize); }); }); } From 5ed0c9e036164042270339aaf63811bbeaacb5a4 Mon Sep 17 00:00:00 2001 From: jonnew Date: Tue, 20 Aug 2024 13:44:53 -0400 Subject: [PATCH 29/35] Finish missing XML comments for neuropixels gain files - I only updated the description for one of the files in both the Neuropixles2.0e and 2.0ebeta configuration operators --- OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs | 15 ++++++++++----- OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs | 15 ++++++++++----- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index 5740e463..aed1cbd8 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -41,11 +41,16 @@ public ConfigureNeuropixelsV2e() /// Gets or sets the path to the gain calibration file for Probe A. /// /// - /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// + /// Each probe is linked to a gain calibration file that contains gain adjustments determined by IMEC during /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. - /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be - /// with the probe serial number to retrieve a new copy. + /// + /// + /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the + /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration + /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. + /// /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe A.")] @@ -69,8 +74,8 @@ public ConfigureNeuropixelsV2e() /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. /// /// - /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the - /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration + /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the + /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. /// /// diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index f9462a5d..ec6b1f6a 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -57,8 +57,8 @@ public ConfigureNeuropixelsV2eBeta() /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. /// /// - /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the - /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration + /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the + /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. /// /// @@ -78,11 +78,16 @@ public ConfigureNeuropixelsV2eBeta() /// Gets or sets the path to the gain calibration file for Probe B. /// /// - /// Each probe is linked to a gain calibration file that contains a gain adjustments determined by IMEC during + /// + /// Each probe is linked to a gain calibration file that contains gain adjustments determined by IMEC during /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. - /// If you have lost track of the gain calibration file for your probe, you can email IMEC at neuropixels.info@imec.be - /// with the probe serial number to retrieve a new copy. + /// + /// + /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the + /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration + /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. + /// /// [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] [Description("Path to the gain calibration file for probe B.")] From 70bc6a92ac02b68fa1bd16fdcac881202b1d8914 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Tue, 20 Aug 2024 13:49:16 -0400 Subject: [PATCH 30/35] Minor fixes - Don't allow manual resizing of splitter panels - Minor adjustments to controls spacing and alignment - Remove unused methods / controls - Add check if a file exists to hide/show probe panel --- .../ChannelConfigurationDialog.Designer.cs | 5 +- .../GenericDeviceDialog.Designer.cs | 5 +- .../NeuropixelsV2eHeadstageDialog.Designer.cs | 17 +++--- ...elsV2eProbeConfigurationDialog.Designer.cs | 56 ++++++++----------- .../NeuropixelsV2eProbeConfigurationDialog.cs | 32 ++++++----- ...europixelsV2eProbeConfigurationDialog.resx | 3 - 6 files changed, 57 insertions(+), 61 deletions(-) diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs index 597be31d..2da9e161 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.Designer.cs @@ -50,6 +50,7 @@ private void InitializeComponent() // this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.IsSplitterFixed = true; this.splitContainer1.Location = new System.Drawing.Point(0, 24); this.splitContainer1.Margin = new System.Windows.Forms.Padding(2); this.splitContainer1.Name = "splitContainer1"; @@ -64,7 +65,7 @@ private void InitializeComponent() this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); this.splitContainer1.Panel2.Controls.Add(this.buttonOK); this.splitContainer1.Size = new System.Drawing.Size(457, 442); - this.splitContainer1.SplitterDistance = 403; + this.splitContainer1.SplitterDistance = 404; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 0; // @@ -82,7 +83,7 @@ private void InitializeComponent() this.zedGraphChannels.ScrollMinX = 0D; this.zedGraphChannels.ScrollMinY = 0D; this.zedGraphChannels.ScrollMinY2 = 0D; - this.zedGraphChannels.Size = new System.Drawing.Size(457, 403); + this.zedGraphChannels.Size = new System.Drawing.Size(457, 404); this.zedGraphChannels.TabIndex = 4; this.zedGraphChannels.UseExtendedPrintDialog = true; this.zedGraphChannels.ZoomEvent += new ZedGraph.ZedGraphControl.ZoomEventHandler(this.ZoomEvent); diff --git a/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs b/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs index 9bc11b83..34e6b6db 100644 --- a/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/GenericDeviceDialog.Designer.cs @@ -45,13 +45,14 @@ private void InitializeComponent() this.propertyGrid.Location = new System.Drawing.Point(0, 0); this.propertyGrid.Margin = new System.Windows.Forms.Padding(2); this.propertyGrid.Name = "propertyGrid"; - this.propertyGrid.Size = new System.Drawing.Size(252, 347); + this.propertyGrid.Size = new System.Drawing.Size(252, 348); this.propertyGrid.TabIndex = 0; // // splitContainer1 // this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Right; this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.IsSplitterFixed = true; this.splitContainer1.Location = new System.Drawing.Point(0, 0); this.splitContainer1.Margin = new System.Windows.Forms.Padding(2); this.splitContainer1.Name = "splitContainer1"; @@ -66,7 +67,7 @@ private void InitializeComponent() this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); this.splitContainer1.Panel2.Controls.Add(this.buttonOK); this.splitContainer1.Size = new System.Drawing.Size(252, 386); - this.splitContainer1.SplitterDistance = 347; + this.splitContainer1.SplitterDistance = 348; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 1; // diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs index cfecfc7f..91afd654 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.Designer.cs @@ -58,7 +58,7 @@ private void InitializeComponent() this.tabControl1.Margin = new System.Windows.Forms.Padding(2); this.tabControl1.Name = "tabControl1"; this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(863, 469); + this.tabControl1.Size = new System.Drawing.Size(863, 468); this.tabControl1.TabIndex = 0; // // tabPageNeuropixelsV2e @@ -68,7 +68,7 @@ private void InitializeComponent() this.tabPageNeuropixelsV2e.Margin = new System.Windows.Forms.Padding(2); this.tabPageNeuropixelsV2e.Name = "tabPageNeuropixelsV2e"; this.tabPageNeuropixelsV2e.Padding = new System.Windows.Forms.Padding(2); - this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(855, 443); + this.tabPageNeuropixelsV2e.Size = new System.Drawing.Size(855, 442); this.tabPageNeuropixelsV2e.TabIndex = 0; this.tabPageNeuropixelsV2e.Text = "NeuropixelsV2e"; this.tabPageNeuropixelsV2e.UseVisualStyleBackColor = true; @@ -79,7 +79,7 @@ private void InitializeComponent() this.panelNeuropixelsV2e.Location = new System.Drawing.Point(2, 2); this.panelNeuropixelsV2e.Margin = new System.Windows.Forms.Padding(2); this.panelNeuropixelsV2e.Name = "panelNeuropixelsV2e"; - this.panelNeuropixelsV2e.Size = new System.Drawing.Size(851, 439); + this.panelNeuropixelsV2e.Size = new System.Drawing.Size(851, 438); this.panelNeuropixelsV2e.TabIndex = 0; // // tabPageBno055 @@ -89,7 +89,7 @@ private void InitializeComponent() this.tabPageBno055.Margin = new System.Windows.Forms.Padding(2); this.tabPageBno055.Name = "tabPageBno055"; this.tabPageBno055.Padding = new System.Windows.Forms.Padding(2); - this.tabPageBno055.Size = new System.Drawing.Size(855, 443); + this.tabPageBno055.Size = new System.Drawing.Size(855, 444); this.tabPageBno055.TabIndex = 1; this.tabPageBno055.Text = "Bno055"; this.tabPageBno055.UseVisualStyleBackColor = true; @@ -100,13 +100,14 @@ private void InitializeComponent() this.panelBno055.Location = new System.Drawing.Point(2, 2); this.panelBno055.Margin = new System.Windows.Forms.Padding(2); this.panelBno055.Name = "panelBno055"; - this.panelBno055.Size = new System.Drawing.Size(851, 439); + this.panelBno055.Size = new System.Drawing.Size(851, 440); this.panelBno055.TabIndex = 0; // // splitContainer1 // this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.IsSplitterFixed = true; this.splitContainer1.Location = new System.Drawing.Point(0, 24); this.splitContainer1.Margin = new System.Windows.Forms.Padding(2); this.splitContainer1.Name = "splitContainer1"; @@ -121,7 +122,7 @@ private void InitializeComponent() this.splitContainer1.Panel2.Controls.Add(this.buttonCancel); this.splitContainer1.Panel2.Controls.Add(this.buttonOkay); this.splitContainer1.Size = new System.Drawing.Size(863, 503); - this.splitContainer1.SplitterDistance = 469; + this.splitContainer1.SplitterDistance = 468; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 1; // @@ -129,7 +130,7 @@ private void InitializeComponent() // 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(747, 3); + this.buttonCancel.Location = new System.Drawing.Point(747, -1); this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(108, 26); @@ -140,7 +141,7 @@ private void InitializeComponent() // 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(625, 3); + this.buttonOkay.Location = new System.Drawing.Point(625, -1); this.buttonOkay.Margin = new System.Windows.Forms.Padding(2); this.buttonOkay.Name = "buttonOkay"; this.buttonOkay.Size = new System.Drawing.Size(108, 26); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs index ed7e8539..63122a73 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs @@ -37,7 +37,6 @@ private void InitializeComponent() System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NeuropixelsV2eProbeConfigurationDialog)); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.statusStrip = new System.Windows.Forms.StatusStrip(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.splitContainer2 = new System.Windows.Forms.SplitContainer(); this.panelProbe = new System.Windows.Forms.Panel(); @@ -112,7 +111,7 @@ private void InitializeComponent() // label6.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); label6.AutoSize = true; - label6.Location = new System.Drawing.Point(4, 443); + label6.Location = new System.Drawing.Point(4, 461); label6.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label6.Name = "label6"; label6.Size = new System.Drawing.Size(32, 13); @@ -147,20 +146,11 @@ private void InitializeComponent() this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 22); this.fileToolStripMenuItem.Text = "File"; // - // statusStrip - // - this.statusStrip.ImageScalingSize = new System.Drawing.Size(24, 24); - this.statusStrip.Location = new System.Drawing.Point(0, 511); - this.statusStrip.Name = "statusStrip"; - this.statusStrip.Padding = new System.Windows.Forms.Padding(1, 0, 9, 0); - this.statusStrip.Size = new System.Drawing.Size(834, 22); - this.statusStrip.TabIndex = 1; - this.statusStrip.Text = "statusStrip1"; - // // splitContainer1 // this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.IsSplitterFixed = true; this.splitContainer1.Location = new System.Drawing.Point(0, 24); this.splitContainer1.Margin = new System.Windows.Forms.Padding(2); this.splitContainer1.Name = "splitContainer1"; @@ -173,8 +163,8 @@ private void InitializeComponent() // splitContainer1.Panel2 // this.splitContainer1.Panel2.Controls.Add(this.panel1); - this.splitContainer1.Size = new System.Drawing.Size(834, 487); - this.splitContainer1.SplitterDistance = 459; + this.splitContainer1.Size = new System.Drawing.Size(834, 509); + this.splitContainer1.SplitterDistance = 477; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 2; // @@ -182,6 +172,7 @@ private void InitializeComponent() // this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; this.splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer2.IsSplitterFixed = true; this.splitContainer2.Location = new System.Drawing.Point(0, 0); this.splitContainer2.Margin = new System.Windows.Forms.Padding(2); this.splitContainer2.Name = "splitContainer2"; @@ -193,8 +184,8 @@ private void InitializeComponent() // splitContainer2.Panel2 // this.splitContainer2.Panel2.Controls.Add(this.panelChannelOptions); - this.splitContainer2.Size = new System.Drawing.Size(834, 459); - this.splitContainer2.SplitterDistance = 624; + this.splitContainer2.Size = new System.Drawing.Size(834, 477); + this.splitContainer2.SplitterDistance = 625; this.splitContainer2.SplitterWidth = 3; this.splitContainer2.TabIndex = 1; // @@ -205,7 +196,7 @@ private void InitializeComponent() this.panelProbe.Location = new System.Drawing.Point(0, 0); this.panelProbe.Margin = new System.Windows.Forms.Padding(2); this.panelProbe.Name = "panelProbe"; - this.panelProbe.Size = new System.Drawing.Size(624, 459); + this.panelProbe.Size = new System.Drawing.Size(625, 477); this.panelProbe.TabIndex = 1; // // panelTrackBar @@ -214,9 +205,9 @@ private void InitializeComponent() this.panelTrackBar.Controls.Add(label6); this.panelTrackBar.Controls.Add(label7); this.panelTrackBar.Controls.Add(this.trackBarProbePosition); - this.panelTrackBar.Location = new System.Drawing.Point(582, -11); + this.panelTrackBar.Location = new System.Drawing.Point(583, 1); this.panelTrackBar.Name = "panelTrackBar"; - this.panelTrackBar.Size = new System.Drawing.Size(39, 456); + this.panelTrackBar.Size = new System.Drawing.Size(39, 474); this.panelTrackBar.TabIndex = 30; // // trackBarProbePosition @@ -229,7 +220,7 @@ private void InitializeComponent() this.trackBarProbePosition.Maximum = 100; this.trackBarProbePosition.Name = "trackBarProbePosition"; this.trackBarProbePosition.Orientation = System.Windows.Forms.Orientation.Vertical; - this.trackBarProbePosition.Size = new System.Drawing.Size(37, 452); + this.trackBarProbePosition.Size = new System.Drawing.Size(37, 470); this.trackBarProbePosition.TabIndex = 22; this.trackBarProbePosition.TickFrequency = 2; this.trackBarProbePosition.TickStyle = System.Windows.Forms.TickStyle.Both; @@ -253,13 +244,13 @@ private void InitializeComponent() this.panelChannelOptions.Location = new System.Drawing.Point(0, 0); this.panelChannelOptions.Margin = new System.Windows.Forms.Padding(2); this.panelChannelOptions.Name = "panelChannelOptions"; - this.panelChannelOptions.Size = new System.Drawing.Size(207, 459); + this.panelChannelOptions.Size = new System.Drawing.Size(206, 477); this.panelChannelOptions.TabIndex = 1; // // buttonChooseCalibrationFile // this.buttonChooseCalibrationFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(172, 24); + this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(171, 24); this.buttonChooseCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.buttonChooseCalibrationFile.Name = "buttonChooseCalibrationFile"; this.buttonChooseCalibrationFile.Size = new System.Drawing.Size(28, 20); @@ -275,8 +266,9 @@ private void InitializeComponent() this.textBoxProbeCalibrationFile.Location = new System.Drawing.Point(11, 24); this.textBoxProbeCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.textBoxProbeCalibrationFile.Name = "textBoxProbeCalibrationFile"; - this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(157, 20); + this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(156, 20); this.textBoxProbeCalibrationFile.TabIndex = 33; + this.textBoxProbeCalibrationFile.TextChanged += new System.EventHandler(this.FileTextChanged); // // comboBoxReference // @@ -287,7 +279,7 @@ private void InitializeComponent() this.comboBoxReference.Location = new System.Drawing.Point(78, 58); this.comboBoxReference.Margin = new System.Windows.Forms.Padding(2); this.comboBoxReference.Name = "comboBoxReference"; - this.comboBoxReference.Size = new System.Drawing.Size(118, 21); + this.comboBoxReference.Size = new System.Drawing.Size(117, 21); this.comboBoxReference.TabIndex = 31; // // comboBoxChannelPresets @@ -299,7 +291,7 @@ private void InitializeComponent() this.comboBoxChannelPresets.Location = new System.Drawing.Point(78, 97); this.comboBoxChannelPresets.Margin = new System.Windows.Forms.Padding(2); this.comboBoxChannelPresets.Name = "comboBoxChannelPresets"; - this.comboBoxChannelPresets.Size = new System.Drawing.Size(118, 21); + this.comboBoxChannelPresets.Size = new System.Drawing.Size(117, 21); this.comboBoxChannelPresets.TabIndex = 24; // // buttonEnableContacts @@ -309,7 +301,7 @@ private void InitializeComponent() this.buttonEnableContacts.Location = new System.Drawing.Point(11, 138); this.buttonEnableContacts.Margin = new System.Windows.Forms.Padding(2); this.buttonEnableContacts.Name = "buttonEnableContacts"; - this.buttonEnableContacts.Size = new System.Drawing.Size(185, 36); + this.buttonEnableContacts.Size = new System.Drawing.Size(184, 36); this.buttonEnableContacts.TabIndex = 20; this.buttonEnableContacts.Text = "Enable Selected Electrodes"; this.toolTip.SetToolTip(this.buttonEnableContacts, "Click and drag to select contacts in the probe view. \r\nPress this button to enabl" + @@ -324,7 +316,7 @@ private void InitializeComponent() this.buttonClearSelections.Location = new System.Drawing.Point(11, 178); this.buttonClearSelections.Margin = new System.Windows.Forms.Padding(2); this.buttonClearSelections.Name = "buttonClearSelections"; - this.buttonClearSelections.Size = new System.Drawing.Size(185, 36); + this.buttonClearSelections.Size = new System.Drawing.Size(184, 36); this.buttonClearSelections.TabIndex = 19; this.buttonClearSelections.Text = "Clear Electrode Selection"; this.toolTip.SetToolTip(this.buttonClearSelections, "Remove selections from contacts in the probe view. Press this button to deselect " + @@ -339,7 +331,7 @@ private void InitializeComponent() this.buttonResetZoom.Location = new System.Drawing.Point(11, 218); this.buttonResetZoom.Margin = new System.Windows.Forms.Padding(2); this.buttonResetZoom.Name = "buttonResetZoom"; - this.buttonResetZoom.Size = new System.Drawing.Size(185, 36); + this.buttonResetZoom.Size = new System.Drawing.Size(184, 36); this.buttonResetZoom.TabIndex = 4; this.buttonResetZoom.Text = "Reset Zoom"; this.toolTip.SetToolTip(this.buttonResetZoom, "Reset the zoom in the probe view so that the probe is zoomed out and centered.\r\nP" + @@ -355,14 +347,14 @@ private void InitializeComponent() this.panel1.Location = new System.Drawing.Point(0, 0); this.panel1.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(834, 25); + this.panel1.Size = new System.Drawing.Size(834, 29); this.panel1.TabIndex = 0; // // 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(744, -1); + this.buttonCancel.Location = new System.Drawing.Point(744, 3); this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(83, 22); @@ -374,7 +366,7 @@ private void InitializeComponent() // 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(655, -1); + this.buttonOkay.Location = new System.Drawing.Point(655, 3); this.buttonOkay.Margin = new System.Windows.Forms.Padding(2); this.buttonOkay.Name = "buttonOkay"; this.buttonOkay.Size = new System.Drawing.Size(83, 22); @@ -389,7 +381,6 @@ private void InitializeComponent() this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(834, 533); this.Controls.Add(this.splitContainer1); - this.Controls.Add(this.statusStrip); this.Controls.Add(this.menuStrip); this.DoubleBuffered = true; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); @@ -423,7 +414,6 @@ private void InitializeComponent() #endregion private System.Windows.Forms.MenuStrip menuStrip; - private System.Windows.Forms.StatusStrip statusStrip; private System.Windows.Forms.SplitContainer splitContainer1; private System.Windows.Forms.Panel panel1; private System.Windows.Forms.Button buttonCancel; diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs index 866dca9c..e9fd788b 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -78,7 +78,7 @@ public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2QuadShankProbeConfigu panelProbe.Controls.Add(ChannelConfiguration); this.AddMenuItemsFromDialogToFileOption(ChannelConfiguration); - panelProbe.Visible = File.Exists(textBoxProbeCalibrationFile.Text); + panelProbe.Visible = IsProbeCalibrationFileValid(textBoxProbeCalibrationFile.Text); ChannelConfiguration.OnZoom += UpdateTrackBarLocation; ChannelConfiguration.OnFileLoad += OnFileLoadEvent; @@ -92,16 +92,6 @@ public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2QuadShankProbeConfigu CheckForExistingChannelPreset(); } - private string GetProbeName(NeuropixelsV2Probe probe) - { - return probe switch - { - NeuropixelsV2Probe.ProbeA => "Probe A", - NeuropixelsV2Probe.ProbeB => "Probe B", - _ => throw new ArgumentException("Invalid probe was specified.") - }; - } - private void FormShown(object sender, EventArgs e) { if (!TopLevel) @@ -517,6 +507,22 @@ private void OnFileLoadEvent(object sender, EventArgs e) CheckForExistingChannelPreset(); } + private void FileTextChanged(object sender, EventArgs e) + { + if (sender is TextBox textBox && textBox != null && textBox.Name == nameof(textBoxProbeCalibrationFile)) + { + panelProbe.Visible = IsProbeCalibrationFileValid(textBoxProbeCalibrationFile.Text); + } + } + + private bool IsProbeCalibrationFileValid(string file) + { + if (string.IsNullOrEmpty(file)) + return false; + + return File.Exists(file); + } + internal void ButtonClick(object sender, EventArgs e) { if (sender is Button button && button != null) @@ -537,11 +543,11 @@ internal void ButtonClick(object sender, EventArgs e) if (ofd.ShowDialog() == DialogResult.OK) { textBoxProbeCalibrationFile.Text = ofd.FileName; - panelProbe.Visible = true; + panelProbe.Visible = IsProbeCalibrationFileValid(textBoxProbeCalibrationFile.Text); } else { - panelProbe.Visible = File.Exists(textBoxProbeCalibrationFile.Text); + panelProbe.Visible = IsProbeCalibrationFileValid(textBoxProbeCalibrationFile.Text); } } else if (button.Name == nameof(buttonResetZoom)) diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx index 47aa9537..578a83a0 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.resx @@ -135,9 +135,6 @@ 17, 17 - - 165, 17 - 274, 17 From 118a7ab01b50ccbd20d83c4a8ef88bbe8a455b5f Mon Sep 17 00:00:00 2001 From: bparks13 Date: Tue, 20 Aug 2024 14:19:33 -0400 Subject: [PATCH 31/35] Allow users to expand right-side panel --- ...elsV2eProbeConfigurationDialog.Designer.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs index 63122a73..f24f9370 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs @@ -164,7 +164,7 @@ private void InitializeComponent() // this.splitContainer1.Panel2.Controls.Add(this.panel1); this.splitContainer1.Size = new System.Drawing.Size(834, 509); - this.splitContainer1.SplitterDistance = 477; + this.splitContainer1.SplitterDistance = 478; this.splitContainer1.SplitterWidth = 3; this.splitContainer1.TabIndex = 2; // @@ -172,7 +172,6 @@ private void InitializeComponent() // this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; this.splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; - this.splitContainer2.IsSplitterFixed = true; this.splitContainer2.Location = new System.Drawing.Point(0, 0); this.splitContainer2.Margin = new System.Windows.Forms.Padding(2); this.splitContainer2.Name = "splitContainer2"; @@ -184,8 +183,8 @@ private void InitializeComponent() // splitContainer2.Panel2 // this.splitContainer2.Panel2.Controls.Add(this.panelChannelOptions); - this.splitContainer2.Size = new System.Drawing.Size(834, 477); - this.splitContainer2.SplitterDistance = 625; + this.splitContainer2.Size = new System.Drawing.Size(834, 478); + this.splitContainer2.SplitterDistance = 626; this.splitContainer2.SplitterWidth = 3; this.splitContainer2.TabIndex = 1; // @@ -196,7 +195,7 @@ private void InitializeComponent() this.panelProbe.Location = new System.Drawing.Point(0, 0); this.panelProbe.Margin = new System.Windows.Forms.Padding(2); this.panelProbe.Name = "panelProbe"; - this.panelProbe.Size = new System.Drawing.Size(625, 477); + this.panelProbe.Size = new System.Drawing.Size(626, 478); this.panelProbe.TabIndex = 1; // // panelTrackBar @@ -205,7 +204,7 @@ private void InitializeComponent() this.panelTrackBar.Controls.Add(label6); this.panelTrackBar.Controls.Add(label7); this.panelTrackBar.Controls.Add(this.trackBarProbePosition); - this.panelTrackBar.Location = new System.Drawing.Point(583, 1); + this.panelTrackBar.Location = new System.Drawing.Point(584, 2); this.panelTrackBar.Name = "panelTrackBar"; this.panelTrackBar.Size = new System.Drawing.Size(39, 474); this.panelTrackBar.TabIndex = 30; @@ -244,13 +243,13 @@ private void InitializeComponent() this.panelChannelOptions.Location = new System.Drawing.Point(0, 0); this.panelChannelOptions.Margin = new System.Windows.Forms.Padding(2); this.panelChannelOptions.Name = "panelChannelOptions"; - this.panelChannelOptions.Size = new System.Drawing.Size(206, 477); + this.panelChannelOptions.Size = new System.Drawing.Size(205, 478); this.panelChannelOptions.TabIndex = 1; // // buttonChooseCalibrationFile // this.buttonChooseCalibrationFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(171, 24); + this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(170, 24); this.buttonChooseCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.buttonChooseCalibrationFile.Name = "buttonChooseCalibrationFile"; this.buttonChooseCalibrationFile.Size = new System.Drawing.Size(28, 20); @@ -266,7 +265,7 @@ private void InitializeComponent() this.textBoxProbeCalibrationFile.Location = new System.Drawing.Point(11, 24); this.textBoxProbeCalibrationFile.Margin = new System.Windows.Forms.Padding(2); this.textBoxProbeCalibrationFile.Name = "textBoxProbeCalibrationFile"; - this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(156, 20); + this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(155, 20); this.textBoxProbeCalibrationFile.TabIndex = 33; this.textBoxProbeCalibrationFile.TextChanged += new System.EventHandler(this.FileTextChanged); // @@ -279,7 +278,7 @@ private void InitializeComponent() this.comboBoxReference.Location = new System.Drawing.Point(78, 58); this.comboBoxReference.Margin = new System.Windows.Forms.Padding(2); this.comboBoxReference.Name = "comboBoxReference"; - this.comboBoxReference.Size = new System.Drawing.Size(117, 21); + this.comboBoxReference.Size = new System.Drawing.Size(116, 21); this.comboBoxReference.TabIndex = 31; // // comboBoxChannelPresets @@ -291,7 +290,7 @@ private void InitializeComponent() this.comboBoxChannelPresets.Location = new System.Drawing.Point(78, 97); this.comboBoxChannelPresets.Margin = new System.Windows.Forms.Padding(2); this.comboBoxChannelPresets.Name = "comboBoxChannelPresets"; - this.comboBoxChannelPresets.Size = new System.Drawing.Size(117, 21); + this.comboBoxChannelPresets.Size = new System.Drawing.Size(116, 21); this.comboBoxChannelPresets.TabIndex = 24; // // buttonEnableContacts @@ -301,7 +300,7 @@ private void InitializeComponent() this.buttonEnableContacts.Location = new System.Drawing.Point(11, 138); this.buttonEnableContacts.Margin = new System.Windows.Forms.Padding(2); this.buttonEnableContacts.Name = "buttonEnableContacts"; - this.buttonEnableContacts.Size = new System.Drawing.Size(184, 36); + this.buttonEnableContacts.Size = new System.Drawing.Size(183, 36); this.buttonEnableContacts.TabIndex = 20; this.buttonEnableContacts.Text = "Enable Selected Electrodes"; this.toolTip.SetToolTip(this.buttonEnableContacts, "Click and drag to select contacts in the probe view. \r\nPress this button to enabl" + @@ -316,7 +315,7 @@ private void InitializeComponent() this.buttonClearSelections.Location = new System.Drawing.Point(11, 178); this.buttonClearSelections.Margin = new System.Windows.Forms.Padding(2); this.buttonClearSelections.Name = "buttonClearSelections"; - this.buttonClearSelections.Size = new System.Drawing.Size(184, 36); + this.buttonClearSelections.Size = new System.Drawing.Size(183, 36); this.buttonClearSelections.TabIndex = 19; this.buttonClearSelections.Text = "Clear Electrode Selection"; this.toolTip.SetToolTip(this.buttonClearSelections, "Remove selections from contacts in the probe view. Press this button to deselect " + @@ -331,7 +330,7 @@ private void InitializeComponent() this.buttonResetZoom.Location = new System.Drawing.Point(11, 218); this.buttonResetZoom.Margin = new System.Windows.Forms.Padding(2); this.buttonResetZoom.Name = "buttonResetZoom"; - this.buttonResetZoom.Size = new System.Drawing.Size(184, 36); + this.buttonResetZoom.Size = new System.Drawing.Size(183, 36); this.buttonResetZoom.TabIndex = 4; this.buttonResetZoom.Text = "Reset Zoom"; this.toolTip.SetToolTip(this.buttonResetZoom, "Reset the zoom in the probe view so that the probe is zoomed out and centered.\r\nP" + @@ -347,14 +346,14 @@ private void InitializeComponent() this.panel1.Location = new System.Drawing.Point(0, 0); this.panel1.Margin = new System.Windows.Forms.Padding(2); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(834, 29); + this.panel1.Size = new System.Drawing.Size(834, 28); this.panel1.TabIndex = 0; // // 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(744, 3); + this.buttonCancel.Location = new System.Drawing.Point(744, 2); this.buttonCancel.Margin = new System.Windows.Forms.Padding(2); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(83, 22); @@ -366,7 +365,7 @@ private void InitializeComponent() // 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(655, 3); + this.buttonOkay.Location = new System.Drawing.Point(655, 2); this.buttonOkay.Margin = new System.Windows.Forms.Padding(2); this.buttonOkay.Name = "buttonOkay"; this.buttonOkay.Size = new System.Drawing.Size(83, 22); From 54e635b94ee17edc3f96a9a4fac5adecefba7a42 Mon Sep 17 00:00:00 2001 From: jonnew Date: Tue, 20 Aug 2024 15:18:41 -0400 Subject: [PATCH 32/35] Rename LinkController variable to PortControl - PortController was prefereable but clashed with the static class PortController, requiring its namespace prefix to be used to disinguish it. --- OpenEphys.Onix1/ConfigureHeadstage64.cs | 12 ++++++------ OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs | 12 ++++++------ .../ConfigureNeuropixelsV2eBetaHeadstage.cs | 12 ++++++------ OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs | 12 ++++++------ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureHeadstage64.cs b/OpenEphys.Onix1/ConfigureHeadstage64.cs index 161ed2e3..0d1a4c70 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64.cs @@ -11,7 +11,7 @@ namespace OpenEphys.Onix1 public class ConfigureHeadstage64 : MultiDeviceFactory { PortName port; - readonly ConfigureHeadstage64PortController LinkController = new(); + readonly ConfigureHeadstage64PortController PortControl = new(); /// /// Initializes a new instance of the class. @@ -35,7 +35,7 @@ public ConfigureHeadstage64() // 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; + PortControl.HubConfiguration = HubConfiguration.Standard; } /// @@ -92,7 +92,7 @@ public PortName Port { port = value; var offset = (uint)port << 8; - LinkController.DeviceAddress = (uint)port; + PortControl.DeviceAddress = (uint)port; Rhd2164.DeviceAddress = offset + 0; Bno055.DeviceAddress = offset + 1; TS4231.DeviceAddress = offset + 2; @@ -122,13 +122,13 @@ public PortName Port "Supplying higher voltages may result in damage to the headstage.")] public double? PortVoltage { - get => LinkController.PortVoltage; - set => LinkController.PortVoltage = value; + get => PortControl.PortVoltage; + set => PortControl.PortVoltage = value; } internal override IEnumerable GetDevices() { - yield return LinkController; + yield return PortControl; yield return Rhd2164; yield return Bno055; yield return TS4231; diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs index 69585a67..e0f40a18 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1eHeadstage.cs @@ -11,7 +11,7 @@ namespace OpenEphys.Onix1 public class ConfigureNeuropixelsV1eHeadstage : MultiDeviceFactory { PortName port; - readonly ConfigureNeuropixelsV1ePortController LinkController = new(); + readonly ConfigureNeuropixelsV1ePortController PortControl = new(); /// /// Initialize a new instance of a class. @@ -19,7 +19,7 @@ public class ConfigureNeuropixelsV1eHeadstage : MultiDeviceFactory public ConfigureNeuropixelsV1eHeadstage() { Port = PortName.PortA; - LinkController.HubConfiguration = HubConfiguration.Passthrough; + PortControl.HubConfiguration = HubConfiguration.Passthrough; } /// @@ -52,7 +52,7 @@ public PortName Port { port = value; var offset = (uint)port << 8; - LinkController.DeviceAddress = (uint)port; + PortControl.DeviceAddress = (uint)port; NeuropixelsV1e.DeviceAddress = offset + 0; Bno055.DeviceAddress = offset + 1; } @@ -72,13 +72,13 @@ public PortName Port "for proper operation. Higher voltages can damage the headstage.")] public double? PortVoltage { - get => LinkController.PortVoltage; - set => LinkController.PortVoltage = value; + get => PortControl.PortVoltage; + set => PortControl.PortVoltage = value; } internal override IEnumerable GetDevices() { - yield return LinkController; + yield return PortControl; yield return NeuropixelsV1e; yield return Bno055; } diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs index 87137174..9464ac86 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBetaHeadstage.cs @@ -10,7 +10,7 @@ namespace OpenEphys.Onix1 public class ConfigureNeuropixelsV2eBetaHeadstage : MultiDeviceFactory { PortName port; - readonly ConfigureNeuropixelsV2ePortController LinkController = new(); + readonly ConfigureNeuropixelsV2ePortController PortControl = new(); /// /// Initialize a new instance of a class. @@ -18,7 +18,7 @@ public class ConfigureNeuropixelsV2eBetaHeadstage : MultiDeviceFactory public ConfigureNeuropixelsV2eBetaHeadstage() { Port = PortName.PortA; - LinkController.HubConfiguration = HubConfiguration.Passthrough; + PortControl.HubConfiguration = HubConfiguration.Passthrough; } /// @@ -51,7 +51,7 @@ public PortName Port { port = value; var offset = (uint)port << 8; - LinkController.DeviceAddress = (uint)port; + PortControl.DeviceAddress = (uint)port; NeuropixelsV2eBeta.DeviceAddress = offset + 0; Bno055.DeviceAddress = offset + 1; } @@ -71,13 +71,13 @@ public PortName Port "for proper operation. Higher voltages can damage the headstage.")] public double? PortVoltage { - get => LinkController.PortVoltage; - set => LinkController.PortVoltage = value; + get => PortControl.PortVoltage; + set => PortControl.PortVoltage = value; } internal override IEnumerable GetDevices() { - yield return LinkController; + yield return PortControl; yield return NeuropixelsV2eBeta; yield return Bno055; } diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs index 9c00b137..be6847fb 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eHeadstage.cs @@ -10,7 +10,7 @@ namespace OpenEphys.Onix1 public class ConfigureNeuropixelsV2eHeadstage : MultiDeviceFactory { PortName port; - readonly ConfigureNeuropixelsV2ePortController LinkController = new(); + readonly ConfigureNeuropixelsV2ePortController PortControl = new(); /// /// Initialize a new instance of a class. @@ -18,7 +18,7 @@ public class ConfigureNeuropixelsV2eHeadstage : MultiDeviceFactory public ConfigureNeuropixelsV2eHeadstage() { Port = PortName.PortA; - LinkController.HubConfiguration = HubConfiguration.Passthrough; + PortControl.HubConfiguration = HubConfiguration.Passthrough; } /// @@ -51,7 +51,7 @@ public PortName Port { port = value; var offset = (uint)port << 8; - LinkController.DeviceAddress = (uint)port; + PortControl.DeviceAddress = (uint)port; NeuropixelsV2e.DeviceAddress = offset + 0; Bno055.DeviceAddress = offset + 1; } @@ -71,13 +71,13 @@ public PortName Port "for proper operation. Higher voltages can damage the headstage.")] public double? PortVoltage { - get => LinkController.PortVoltage; - set => LinkController.PortVoltage = value; + get => PortControl.PortVoltage; + set => PortControl.PortVoltage = value; } internal override IEnumerable GetDevices() { - yield return LinkController; + yield return PortControl; yield return NeuropixelsV2e; yield return Bno055; } From 06713b56b81419afa9c9ffc2e8fda325831f1757 Mon Sep 17 00:00:00 2001 From: Jonathan Newman Date: Wed, 21 Aug 2024 08:30:53 -0400 Subject: [PATCH 33/35] Update README.md - Make image a more reasonable size --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index caa6e3c4..0f9a2914 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,14 @@ Acquisition System](https://open-ephys.github.io/onix-docs). ### Run in visual studio 1. Double click the `Setup.cmd` file in the `.bonsai` folder. This will install a - portable version of Bonsai in the `.bonsai` folder along with the Bonsai packages - required to compile and run the library. + portable version of Bonsai in the folder along with its required packages. 1. Open `OpenEphys.Onix1.sln` in Visual Studio (Community Edition is fine). 1. Select which project to compile and run Bonsai against. - `OpenEphys.Onix1`: core library - `OpenEphys.Onix1.Design`: core library and GUI elements 1. Press the Play button to - - Compile the library + - Compile the library selected in step 3 - Run the Bonsai application installed in step 1 - Instruct Bonsai to load the compiled library -![Select which library to compile](./.bonsai/build-and-run.png) \ No newline at end of file +Select which library to compile From 3621cd3dbd3c4e8f3f2125fba0f66c6c8c36a882 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Wed, 21 Aug 2024 09:22:04 -0400 Subject: [PATCH 34/35] Bump version to 0.2.0 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 64baf9c1..679e3f6d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,7 +13,7 @@ LICENSE true icon.png - 0.1.0 + 0.2.0 9.0 strict From 045a968d8b802b36f0882776257f5331f9b18910 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Wed, 21 Aug 2024 11:55:50 -0400 Subject: [PATCH 35/35] Remove link to internal class - When building the docfx website, we do not render private or internal classes, leading to a broken reference --- OpenEphys.Onix1/PortStatus.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenEphys.Onix1/PortStatus.cs b/OpenEphys.Onix1/PortStatus.cs index 88514db6..97719ef0 100644 --- a/OpenEphys.Onix1/PortStatus.cs +++ b/OpenEphys.Onix1/PortStatus.cs @@ -12,7 +12,7 @@ namespace OpenEphys.Onix1 /// /// This data stream class must be linked to an appropriate headstage, /// miniscope, etc. configuration whose communication is dictated by - /// a . + /// a PortController. /// [Description("Produces a sequence of port status information.")] public class PortStatus : Source