Skip to content

Commit

Permalink
Merge pull request #1 from CWRUChielLab/increase-stim-param-limits
Browse files Browse the repository at this point in the history
Increase limits for phase durations in stimulation dialog window
  • Loading branch information
jpgill86 authored Jan 23, 2021
2 parents 46f68ca + c05f48f commit ad97d67
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 21 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Change Log

* Increase limits for phase durations in stimulation dialog window ([#1][pr-1])
* First and second phase durations were capped at 5 ms each. These were
increased to allow for the longest possible pulses permitted by the
hardware (~2-3 seconds, depending on sampling period).

[pr-1]: https://github.com/CWRUChielLab/IntanStimRecordController/pull/1
16 changes: 10 additions & 6 deletions README.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Intan Stimulation/Recording Controller Software - Source Code (v1.07)
---------------------------------------------------------------------
Intan Stimulation/Recording Controller Software - Source Code (v1.07 - modified)
--------------------------------------------------------------------------------

NOTE: This software has been modified from the version provided by the
manufacturer. See CHANGELOG.md for details. Notes for building can be found
here: https://github.com/CWRUChielLab/IntanStimRecordController/wiki

This directory contains the Intan Stimulation/Recording Controller C++/Qt source code and two supporting files:
main.bit (the FPGA configuration file) and an operating system-specific Opal Kelly library file located
Expand All @@ -16,7 +20,7 @@ connecting the Intan Recording Controller to a computer. (They are not included
However, you can run the Recording Controller software in demonstration mode without installing drivers
or plugging in an interface board.

The C++ source code uses a handful of C++11 features, so g++ users may need to add -std=c++11 to their
The C++ source code uses a handful of C++11 features, so g++ users may need to add -std=c++11 to their
command line or make file.

This code was tested with Qt 4.8.1 and with Qt 5.7.0.
Expand All @@ -25,7 +29,7 @@ For production code, you should compile a Release build (Visual Studio) or use t
-O3 (g++). Otherwise, the compiled code may not be fast enough to keep up with how fast data streams
from the USB interface board. This will show up as the FIFO lag becoming very high for debug builds.

(Thanks to Josh Siegle at MIT and Open-Ephys.org for tips on Mac and Linux compilation.)
(Thanks to Josh Siegle at MIT and Open-Ephys.org for tips on Mac and Linux compilation.)

Other Linux tips
----------------
Expand Down Expand Up @@ -56,7 +60,7 @@ code on a Mac. This description is based on exprience using a MacBook Pro runni

-------------------------------------------------------------------------

Some Windows users have reported that it is necessary to install Qt with 32-bit support for the
Some Windows users have reported that it is necessary to install Qt with 32-bit support for the
okFrontPanel DLL to load properly. But this was with an older version of the Recording Controller source
code that didn't support 64-bit, so it may not apply any more.

Expand Down Expand Up @@ -99,4 +103,4 @@ website, http://www.intantech.com:
* RHS2000 USB/FPGA Interface: RhythmStim
* RHS2000 Stim SPI Cable/Connector Specification
* RHS2000 Application Note: Data File Formats
* RHS2000 Application Note: Adding Stim SPI Cables to a Commutator
* RHS2000 Application Note: Adding Stim SPI Cables to a Commutator
21 changes: 21 additions & 0 deletions source/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4804,6 +4804,27 @@ void MainWindow::setStimSequenceParameters(Rhs2000EvalBoard *evalBoard, double t
eventChargeRecovOff = 0;
}

//warn if events have exceeded the max time
if (eventStartStim > NEVER ||
eventStimPhase2 > NEVER ||
eventStimPhase3 > NEVER ||
eventEndStim > NEVER ||
eventRepeatStim > NEVER ||
eventEnd > NEVER ||
eventAmpSettleOn > NEVER ||
eventAmpSettleOff > NEVER ||
eventAmpSettleOnRepeat > NEVER ||
eventAmpSettleOffRepeat > NEVER ||
eventChargeRecovOn > NEVER ||
eventChargeRecovOff > NEVER)
{
QMessageBox::critical(this, tr("Stimulation Pulse Duration Exceeds Hardware Limitation"),
tr("WARNING: The selected stimulation parameters define a pulse that is longer "
"than what the hardware can produce. If you do not reduce the pulse duration, "
"the hardware is likely to execute the wrong stimulation protocol!"),
QMessageBox::Ok);
}

evalBoard->programStimReg(stream, channel, Rhs2000EvalBoard::EventAmpSettleOn, eventAmpSettleOn);
evalBoard->programStimReg(stream, channel, Rhs2000EvalBoard::EventStartStim, eventStartStim);
evalBoard->programStimReg(stream, channel, Rhs2000EvalBoard::EventStimPhase2, eventStimPhase2);
Expand Down
98 changes: 84 additions & 14 deletions source/stimparamdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ StimParamDialog::StimParamDialog(StimParameters *parameter, QString nativeChanne
timestep = timestep_us;
currentstep = currentstep_uA;

//maximum duration of a single pulse is hardware limited to timestep * (2^16 - 1)
maxPulseDuration_us = timestep_us * 65535;

//create a new StimFigure
stimFigure = new StimFigure(parameters, this);

Expand All @@ -61,11 +64,11 @@ StimParamDialog::StimParamDialog(StimParameters *parameter, QString nativeChanne

firstPhaseDurationLabel = new QLabel(tr("First Phase Duration (D1): "));
firstPhaseDuration = new TimeSpinBox(timestep_us);
firstPhaseDuration->setRange(0, 5000);
firstPhaseDuration->setRange(0, maxPulseDuration_us);

secondPhaseDurationLabel = new QLabel(tr("Second Phase Duration (D2): "));
secondPhaseDuration = new TimeSpinBox(timestep_us);
secondPhaseDuration->setRange(0, 5000);
secondPhaseDuration->setRange(0, maxPulseDuration_us);

interphaseDelayLabel = new QLabel(tr("Interphase Delay (DP): "));
interphaseDelay = new TimeSpinBox(timestep_us);
Expand Down Expand Up @@ -151,6 +154,12 @@ StimParamDialog::StimParamDialog(StimParameters *parameter, QString nativeChanne
refractoryPeriod = new TimeSpinBox(timestep_us);
refractoryPeriod->setRange(0, 1000000);

//create Single Pulse Duration widgets
singlePulseDuration = new QGroupBox(tr("Single Pulse Duration"));
singlePulseDuration->setToolTip(tr("Max = 65535 * time step\nActual = delay + waveform duration + refractory period"));
maxSinglePulseDurationLabel = new QLabel(tr("Maximum single pulse duration: ") + QString::number(maxPulseDuration_us / 1000) + " ms");
actualSinglePulseDurationLabel = new QLabel(tr("Pulse Duration Placeholder"));

//create Amp Settle widgets
ampSettle = new QGroupBox(tr("Amp Settle"));

Expand Down Expand Up @@ -222,6 +231,13 @@ StimParamDialog::StimParamDialog(StimParameters *parameter, QString nativeChanne
connect(secondPhaseAmplitude, SIGNAL(editingFinished()), this, SLOT(roundCurrentInputs()));
connect(enableStim, SIGNAL(toggled(bool)), stimFigure, SLOT(highlightStimTrace(bool)));

connect(stimShape, SIGNAL(currentIndexChanged(int)), this, SLOT(updateActualSinglePulseDuration()));
connect(firstPhaseDuration, SIGNAL(valueChanged(double)), this, SLOT(updateActualSinglePulseDuration()));
connect(secondPhaseDuration, SIGNAL(valueChanged(double)), this, SLOT(updateActualSinglePulseDuration()));
connect(interphaseDelay, SIGNAL(valueChanged(double)), this, SLOT(updateActualSinglePulseDuration()));
connect(postTriggerDelay, SIGNAL(valueChanged(double)), this, SLOT(updateActualSinglePulseDuration()));
connect(refractoryPeriod, SIGNAL(valueChanged(double)), this, SLOT(updateActualSinglePulseDuration()));

//connect signals to stimFigure's non-highlight slots
connect(stimShape, SIGNAL(currentIndexChanged(int)), stimFigure, SLOT(updateStimShape(int)));
connect(stimPolarity, SIGNAL(currentIndexChanged(int)), stimFigure, SLOT(updateStimPolarity(int)));
Expand Down Expand Up @@ -421,6 +437,12 @@ StimParamDialog::StimParamDialog(StimParameters *parameter, QString nativeChanne
pulseTrainLayout->addLayout(refractoryPeriodRow);
pulseTrain->setLayout(pulseTrainLayout);

//single pulse duration widgets' layout
QVBoxLayout *singlePulseDurationLayout = new QVBoxLayout;
singlePulseDurationLayout->addWidget(maxSinglePulseDurationLabel);
singlePulseDurationLayout->addWidget(actualSinglePulseDurationLabel);
singlePulseDuration->setLayout(singlePulseDurationLayout);

//amp Settle widgets' layout
QHBoxLayout *preStimAmpSettleRow = new QHBoxLayout;
preStimAmpSettleRow->addWidget(preStimAmpSettleLabel);
Expand Down Expand Up @@ -469,6 +491,7 @@ StimParamDialog::StimParamDialog(StimParameters *parameter, QString nativeChanne
QVBoxLayout *firstColumn = new QVBoxLayout;
firstColumn->addWidget(trigger);
firstColumn->addWidget(pulseTrain);
firstColumn->addWidget(singlePulseDuration);
firstColumn->addStretch();

//second Column
Expand Down Expand Up @@ -571,11 +594,22 @@ void StimParamDialog::loadParameters(StimParameters *parameters)
calculatePulseTrainFrequency();
calculateCharge();

//calculate actual single pulse duration from other parameters and display the value in its label
updateActualSinglePulseDuration();

}

/* Public slot that saves the values from the dialog box widgets into the parameters object, and closes the window */
void StimParamDialog::accept()
{
if (calculateActualSinglePulseDuration() > maxPulseDuration_us) {
QMessageBox::warning(this, tr("Single pulse duration is too long"),
tr("The single pulse duration exceeds hardware limitations. "
"You must reduce the duration before you can save these settings."),
QMessageBox::Ok);
return;
}

//save the values of the parameters from the dialog box into the object
parameters->stimShape = (StimParameters::StimShapeValues) stimShape->currentIndex();
parameters->stimPolarity = (StimParameters::StimPolarityValues) stimPolarity->currentIndex();
Expand Down Expand Up @@ -743,6 +777,9 @@ void StimParamDialog::enableWidgets()
postStimChargeRecovOffLabel->setEnabled(enableStim->isChecked() && enableChargeRecovery->isChecked());
postStimChargeRecovOff->setEnabled(enableStim->isChecked() && enableChargeRecovery->isChecked());

/* Single Pulse Period */
singlePulseDuration->setEnabled(enableStim->isChecked());

/* Reset Text for First Phase Labels */
if (stimShape->currentIndex() == StimParameters::Biphasic || stimShape->currentIndex() == StimParameters::BiphasicWithInterphaseDelay)
{
Expand Down Expand Up @@ -867,32 +904,65 @@ void StimParamDialog::constrainRefractoryPeriod()
refractoryPeriod->setTrueMinimum(qMax(postStimAmpSettle->getTrueValue(), postStimChargeRecovOff->getTrueValue()));
}

/* Private slot that constrains pulseTrainPeriod's lowest possible value to the sum of the durations of active phases */
void StimParamDialog::constrainPulseTrainPeriod()

/* Private method for calculating the total duration of the waveform */
double StimParamDialog::calculateWaveformDuration()
{
double minimum;
double waveformDuration;
//if biphasic
if (stimShape->currentIndex() == StimParameters::Biphasic)
{
//minimum equals D1 + D2
minimum = firstPhaseDuration->getTrueValue() + secondPhaseDuration->getTrueValue();
//duration equals D1 + D2
waveformDuration = firstPhaseDuration->getTrueValue() + secondPhaseDuration->getTrueValue();
}

//if biphasic with interphase delay
else if (stimShape->currentIndex() == StimParameters::BiphasicWithInterphaseDelay)
{
//minimum equals D1 + D2 + D_int
minimum = firstPhaseDuration->getTrueValue() + secondPhaseDuration->getTrueValue() + interphaseDelay->getTrueValue();
//duration equals D1 + D2 + D_int
waveformDuration = firstPhaseDuration->getTrueValue() + secondPhaseDuration->getTrueValue() + interphaseDelay->getTrueValue();
}

//if triphasic
else if (stimShape->currentIndex() == StimParameters::Triphasic)
{
//minimum equals 2*D1 + D2
minimum = (2 * firstPhaseDuration->getTrueValue()) + secondPhaseDuration->getTrueValue();
//duration equals 2*D1 + D2
waveformDuration = (2 * firstPhaseDuration->getTrueValue()) + secondPhaseDuration->getTrueValue();
}

pulseTrainPeriod->setTrueMinimum(minimum);
return waveformDuration;
}


/* Private slot that constrains pulseTrainPeriod's lowest possible value to the sum of the durations of active phases */
void StimParamDialog::constrainPulseTrainPeriod()
{
pulseTrainPeriod->setTrueMinimum(calculateWaveformDuration());
}


/* Private method for calculating the total duration of the single pulse */
double StimParamDialog::calculateActualSinglePulseDuration()
{
double totalPulseDuration = postTriggerDelay->getTrueValue() + calculateWaveformDuration() + refractoryPeriod->getTrueValue();
return totalPulseDuration;
}


/* Private slot that calculates the actual single pulse duration and updates its label */
void StimParamDialog::updateActualSinglePulseDuration()
{
double totalPulseDuration = calculateActualSinglePulseDuration();

if (totalPulseDuration < 999)
actualSinglePulseDurationLabel->setText("Actual single pulse duration: " + QString::number(totalPulseDuration, 'f', 1) + " " + QSTRING_MU_SYMBOL + "s");
else
actualSinglePulseDurationLabel->setText("Actual single pulse duration: " + QString::number(totalPulseDuration/1000, 'f', 3) + " ms");

if (totalPulseDuration <= maxPulseDuration_us)
actualSinglePulseDurationLabel->setStyleSheet("QLabel {}");
else
actualSinglePulseDurationLabel->setStyleSheet("QLabel {color: red}");
}


Expand All @@ -919,10 +989,10 @@ void StimParamDialog::roundTimeInputs()
//refractoryPeriod
refractoryPeriod->round();

//preStimAmpSettle
//preStimAmpSettle
preStimAmpSettle->round();

//postStimAmpSettle
//postStimAmpSettle
postStimAmpSettle->round();

//postStimChargeRecovOn
Expand Down
10 changes: 9 additions & 1 deletion source/stimparamdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public slots:
void notifyFocusChanged(QWidget *lostFocus, QWidget *gainedFocus);

private:
double calculateWaveformDuration();
double calculateActualSinglePulseDuration();

QDialogButtonBox *buttonBox;

StimFigure *stimFigure;
Expand Down Expand Up @@ -120,7 +123,11 @@ public slots:
QLabel *postStimChargeRecovOffLabel;
QCheckBox *enableChargeRecovery;

double timestep, currentstep;
double timestep, currentstep, maxPulseDuration_us;

QGroupBox *singlePulseDuration;
QLabel *maxSinglePulseDurationLabel;
QLabel *actualSinglePulseDurationLabel;

private slots:
void enableWidgets();
Expand All @@ -130,6 +137,7 @@ private slots:
void constrainPostStimChargeRecovery();
void constrainRefractoryPeriod();
void constrainPulseTrainPeriod();
void updateActualSinglePulseDuration();
void roundTimeInputs();
void roundCurrentInputs();

Expand Down

0 comments on commit ad97d67

Please sign in to comment.