diff --git a/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.Designer.cs b/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.Designer.cs index a73db59..2b37b51 100644 --- a/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.Designer.cs @@ -41,12 +41,12 @@ private void InitializeComponent() this.labelAmplitudeCathodic = new System.Windows.Forms.Label(); this.labelPulseWidthCathodic = new System.Windows.Forms.Label(); this.textboxPulseWidthCathodic = new System.Windows.Forms.TextBox(); - this.textboxAmplitudeCathodic = new System.Windows.Forms.TextBox(); + this.textboxAmplitudeCathodicRequested = new System.Windows.Forms.TextBox(); this.groupBoxAnode = new System.Windows.Forms.GroupBox(); this.labelAmplitudeAnodic = new System.Windows.Forms.Label(); this.labelPulseWidthAnodic = new System.Windows.Forms.Label(); this.textboxPulseWidthAnodic = new System.Windows.Forms.TextBox(); - this.textboxAmplitudeAnodic = new System.Windows.Forms.TextBox(); + this.textboxAmplitudeAnodicRequested = new System.Windows.Forms.TextBox(); this.buttonClearPulses = new System.Windows.Forms.Button(); this.buttonReadPulses = new System.Windows.Forms.Button(); this.textboxInterPulseInterval = new System.Windows.Forms.TextBox(); @@ -76,6 +76,8 @@ private void InitializeComponent() this.groupBox1 = new System.Windows.Forms.GroupBox(); this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.textboxAmplitudeAnodic = new System.Windows.Forms.TextBox(); + this.textboxAmplitudeCathodic = new System.Windows.Forms.TextBox(); this.statusStrip.SuspendLayout(); this.panelParameters.SuspendLayout(); this.groupBoxCathode.SuspendLayout(); @@ -171,7 +173,7 @@ private void InitializeComponent() this.panelParameters.Location = new System.Drawing.Point(3, 17); this.panelParameters.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.panelParameters.Name = "panelParameters"; - this.panelParameters.Size = new System.Drawing.Size(439, 251); + this.panelParameters.Size = new System.Drawing.Size(439, 285); this.panelParameters.TabIndex = 0; // // textBoxStepSize @@ -187,15 +189,16 @@ private void InitializeComponent() // // groupBoxCathode // + this.groupBoxCathode.Controls.Add(this.textboxAmplitudeCathodic); this.groupBoxCathode.Controls.Add(this.labelAmplitudeCathodic); this.groupBoxCathode.Controls.Add(this.labelPulseWidthCathodic); this.groupBoxCathode.Controls.Add(this.textboxPulseWidthCathodic); - this.groupBoxCathode.Controls.Add(this.textboxAmplitudeCathodic); + this.groupBoxCathode.Controls.Add(this.textboxAmplitudeCathodicRequested); this.groupBoxCathode.Location = new System.Drawing.Point(231, 94); this.groupBoxCathode.Margin = new System.Windows.Forms.Padding(4); this.groupBoxCathode.Name = "groupBoxCathode"; this.groupBoxCathode.Padding = new System.Windows.Forms.Padding(4); - this.groupBoxCathode.Size = new System.Drawing.Size(195, 69); + this.groupBoxCathode.Size = new System.Drawing.Size(195, 101); this.groupBoxCathode.TabIndex = 3; this.groupBoxCathode.TabStop = false; this.groupBoxCathode.Text = "Cathode"; @@ -213,7 +216,7 @@ private void InitializeComponent() // labelPulseWidthCathodic // this.labelPulseWidthCathodic.AutoSize = true; - this.labelPulseWidthCathodic.Location = new System.Drawing.Point(11, 44); + this.labelPulseWidthCathodic.Location = new System.Drawing.Point(11, 73); this.labelPulseWidthCathodic.Name = "labelPulseWidthCathodic"; this.labelPulseWidthCathodic.Size = new System.Drawing.Size(107, 16); this.labelPulseWidthCathodic.TabIndex = 24; @@ -221,7 +224,7 @@ private void InitializeComponent() // // textboxPulseWidthCathodic // - this.textboxPulseWidthCathodic.Location = new System.Drawing.Point(132, 39); + this.textboxPulseWidthCathodic.Location = new System.Drawing.Point(132, 68); this.textboxPulseWidthCathodic.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.textboxPulseWidthCathodic.Name = "textboxPulseWidthCathodic"; this.textboxPulseWidthCathodic.Size = new System.Drawing.Size(55, 22); @@ -229,26 +232,27 @@ private void InitializeComponent() this.textboxPulseWidthCathodic.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.ParameterKeyPress_Time); this.textboxPulseWidthCathodic.Leave += new System.EventHandler(this.Samples_TextChanged); // - // textboxAmplitudeCathodic + // textboxAmplitudeCathodicRequested // - this.textboxAmplitudeCathodic.Location = new System.Drawing.Point(132, 14); - this.textboxAmplitudeCathodic.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); - this.textboxAmplitudeCathodic.Name = "textboxAmplitudeCathodic"; - this.textboxAmplitudeCathodic.Size = new System.Drawing.Size(55, 22); - this.textboxAmplitudeCathodic.TabIndex = 5; - this.textboxAmplitudeCathodic.Leave += new System.EventHandler(this.Amplitude_TextChanged); + this.textboxAmplitudeCathodicRequested.Location = new System.Drawing.Point(132, 14); + this.textboxAmplitudeCathodicRequested.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.textboxAmplitudeCathodicRequested.Name = "textboxAmplitudeCathodicRequested"; + this.textboxAmplitudeCathodicRequested.Size = new System.Drawing.Size(55, 22); + this.textboxAmplitudeCathodicRequested.TabIndex = 5; + this.textboxAmplitudeCathodicRequested.Leave += new System.EventHandler(this.Amplitude_TextChanged); // // groupBoxAnode // + this.groupBoxAnode.Controls.Add(this.textboxAmplitudeAnodic); this.groupBoxAnode.Controls.Add(this.labelAmplitudeAnodic); this.groupBoxAnode.Controls.Add(this.labelPulseWidthAnodic); this.groupBoxAnode.Controls.Add(this.textboxPulseWidthAnodic); - this.groupBoxAnode.Controls.Add(this.textboxAmplitudeAnodic); + this.groupBoxAnode.Controls.Add(this.textboxAmplitudeAnodicRequested); this.groupBoxAnode.Location = new System.Drawing.Point(13, 94); this.groupBoxAnode.Margin = new System.Windows.Forms.Padding(4); this.groupBoxAnode.Name = "groupBoxAnode"; this.groupBoxAnode.Padding = new System.Windows.Forms.Padding(4); - this.groupBoxAnode.Size = new System.Drawing.Size(195, 69); + this.groupBoxAnode.Size = new System.Drawing.Size(195, 101); this.groupBoxAnode.TabIndex = 2; this.groupBoxAnode.TabStop = false; this.groupBoxAnode.Text = "Anode"; @@ -265,7 +269,7 @@ private void InitializeComponent() // labelPulseWidthAnodic // this.labelPulseWidthAnodic.AutoSize = true; - this.labelPulseWidthAnodic.Location = new System.Drawing.Point(8, 44); + this.labelPulseWidthAnodic.Location = new System.Drawing.Point(8, 73); this.labelPulseWidthAnodic.Name = "labelPulseWidthAnodic"; this.labelPulseWidthAnodic.Size = new System.Drawing.Size(107, 16); this.labelPulseWidthAnodic.TabIndex = 7; @@ -273,7 +277,7 @@ private void InitializeComponent() // // textboxPulseWidthAnodic // - this.textboxPulseWidthAnodic.Location = new System.Drawing.Point(129, 39); + this.textboxPulseWidthAnodic.Location = new System.Drawing.Point(129, 68); this.textboxPulseWidthAnodic.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.textboxPulseWidthAnodic.Name = "textboxPulseWidthAnodic"; this.textboxPulseWidthAnodic.Size = new System.Drawing.Size(55, 22); @@ -281,19 +285,19 @@ private void InitializeComponent() this.textboxPulseWidthAnodic.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.ParameterKeyPress_Time); this.textboxPulseWidthAnodic.Leave += new System.EventHandler(this.Samples_TextChanged); // - // textboxAmplitudeAnodic + // textboxAmplitudeAnodicRequested // - this.textboxAmplitudeAnodic.Location = new System.Drawing.Point(129, 14); - this.textboxAmplitudeAnodic.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); - this.textboxAmplitudeAnodic.Name = "textboxAmplitudeAnodic"; - this.textboxAmplitudeAnodic.Size = new System.Drawing.Size(55, 22); - this.textboxAmplitudeAnodic.TabIndex = 3; - this.textboxAmplitudeAnodic.Leave += new System.EventHandler(this.Amplitude_TextChanged); + this.textboxAmplitudeAnodicRequested.Location = new System.Drawing.Point(129, 14); + this.textboxAmplitudeAnodicRequested.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.textboxAmplitudeAnodicRequested.Name = "textboxAmplitudeAnodicRequested"; + this.textboxAmplitudeAnodicRequested.Size = new System.Drawing.Size(55, 22); + this.textboxAmplitudeAnodicRequested.TabIndex = 3; + this.textboxAmplitudeAnodicRequested.Leave += new System.EventHandler(this.Amplitude_TextChanged); // // buttonClearPulses // this.buttonClearPulses.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F); - this.buttonClearPulses.Location = new System.Drawing.Point(19, 206); + this.buttonClearPulses.Location = new System.Drawing.Point(19, 238); this.buttonClearPulses.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.buttonClearPulses.Name = "buttonClearPulses"; this.buttonClearPulses.Size = new System.Drawing.Size(99, 34); @@ -308,7 +312,7 @@ private void InitializeComponent() // buttonReadPulses // this.buttonReadPulses.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F); - this.buttonReadPulses.Location = new System.Drawing.Point(123, 206); + this.buttonReadPulses.Location = new System.Drawing.Point(123, 238); this.buttonReadPulses.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.buttonReadPulses.Name = "buttonReadPulses"; this.buttonReadPulses.Size = new System.Drawing.Size(100, 34); @@ -368,7 +372,7 @@ private void InitializeComponent() // buttonAddPulses // this.buttonAddPulses.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F); - this.buttonAddPulses.Location = new System.Drawing.Point(318, 206); + this.buttonAddPulses.Location = new System.Drawing.Point(318, 238); this.buttonAddPulses.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.buttonAddPulses.Name = "buttonAddPulses"; this.buttonAddPulses.Size = new System.Drawing.Size(100, 34); @@ -392,7 +396,7 @@ private void InitializeComponent() // labelNumberOfPulses // this.labelNumberOfPulses.AutoSize = true; - this.labelNumberOfPulses.Location = new System.Drawing.Point(237, 174); + this.labelNumberOfPulses.Location = new System.Drawing.Point(237, 206); this.labelNumberOfPulses.Name = "labelNumberOfPulses"; this.labelNumberOfPulses.Size = new System.Drawing.Size(113, 16); this.labelNumberOfPulses.TabIndex = 13; @@ -409,7 +413,7 @@ private void InitializeComponent() // // textboxNumberOfStimuli // - this.textboxNumberOfStimuli.Location = new System.Drawing.Point(363, 170); + this.textboxNumberOfStimuli.Location = new System.Drawing.Point(363, 202); this.textboxNumberOfStimuli.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.textboxNumberOfStimuli.Name = "textboxNumberOfStimuli"; this.textboxNumberOfStimuli.Size = new System.Drawing.Size(55, 22); @@ -433,7 +437,7 @@ private void InitializeComponent() // // textboxInterStimulusInterval // - this.textboxInterStimulusInterval.Location = new System.Drawing.Point(143, 169); + this.textboxInterStimulusInterval.Location = new System.Drawing.Point(143, 201); this.textboxInterStimulusInterval.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.textboxInterStimulusInterval.Name = "textboxInterStimulusInterval"; this.textboxInterStimulusInterval.Size = new System.Drawing.Size(55, 22); @@ -444,7 +448,7 @@ private void InitializeComponent() // labelInterStimulusInterval // this.labelInterStimulusInterval.AutoSize = true; - this.labelInterStimulusInterval.Location = new System.Drawing.Point(15, 172); + this.labelInterStimulusInterval.Location = new System.Drawing.Point(15, 204); this.labelInterStimulusInterval.Name = "labelInterStimulusInterval"; this.labelInterStimulusInterval.Size = new System.Drawing.Size(115, 16); this.labelInterStimulusInterval.TabIndex = 9; @@ -499,7 +503,7 @@ private void InitializeComponent() this.tabPageTable.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.tabPageTable.Name = "tabPageTable"; this.tabPageTable.Padding = new System.Windows.Forms.Padding(3, 2, 3, 2); - this.tabPageTable.Size = new System.Drawing.Size(1082, 646); + this.tabPageTable.Size = new System.Drawing.Size(1082, 644); this.tabPageTable.TabIndex = 1; this.tabPageTable.Text = "Table"; this.tabPageTable.UseVisualStyleBackColor = true; @@ -517,7 +521,7 @@ private void InitializeComponent() this.dataGridViewStimulusTable.Name = "dataGridViewStimulusTable"; this.dataGridViewStimulusTable.RowHeadersWidth = 62; this.dataGridViewStimulusTable.RowTemplate.Height = 28; - this.dataGridViewStimulusTable.Size = new System.Drawing.Size(1076, 642); + this.dataGridViewStimulusTable.Size = new System.Drawing.Size(1076, 640); this.dataGridViewStimulusTable.TabIndex = 0; this.dataGridViewStimulusTable.CellEndEdit += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridViewStimulusTable_CellEndEdit); this.dataGridViewStimulusTable.DataBindingComplete += new System.Windows.Forms.DataGridViewBindingCompleteEventHandler(this.DataGridViewStimulusTable_DataBindingComplete); @@ -529,7 +533,7 @@ private void InitializeComponent() this.panelProbe.Location = new System.Drawing.Point(1099, 2); this.panelProbe.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.panelProbe.Name = "panelProbe"; - this.panelProbe.Size = new System.Drawing.Size(445, 401); + this.panelProbe.Size = new System.Drawing.Size(445, 367); this.panelProbe.TabIndex = 0; // // menuStrip @@ -590,7 +594,7 @@ private void InitializeComponent() this.tableLayoutPanel1.Name = "tableLayoutPanel1"; this.tableLayoutPanel1.RowCount = 3; this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 274F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 308F)); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 42F)); this.tableLayoutPanel1.Size = new System.Drawing.Size(1547, 721); this.tableLayoutPanel1.TabIndex = 8; @@ -599,11 +603,11 @@ private void InitializeComponent() // this.groupBox1.Controls.Add(this.panelParameters); this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill; - this.groupBox1.Location = new System.Drawing.Point(1099, 407); + this.groupBox1.Location = new System.Drawing.Point(1099, 373); this.groupBox1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.groupBox1.Name = "groupBox1"; this.groupBox1.Padding = new System.Windows.Forms.Padding(3, 2, 3, 2); - this.groupBox1.Size = new System.Drawing.Size(445, 270); + this.groupBox1.Size = new System.Drawing.Size(445, 304); this.groupBox1.TabIndex = 0; this.groupBox1.TabStop = false; this.groupBox1.Text = "Define Stimuli"; @@ -621,6 +625,24 @@ private void InitializeComponent() this.flowLayoutPanel1.Size = new System.Drawing.Size(1541, 38); this.flowLayoutPanel1.TabIndex = 7; // + // textboxAmplitudeAnodic + // + this.textboxAmplitudeAnodic.Location = new System.Drawing.Point(129, 39); + this.textboxAmplitudeAnodic.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.textboxAmplitudeAnodic.Name = "textboxAmplitudeAnodic"; + this.textboxAmplitudeAnodic.ReadOnly = true; + this.textboxAmplitudeAnodic.Size = new System.Drawing.Size(55, 22); + this.textboxAmplitudeAnodic.TabIndex = 8; + // + // textboxAmplitudeCathodic + // + this.textboxAmplitudeCathodic.Location = new System.Drawing.Point(132, 39); + this.textboxAmplitudeCathodic.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.textboxAmplitudeCathodic.Name = "textboxAmplitudeCathodic"; + this.textboxAmplitudeCathodic.ReadOnly = true; + this.textboxAmplitudeCathodic.Size = new System.Drawing.Size(55, 22); + this.textboxAmplitudeCathodic.TabIndex = 9; + // // Rhs2116StimulusSequenceDialog // this.AccessibleDescription = ""; @@ -668,7 +690,7 @@ private void InitializeComponent() private System.Windows.Forms.Button buttonOk; private System.Windows.Forms.Label labelAmplitudeAnodic; private System.Windows.Forms.Label labelDelay; - private System.Windows.Forms.TextBox textboxAmplitudeAnodic; + private System.Windows.Forms.TextBox textboxAmplitudeAnodicRequested; private System.Windows.Forms.TextBox textboxDelay; private System.Windows.Forms.CheckBox checkboxBiphasicSymmetrical; private System.Windows.Forms.Button buttonAddPulses; @@ -688,7 +710,7 @@ private void InitializeComponent() private System.Windows.Forms.Label labelAmplitudeCathodic; private System.Windows.Forms.Label labelPulseWidthCathodic; private System.Windows.Forms.TextBox textboxPulseWidthCathodic; - private System.Windows.Forms.TextBox textboxAmplitudeCathodic; + private System.Windows.Forms.TextBox textboxAmplitudeCathodicRequested; private System.Windows.Forms.TextBox textboxInterPulseInterval; private System.Windows.Forms.Label labelInterPulseInterval; private System.Windows.Forms.Button buttonClearPulses; @@ -707,5 +729,7 @@ private void InitializeComponent() private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; private System.Windows.Forms.TextBox textBoxStepSize; private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox textboxAmplitudeCathodic; + private System.Windows.Forms.TextBox textboxAmplitudeAnodic; } } diff --git a/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.cs b/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.cs index 178a5fd..9872758 100644 --- a/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.cs +++ b/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.cs @@ -12,8 +12,15 @@ namespace OpenEphys.Onix1.Design /// public partial class Rhs2116StimulusSequenceDialog : Form { + const double SamplePeriodMilliSeconds = 1e3 / Rhs2116.SampleFrequencyHz; + internal Rhs2116StimulusSequencePair Sequence { get; set; } + private readonly Rhs2116StimulusSequencePair SequenceCopy = new(); + + private readonly double[] RequestedAnodicAmplitudeuA; + private readonly double[] RequestedCathodicAmplitudeuA; + /// /// Holds the step size that is displayed in the text box of the GUI. This is not the step size that is saved for the stimulus sequence object. /// @@ -21,11 +28,9 @@ public partial class Rhs2116StimulusSequenceDialog : Form internal readonly Rhs2116ChannelConfigurationDialog ChannelDialog; - private const double SamplePeriodMilliSeconds = 1e3 / 30.1932367151e3; const double MinAmplitudeuA = 0.01; // NB: Minimum possible amplitude is 10 nA (0.01 µA) const double MaxAmplitudeuA = 2550; // NB: Maximum possible amplitude is 2550000 nA (2550 µA) - /// /// Opens a dialog allowing for easy changing of stimulus sequence parameters, with visual feedback on what the resulting stimulus sequence looks like. /// @@ -37,6 +42,15 @@ public Rhs2116StimulusSequenceDialog(Rhs2116StimulusSequencePair sequence, Rhs21 Shown += FormShown; Sequence = new Rhs2116StimulusSequencePair(sequence); + RequestedAnodicAmplitudeuA = new double[Sequence.Stimuli.Length]; + RequestedCathodicAmplitudeuA = new double[Sequence.Stimuli.Length]; + + for (int i = 0; i < Sequence.Stimuli.Length; i++) + { + RequestedAnodicAmplitudeuA[i] = Sequence.Stimuli[i].AnodicAmplitudeSteps * Sequence.CurrentStepSizeuA; + RequestedCathodicAmplitudeuA[i] = Sequence.Stimuli[i].CathodicAmplitudeSteps * Sequence.CurrentStepSizeuA; + } + StepSize = Sequence.CurrentStepSize; ChannelDialog = new(probeGroup) @@ -229,11 +243,11 @@ private void HighlightInvalidContacts() ChannelDialog.RefreshZedGraph(); } - private static double GetPeakToPeakAmplitudeInMicroAmps(Rhs2116StimulusSequencePair stimulusSequence) + private double GetPeakToPeakAmplitudeInMicroAmps() { - return stimulusSequence.MaximumPeakToPeakAmplitudeSteps > 0 - ? stimulusSequence.CurrentStepSizeuA * stimulusSequence.MaximumPeakToPeakAmplitudeSteps - : stimulusSequence.CurrentStepSizeuA * 1; + return Sequence.MaximumPeakToPeakAmplitudeSteps > 0 + ? Sequence.GetMaxPeakToPeakAmplitudeuA() + : Sequence.CurrentStepSizeuA * 1; // NB: Used to give a buffer when plotting the stimulus waveform } private void DrawStimulusWaveform() @@ -244,24 +258,22 @@ private void DrawStimulusWaveform() zedGraphWaveform.GraphPane.GraphObjList.Clear(); zedGraphWaveform.ZoomOutAll(zedGraphWaveform.GraphPane); - double peakToPeak = GetPeakToPeakAmplitudeInMicroAmps(Sequence) * 1.1; + double peakToPeak = GetPeakToPeakAmplitudeInMicroAmps() * 1.1; ZoomInBoundaryY = 3; - var stimuli = Sequence.Stimuli; - double maxLength = 0; - for (int i = 0; i < stimuli.Length; i++) + for (int i = 0; i < Sequence.Stimuli.Length; i++) { var channelOffset = peakToPeak * i; if (ChannelDialog.SelectedContacts[i] || plotAllContacts) { - PointPairList pointPairs = CreateStimulusWaveform(stimuli[i], channelOffset, peakToPeak); + PointPairList pointPairs = CreateStimulusWaveform(Sequence.Stimuli[i], channelOffset, peakToPeak); Color color; - if (stimuli[i].IsValid()) + if (Sequence.Stimuli[i].IsValid()) { color = Color.CornflowerBlue; } @@ -290,7 +302,7 @@ private void DrawStimulusWaveform() zedGraphWaveform.GraphPane.XAxis.Scale.Max = maxLength; zedGraphWaveform.GraphPane.XAxis.Scale.Min = -(maxLength * 0.02); zedGraphWaveform.GraphPane.YAxis.Scale.Min = -2; - zedGraphWaveform.GraphPane.YAxis.Scale.Max = stimuli.Length - 0.2; + zedGraphWaveform.GraphPane.YAxis.Scale.Max = Sequence.Stimuli.Length - 0.2; DrawScale(); @@ -340,7 +352,7 @@ private void DrawScale() timeScale.ZOrder = ZOrder.A_InFront; zedGraphWaveform.GraphPane.GraphObjList.Add(timeScale); - TextObj amplitudeScale = new((GetPeakToPeakAmplitudeInMicroAmps(Sequence) / 2).ToString() + " µA", zeroOffsetX, zeroOffsetY + y * 1.02, CoordType.AxisXYScale, AlignH.Center, AlignV.Bottom); + TextObj amplitudeScale = new((GetPeakToPeakAmplitudeInMicroAmps() / 2).ToString() + " µA", zeroOffsetX, zeroOffsetY + y * 1.02, CoordType.AxisXYScale, AlignH.Center, AlignV.Bottom); amplitudeScale.FontSpec.Border.IsVisible = false; amplitudeScale.FontSpec.Fill.IsVisible = false; amplitudeScale.ZOrder = ZOrder.A_InFront; @@ -604,7 +616,7 @@ private void SetStatusValidity() return (reason, ind); }) .Where(reason => reason.reason != "") - .First(); + .FirstOrDefault(); toolStripStatusIsValid.Image = Properties.Resources.StatusCriticalImage; toolStripStatusIsValid.Text = string.Format("Invalid sequence - Contact {0}, Reason: {1}", reason.ind, reason.reason); @@ -619,68 +631,96 @@ private void SetPercentOfSlotsUsed() private void ButtonAddPulses_Click(object sender, EventArgs e) { - if (ChannelDialog.SelectedContacts.All(x => x)) - { - DialogResult result = MessageBox.Show("Caution: All channels are currently selected, and all " + - "settings will be applied to all channels if you continue. Press Okay to add pulse settings to all channels, or Cancel to keep them as is", - "Set all channel settings?", MessageBoxButtons.OKCancel); - - if (result == DialogResult.Cancel) - { - return; - } - } - if (ChannelDialog.SelectedContacts.All(x => x == false)) { MessageBox.Show("No contacts selected. Please select contact(s) before trying to add pulses."); return; } - if (StepSize != Sequence.CurrentStepSize) + var stimuli = Sequence.Stimuli + .Select((s, ind) => { return (Index: ind, Stimulus: s); }) + .Where(s => s.Stimulus.Valid + && (s.Stimulus.AnodicAmplitudeSteps != 0 + || s.Stimulus.CathodicAmplitudeSteps != 0 + || (s.Stimulus.AnodicAmplitudeSteps == 0 && RequestedAnodicAmplitudeuA[s.Index] != 0.0) + || (s.Stimulus.CathodicAmplitudeSteps == 0 && RequestedCathodicAmplitudeuA[s.Index] != 0.0)) + && !ChannelDialog.SelectedContacts[s.Index]) + .Select(s => + { + GetSampleFromAmplitude(RequestedAnodicAmplitudeuA[s.Index], out var requestedAnodicSteps); + var requestedAnodicError = s.Stimulus.AnodicAmplitudeSteps == 0 + ? GetAmplitudeFromSample(requestedAnodicSteps, StepSize) + : CalculateAmplitudePercentError(RequestedAnodicAmplitudeuA[s.Index], StepSize); + + GetSampleFromAmplitude(RequestedCathodicAmplitudeuA[s.Index], out var requestedCathodicSteps); + var requestedCathodicError = s.Stimulus.CathodicAmplitudeSteps == 0 + ? GetAmplitudeFromSample(requestedCathodicSteps, StepSize) + : CalculateAmplitudePercentError(RequestedCathodicAmplitudeuA[s.Index], StepSize); + + return (s.Index, + ErrorAnodic: requestedAnodicError, + ErrorCathodic: requestedCathodicError, + StepsAnodic: requestedAnodicSteps, + StepsCathodic: requestedCathodicSteps); + }); + + if (Sequence.CurrentStepSize != StepSize && stimuli.Any(e => e.ErrorCathodic != 0 || e.ErrorAnodic != 0 && + ((Sequence.Stimuli[e.Index].AnodicAmplitudeSteps == 0 && e.StepsAnodic != 0) || + (Sequence.Stimuli[e.Index].CathodicAmplitudeSteps == 0 && e.StepsCathodic != 0)))) { - var stimuli = Sequence.Stimuli - .Select((s, ind) => { return (Index: ind, Stimulus: s); }) - .Where(s => s.Stimulus.IsValid() && (s.Stimulus.AnodicAmplitudeSteps != 0 || s.Stimulus.CathodicAmplitudeSteps != 0) && !ChannelDialog.SelectedContacts[s.Index]) - .Select(s => - { - var currentAnodicAmplitude = GetAmplitudeFromSample(s.Stimulus.AnodicAmplitudeSteps, Sequence.CurrentStepSize); - var currentCathodicAmplitude = GetAmplitudeFromSample(s.Stimulus.CathodicAmplitudeSteps, Sequence.CurrentStepSize); - - var validAnodicAmplitude = GetSampleFromAmplitude(currentAnodicAmplitude, out var newAnodicSteps); - var validCathodicAmplitude = GetSampleFromAmplitude(currentAnodicAmplitude, out var newCathodicSteps); - - return (ValidAmplitudes: validAnodicAmplitude && newAnodicSteps != 0 && validCathodicAmplitude && newCathodicSteps != 0, - s.Index, - s.Stimulus, - NewAnodicSteps: newAnodicSteps, - NewCathodicSteps: newCathodicSteps); - }); - - foreach (var (ValidAmplitudes, Index, Stimulus, NewAnodicSteps, NewCathodicSteps) in stimuli) + var message = $"The step size is changing from {GetStepSizeStringuA(Sequence.CurrentStepSize)} to {GetStepSizeStringuA(StepSize)}, " + + $"which will adjust some amplitudes. If applied, the following values will be modified:\n"; + + foreach (var (Index, ErrorAnodic, ErrorCathodic, StepsAnodic, StepsCathodic) in stimuli) { - if (ValidAmplitudes) + if (ErrorAnodic != 0 || ErrorCathodic != 0 && ((Sequence.Stimuli[Index].AnodicAmplitudeSteps == 0 && StepsAnodic != 0) || (Sequence.Stimuli[Index].CathodicAmplitudeSteps == 0 && StepsCathodic != 0))) { - Sequence.Stimuli[Index].AnodicAmplitudeSteps = NewAnodicSteps; - Sequence.Stimuli[Index].CathodicAmplitudeSteps = NewCathodicSteps; + var oldAnodicAmplitude = GetAmplitudeFromSample(Sequence.Stimuli[Index].AnodicAmplitudeSteps, Sequence.CurrentStepSize); + var newAnodicAmplitude = GetAmplitudeFromSample(StepsAnodic, StepSize); + + var oldCathodicAmplitude = GetAmplitudeFromSample(Sequence.Stimuli[Index].CathodicAmplitudeSteps, Sequence.CurrentStepSize); + var newCathodicAmplitude = GetAmplitudeFromSample(StepsCathodic, StepSize); + + if (oldAnodicAmplitude == newAnodicAmplitude && oldCathodicAmplitude == newCathodicAmplitude) continue; + + message += $"\nChannel {Index}: Anode = {oldAnodicAmplitude} µA → {newAnodicAmplitude} µA," + + $" Cathode = {oldCathodicAmplitude} µA → {newCathodicAmplitude} µA"; } - else - { - var result = MessageBox.Show($"The new amplitude ({GetAmplitudeString((byte)textboxAmplitudeAnodic.Tag) + " µA"}) is using a step size of {GetStepSizeStringuA(StepSize)}," + - $" but channel {Index} ({GetAmplitudeString(Stimulus.AnodicAmplitudeSteps, Sequence.CurrentStepSize) + " µA"}) cannot be defined with this step size. " + - $"Press Ok to clear channel {Index}, or Cancel to stop adding this sequence.", - "Amplitude Out of Range", MessageBoxButtons.OKCancel); + } - if (result == DialogResult.Cancel) - { - return; - } + message += "\n\nClick Update to update these channels, or Cancel to stop."; + CustomMessageBox messageBox = new(message, "New Amplitude Values", "Update", "Cancel"); + var result = messageBox.ShowDialog(); + + if (result == DialogResult.Cancel) return; + } + + foreach (var (Index, ErrorAnodic, ErrorCathodic, StepsAnodic, StepsCathodic) in stimuli) + { + if (StepsAnodic == 0 && StepsCathodic == 0) + { + if (Sequence.Stimuli[Index].AnodicAmplitudeSteps != 0 && Sequence.Stimuli[Index].CathodicAmplitudeSteps != 0) + { + SequenceCopy.UpdateStimulus(Sequence.Stimuli[Index], Index); // NB: Store the current pulse pattern before clearing Sequence.Stimuli[Index].Clear(); } } + else + { + if (Sequence.Stimuli[Index].NumberOfStimuli == 0 && SequenceCopy.Stimuli[Index].IsValid() && SequenceCopy.Stimuli[Index].NumberOfStimuli != 0) + { + Sequence.UpdateStimulus(SequenceCopy.Stimuli[Index], Index); // NB: Restore pulse timings before adding amplitude steps + } + else if (Sequence.Stimuli[Index].NumberOfStimuli == 0 && SequenceCopy.Stimuli[Index].NumberOfStimuli != 0) continue; + + Sequence.Stimuli[Index].AnodicAmplitudeSteps = StepsAnodic; + Sequence.Stimuli[Index].CathodicAmplitudeSteps = StepsCathodic; + } } + dataGridViewStimulusTable.DataSource = Sequence.Stimuli; // NB: Force an update in case pulse timings were restored + for (int i = 0; i < ChannelDialog.SelectedContacts.Length; i++) { if (ChannelDialog.SelectedContacts[i]) @@ -690,6 +730,11 @@ private void ButtonAddPulses_Click(object sender, EventArgs e) Sequence.Stimuli[i].DelaySamples = (uint)textboxDelay.Tag; } + if (textboxAmplitudeAnodicRequested.Tag != null) + { + RequestedAnodicAmplitudeuA[i] = (double)textboxAmplitudeAnodicRequested.Tag; + } + if (textboxAmplitudeAnodic.Tag != null) { Sequence.Stimuli[i].AnodicAmplitudeSteps = (byte)textboxAmplitudeAnodic.Tag; @@ -705,6 +750,11 @@ private void ButtonAddPulses_Click(object sender, EventArgs e) Sequence.Stimuli[i].DwellSamples = (uint)textboxInterPulseInterval.Tag; } + if (textboxAmplitudeCathodicRequested.Tag != null) + { + RequestedCathodicAmplitudeuA[i] = (double)textboxAmplitudeCathodicRequested.Tag; + } + if (textboxAmplitudeCathodic.Tag != null) { Sequence.Stimuli[i].CathodicAmplitudeSteps = (byte)textboxAmplitudeCathodic.Tag; @@ -882,16 +932,31 @@ private bool GetSampleFromTime(double value, out uint samples) /// Get the number of samples needed at the current step size to represent a given amplitude. /// /// Double value defining the amplitude in microamps. + /// /// Output returning the number of samples as a byte. /// Returns true if the number of samples is a valid byte value (between 0 and 255). Returns false if the number of samples cannot be represented in byte format. - private bool GetSampleFromAmplitude(double value, out byte samples) + private bool GetSampleFromAmplitude(double value, Rhs2116StepSize stepSize, out byte samples) { - var ratio = value / Rhs2116StimulusSequence.GetStepSizeuA(StepSize); - samples = (byte)Math.Round(ratio); + var ratio = GetRatio(value, Rhs2116StimulusSequence.GetStepSizeuA(stepSize)); + + if (ratio >= 255) samples = 255; + else if (ratio <= 0) samples = 0; + else samples = (byte)Math.Round(ratio); return !(ratio > byte.MaxValue || ratio < 0); } + private double GetRatio(double value1, double value2) + { + return value1 / value2; + } + + /// + private bool GetSampleFromAmplitude(double value, out byte samples) + { + return GetSampleFromAmplitude(value, StepSize, out samples); + } + private double GetTimeFromSample(uint value) { return value * SamplePeriodMilliSeconds; @@ -902,28 +967,52 @@ private double GetAmplitudeFromSample(byte value, Rhs2116StepSize stepSize) return value * Rhs2116StimulusSequence.GetStepSizeuA(stepSize); } + private void UpdateAmplitudeTextBoxes(TextBox textBox, string text = "", byte? tag = null) + { + if (checkboxBiphasicSymmetrical.Checked) + { + textboxAmplitudeCathodic.Text = text; + textboxAmplitudeCathodic.Tag = tag.HasValue ? tag.Value : null; + + textboxAmplitudeAnodic.Text = text; + textboxAmplitudeAnodic.Tag = tag.HasValue ? tag.Value : null; + + if (textBox.Name == nameof(textboxAmplitudeAnodicRequested)) + { + textboxAmplitudeCathodicRequested.Text = textboxAmplitudeAnodicRequested.Text; + textboxAmplitudeCathodicRequested.Tag = textboxAmplitudeAnodicRequested.Tag; + } + else if (textBox.Name == nameof(textboxAmplitudeCathodicRequested)) + { + textboxAmplitudeAnodicRequested.Text = textboxAmplitudeCathodicRequested.Text; + textboxAmplitudeAnodicRequested.Tag = textboxAmplitudeCathodicRequested.Tag; + } + } + else + { + if (textBox.Name == nameof(textboxAmplitudeAnodicRequested)) + { + textboxAmplitudeAnodic.Text = text; + textboxAmplitudeAnodic.Tag = tag.HasValue ? tag.Value : null; + } + else if (textBox.Name == nameof(textboxAmplitudeCathodicRequested)) + { + textboxAmplitudeCathodic.Text = text; + textboxAmplitudeCathodic.Tag = tag.HasValue ? tag.Value : null; + } + } + + } + private void Amplitude_TextChanged(object sender, EventArgs e) { TextBox textBox = (TextBox)sender; if (textBox.Text == "") { + UpdateAmplitudeTextBoxes(textBox); textBox.Tag = null; - if (checkboxBiphasicSymmetrical.Checked) - { - if (textBox.Name == nameof(textboxAmplitudeAnodic)) - { - textboxAmplitudeCathodic.Text = ""; - textboxAmplitudeCathodic.Tag = null; - } - else if (textBox.Name == nameof(textboxAmplitudeCathodic)) - { - textboxAmplitudeAnodic.Text = ""; - textboxAmplitudeAnodic.Tag = null; - } - } - return; } @@ -931,36 +1020,26 @@ private void Amplitude_TextChanged(object sender, EventArgs e) { if (!UpdateStepSizeFromAmplitude(result)) { - textBox.Text = result > MaxAmplitudeuA ? MaxAmplitudeuA.ToString() : "0"; - textBox.Tag = result > MaxAmplitudeuA ? 255 : 0; + string text = result > MaxAmplitudeuA ? MaxAmplitudeuA.ToString() : "0"; + byte tag = result > MaxAmplitudeuA ? (byte)255 : (byte)0; + + UpdateAmplitudeTextBoxes(textBox, text, tag); + return; } + textBox.Tag = result; + GetSampleFromAmplitude(result, out byte sampleAmplitude); - textBox.Text = GetAmplitudeString(sampleAmplitude); - textBox.Tag = sampleAmplitude; + UpdateAmplitudeTextBoxes(textBox, GetAmplitudeString(sampleAmplitude), sampleAmplitude); } else { - MessageBox.Show("Unable to parse text. Please enter a valid value in milliamps"); + MessageBox.Show("Unable to parse text. Please enter a valid value in milliamps.", "Invalid Requested Amplitude"); textBox.Text = ""; textBox.Tag = null; } - - if (checkboxBiphasicSymmetrical.Checked) - { - if (textBox.Name == nameof(textboxAmplitudeAnodic)) - { - textboxAmplitudeCathodic.Text = textBox.Text; - textboxAmplitudeCathodic.Tag = textBox.Tag; - } - else if (textBox.Name == nameof(textboxAmplitudeCathodic)) - { - textboxAmplitudeAnodic.Text = textBox.Text; - textboxAmplitudeAnodic.Tag = textBox.Tag; - } - } } /// @@ -988,56 +1067,18 @@ private bool UpdateStepSizeFromAmplitude(double amplitude) return false; } - // NB: Update step size to a value that supports the requested amplitude. - var possibleStepSizes = Enum.GetValues(typeof(Rhs2116StepSize)) - .Cast() - .Where(s => - { - return IsValidNumberOfSteps(GetNumberOfSteps(amplitude, s)); - }); + var stepSizes = Enum.GetValues(typeof(Rhs2116StepSize)).Cast(); + var validStepSizes = stepSizes.Where(stepSize => IsValidNumberOfSteps(GetNumberOfSteps(amplitude, stepSize))); - if (possibleStepSizes.Count() == 1) + if (validStepSizes.Count() == 1) { - StepSize = possibleStepSizes.First(); - } - else - { - if (possibleStepSizes.Contains(Sequence.CurrentStepSize)) - { - StepSize = Sequence.CurrentStepSize; - } - else - { - // NB: Search through the possible step sizes and try to find one that matches all current amplitudes - var validStepSizes = possibleStepSizes.Where(s => - { - var numberOfStimuli = Sequence.Stimuli.Length; - - bool[] isValid = new bool[numberOfStimuli]; - - for (int i = 0; i < numberOfStimuli; i++) - { - isValid[i] = IsValidNumberOfSteps(GetNumberOfSteps(GetAmplitudeFromSample(Sequence.Stimuli[i].AnodicAmplitudeSteps, Sequence.CurrentStepSize), s)); - } - - return isValid.All(i => i); - }); - - if (!validStepSizes.Any()) - { - MessageBox.Show("No step size found that fits all existing and new amplitudes. " + - "Either clear existing stimuli that fall outside the range of the new step size, or modify " + - "the new amplitude.", "Invalid Amplitude"); - - StepSize = possibleStepSizes.First(); - } - else - { - StepSize = validStepSizes.First(); - } - } + StepSize = validStepSizes.First(); + textBoxStepSize.Text = GetStepSizeStringuA(StepSize); + + return true; } + StepSize = Rhs2116StimulusSequence.GetStepSizeWithMinError(validStepSizes, Sequence.Stimuli, Sequence.CurrentStepSize); textBoxStepSize.Text = GetStepSizeStringuA(StepSize); return true; @@ -1053,6 +1094,17 @@ private int GetNumberOfSteps(double amplitude, Rhs2116StepSize stepSize) return (int)(amplitude / Rhs2116StimulusSequence.GetStepSizeuA(stepSize)); } + private double CalculateAmplitudePercentError(double amplitude, Rhs2116StepSize stepSize) + { + if (amplitude == 0) return 0; + + var stepSizeuA = Rhs2116StimulusSequence.GetStepSizeuA(stepSize); + + GetSampleFromAmplitude(amplitude, stepSize, out var steps); + + return 100 * ((amplitude - steps * stepSizeuA) / amplitude); + } + private void Checkbox_CheckedChanged(object sender, EventArgs e) { if (checkboxBiphasicSymmetrical.Checked) @@ -1067,6 +1119,9 @@ private void Checkbox_CheckedChanged(object sender, EventArgs e) textboxAmplitudeCathodic.Text = textboxAmplitudeAnodic.Text; textboxAmplitudeCathodic.Tag = textboxAmplitudeAnodic.Tag; + + textboxAmplitudeCathodicRequested.Text = textboxAmplitudeAnodicRequested.Text; + textboxAmplitudeCathodicRequested.Tag = textboxAmplitudeAnodicRequested.Tag; } else { @@ -1078,6 +1133,9 @@ private void Checkbox_CheckedChanged(object sender, EventArgs e) textboxAmplitudeAnodic.Text = textboxAmplitudeCathodic.Text; textboxAmplitudeAnodic.Tag = textboxAmplitudeCathodic.Tag; + + textboxAmplitudeAnodicRequested.Text = textboxAmplitudeCathodicRequested.Text; + textboxAmplitudeAnodicRequested.Tag = textboxAmplitudeCathodicRequested.Tag; } } else @@ -1089,25 +1147,13 @@ private void Checkbox_CheckedChanged(object sender, EventArgs e) private void ButtonClearPulses_Click(object sender, EventArgs e) { - if (ChannelDialog.SelectedContacts.All(x => x == false) || ChannelDialog.SelectedContacts.All(x => x == true)) - { - DialogResult result = MessageBox.Show("Caution: All channels are currently selected, and all " + - "settings will be cleared if you continue. Press Okay to clear all pulse settings, or Cancel to keep them", - "Remove all channel settings?", MessageBoxButtons.OKCancel); - - if (result == DialogResult.Cancel) - { - return; - } - } - - var clearAllContacts = ChannelDialog.SelectedContacts.All(x => x == false); - for (int i = 0; i < ChannelDialog.SelectedContacts.Length; i++) { - if (ChannelDialog.SelectedContacts[i] || clearAllContacts) + if (ChannelDialog.SelectedContacts[i]) { Sequence.Stimuli[i].Clear(); + RequestedAnodicAmplitudeuA[i] = 0.0; + RequestedCathodicAmplitudeuA[i] = 0.0; } } @@ -1153,12 +1199,34 @@ private void ButtonReadPulses_Click(object sender, EventArgs e) textboxAmplitudeAnodic.Text = GetAmplitudeString(Sequence.Stimuli[index].AnodicAmplitudeSteps); textboxAmplitudeAnodic.Tag = Sequence.Stimuli[index].AnodicAmplitudeSteps; + if (RequestedAnodicAmplitudeuA[index] != 0.0) + { + textboxAmplitudeAnodicRequested.Text = RequestedAnodicAmplitudeuA[index].ToString(); + textboxAmplitudeAnodicRequested.Tag = RequestedAnodicAmplitudeuA[index]; + } + else + { + textboxAmplitudeAnodicRequested.Text = ""; + textboxAmplitudeAnodicRequested.Tag = null; + } + textboxPulseWidthAnodic.Text = GetTimeString(Sequence.Stimuli[index].AnodicWidthSamples); textboxPulseWidthAnodic.Tag = Sequence.Stimuli[index].AnodicWidthSamples; textboxAmplitudeCathodic.Text = GetAmplitudeString(Sequence.Stimuli[index].CathodicAmplitudeSteps); textboxAmplitudeCathodic.Tag = Sequence.Stimuli[index].CathodicAmplitudeSteps; + if (RequestedCathodicAmplitudeuA[index] != 0.0) + { + textboxAmplitudeCathodicRequested.Text = RequestedCathodicAmplitudeuA[index].ToString(); + textboxAmplitudeCathodicRequested.Tag = RequestedCathodicAmplitudeuA[index]; + } + else + { + textboxAmplitudeCathodicRequested.Text = ""; + textboxAmplitudeCathodicRequested.Tag = null; + } + textboxPulseWidthCathodic.Text = GetTimeString(Sequence.Stimuli[index].CathodicWidthSamples); textboxPulseWidthCathodic.Tag = Sequence.Stimuli[index].CathodicWidthSamples; @@ -1175,7 +1243,7 @@ private void MenuItemSaveFile_Click(object sender, EventArgs e) { if (!Sequence.Valid) { - var result = MessageBox.Show("Warning: Not all stimuli are valid; are you sure you want to save this file?", + var result = MessageBox.Show("Warning: Not all stimuli are valid; are you sure you want to save this file?", "Invalid Stimuli", MessageBoxButtons.YesNo, MessageBoxIcon.Error); if (result == DialogResult.No) return; diff --git a/OpenEphys.Onix1/Rhs2116Stimulus.cs b/OpenEphys.Onix1/Rhs2116Stimulus.cs index d57522f..b5cbcda 100644 --- a/OpenEphys.Onix1/Rhs2116Stimulus.cs +++ b/OpenEphys.Onix1/Rhs2116Stimulus.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Xml.Serialization; +using Bonsai; namespace OpenEphys.Onix1 { @@ -8,6 +9,28 @@ namespace OpenEphys.Onix1 /// public class Rhs2116Stimulus { + /// + /// Initializes a new instance of the class. + /// + public Rhs2116Stimulus() { } + + /// + /// Construct a new instance with the same parameters as the given object. + /// + /// + public Rhs2116Stimulus(Rhs2116Stimulus stimulus) + { + NumberOfStimuli = stimulus.NumberOfStimuli; + AnodicFirst = stimulus.AnodicFirst; + DelaySamples = stimulus.DelaySamples; + DwellSamples = stimulus.DwellSamples; + AnodicAmplitudeSteps = stimulus.AnodicAmplitudeSteps; + AnodicWidthSamples = stimulus.AnodicWidthSamples; + CathodicAmplitudeSteps = stimulus.CathodicAmplitudeSteps; + CathodicWidthSamples = stimulus.CathodicWidthSamples; + InterStimulusIntervalSamples = stimulus.InterStimulusIntervalSamples; + } + /// /// Number of stimuli delivered for each trigger. /// diff --git a/OpenEphys.Onix1/Rhs2116StimulusSequence.cs b/OpenEphys.Onix1/Rhs2116StimulusSequence.cs index 6771905..1258dc0 100644 --- a/OpenEphys.Onix1/Rhs2116StimulusSequence.cs +++ b/OpenEphys.Onix1/Rhs2116StimulusSequence.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Xml.Serialization; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("OpenEphys.Onix1.Design")] namespace OpenEphys.Onix1 { @@ -11,7 +14,7 @@ namespace OpenEphys.Onix1 /// public class Rhs2116StimulusSequence { - const int NumberOfChannelsPerDevice = 16; + internal const int NumberOfChannels = 16; /// /// Initializes a new instance of the class with 16 default @@ -19,7 +22,7 @@ public class Rhs2116StimulusSequence /// public Rhs2116StimulusSequence() { - Stimuli = new Rhs2116Stimulus[NumberOfChannelsPerDevice]; + Stimuli = new Rhs2116Stimulus[NumberOfChannels]; for (var i = 0; i < Stimuli.Length; i++) { @@ -147,6 +150,15 @@ public static double GetStepSizeuA(Rhs2116StepSize stepSize) }; } + /// + /// Gets the maximum peak-to-peak amplitude across all stimuli. + /// + /// Double containing the maximum peak-to-peak amplitude in µA. + public double GetMaxPeakToPeakAmplitudeuA() + { + return CurrentStepSizeuA * MaximumPeakToPeakAmplitudeSteps; + } + /// /// Gets the maximum possible amplitude for a single pulse, in µA. /// @@ -172,7 +184,7 @@ private Dictionary GetDeltaTable() { var table = new Dictionary(); - for (int i = 0; i < NumberOfChannelsPerDevice; i++) + for (int i = 0; i < NumberOfChannels; i++) { var s = Stimuli[i]; @@ -217,5 +229,51 @@ private static void AddOrInsert(ref Dictionary table, int channe table[key][channel + 16] = polarity; } } + + internal static Rhs2116StepSize GetStepSizeWithMinError(IEnumerable stepSizes, Rhs2116Stimulus[] stimuli, Rhs2116StepSize currentStepSize) + { + var numberOfStepSizes = stepSizes.Count(); + var maxError = new List(numberOfStepSizes); + var stepSizesuA = stepSizes.Select(s => GetStepSizeuA(s)).ToArray(); + var currentStepSizeuA = GetStepSizeuA(currentStepSize); + + static double CalculateError(double amplitude, double stepSizeuA) + { + return Math.Abs((amplitude - (stepSizeuA * Math.Round(amplitude / stepSizeuA))) / amplitude); + } + + for (int s = 0; s < numberOfStepSizes; s++) + { + maxError.Add(0); + + for (int c = 0; c < stimuli.Length; c += 1) + { + if (stimuli[c].AnodicAmplitudeSteps == 0 && stimuli[c].CathodicAmplitudeSteps == 0) continue; + + var anodicAmp = stimuli[c].AnodicAmplitudeSteps * currentStepSizeuA; + var cathodicAmp = stimuli[c].CathodicAmplitudeSteps * currentStepSizeuA; + + var anodicError = anodicAmp < stepSizesuA[s] || anodicAmp > stepSizesuA[s] * 255 ? + double.PositiveInfinity : + CalculateError(anodicAmp, stepSizesuA[s]); + + var cathodicError = cathodicAmp < stepSizesuA[s] || cathodicAmp > stepSizesuA[s] * 255 ? + double.PositiveInfinity : + CalculateError(cathodicAmp, stepSizesuA[s]); + + maxError[s] = Math.Max(maxError[s], Math.Max(anodicError, cathodicError)); + } + } + + if (maxError.Distinct().Count() == 1) + { + // NB: All step sizes have the same error; compare to current step size and choose the closest step size + return stepSizes.OrderBy(s => Math.Abs(GetStepSizeuA(s) - currentStepSizeuA)).First(); + } + + var optimalStepIndex = maxError.IndexOf(maxError.Min()); + + return stepSizes.ElementAt(optimalStepIndex); + } } } diff --git a/OpenEphys.Onix1/Rhs2116StimulusSequencePair.cs b/OpenEphys.Onix1/Rhs2116StimulusSequencePair.cs index 20f3a68..05bbb0f 100644 --- a/OpenEphys.Onix1/Rhs2116StimulusSequencePair.cs +++ b/OpenEphys.Onix1/Rhs2116StimulusSequencePair.cs @@ -1,6 +1,9 @@ using System; using System.Linq; using System.Xml.Serialization; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("OpenEphys.Onix1.Design")] namespace OpenEphys.Onix1 { @@ -9,8 +12,8 @@ namespace OpenEphys.Onix1 /// public class Rhs2116StimulusSequencePair { - internal readonly Rhs2116StimulusSequence StimulusSequenceA; - internal readonly Rhs2116StimulusSequence StimulusSequenceB; + internal Rhs2116StimulusSequence StimulusSequenceA { get; } + internal Rhs2116StimulusSequence StimulusSequenceB { get; } /// /// Initializes a new instance of the class with 16 default @@ -33,6 +36,18 @@ public Rhs2116StimulusSequencePair(Rhs2116StimulusSequencePair stimulusSequenceD StimulusSequenceB = new Rhs2116StimulusSequence(stimulusSequenceDual.StimulusSequenceB); } + /// + /// Initializes a new instance of the by performing a + /// shallow copy of the reference sequences. + /// + /// Existing for sequence A. + /// Existing for sequence B. + public Rhs2116StimulusSequencePair(Rhs2116StimulusSequence stimulusSequenceA, Rhs2116StimulusSequence stimulusSequenceB) + { + StimulusSequenceA = new Rhs2116StimulusSequence(stimulusSequenceA); + StimulusSequenceB = new Rhs2116StimulusSequence(stimulusSequenceB); + } + /// /// Gets or sets the array of stimuli. /// @@ -46,6 +61,34 @@ public Rhs2116Stimulus[] Stimuli } } + /// + /// Updates the stimulus at the given index. + /// + /// + /// This is necessary to change the values of individual stimuli, since the implementation for getting does not + /// allow for the underlying to be updated. + /// + /// Current to copy. + /// Zero-indexed value of the channel to update. + /// Index must be between 0 and the sum of the number of elements in + /// and . + internal void UpdateStimulus(Rhs2116Stimulus stimulus, int index) + { + if (index >= Stimuli.Length || index < 0) + { + throw new IndexOutOfRangeException("Index is outside of the range of stimuli. Must be less than " + Stimuli.Length.ToString() + ", and greater than zero."); + } + + if (index < StimulusSequenceA.Stimuli.Length) + { + StimulusSequenceA.Stimuli[index] = stimulus.Clone(); + } + else + { + StimulusSequenceB.Stimuli[index - StimulusSequenceA.Stimuli.Length] = stimulus.Clone(); + } + } + /// /// Gets or sets the . /// @@ -99,5 +142,14 @@ public Rhs2116StepSize CurrentStepSize /// [XmlIgnore] public double CurrentStepSizeuA => StimulusSequenceA.CurrentStepSizeuA; + + /// + /// Gets the maximum peak-to-peak amplitude across all stimuli. + /// + /// Double containing the maximum peak-to-peak amplitude in µA. + public double GetMaxPeakToPeakAmplitudeuA() + { + return Math.Max(StimulusSequenceA.GetMaxPeakToPeakAmplitudeuA(), StimulusSequenceB.GetMaxPeakToPeakAmplitudeuA()); + } } }