UltraScan III
us_rotor_calibration.cpp
Go to the documentation of this file.
1 
3 #include "us_rotor.h"
4 #include "us_rotor_calibration.h"
5 #include "us_rotor_gui.h"
6 #include "us_abstractrotor_gui.h"
7 #include "us_settings.h"
8 #include "us_license_t.h"
9 #include "us_math2.h"
10 #include "us_util.h"
11 #include "us_db2.h"
12 #include "us_license.h"
13 #include "us_gui_settings.h"
14 #include "us_run_details2.h"
15 #include "us_passwd.h"
16 #include "us_get_dbexp.h"
17 
19 // the class US_FitMeniscus.
20 
21 int main( int argc, char* argv[] )
22 {
23  QApplication application( argc, argv );
24 
25  #include "main1.inc"
26 
27  // License is OK. Start up.
28 
30  w.show();
31  return application.exec();
32 }
33 
35 {
36  unsigned int row = 0;
37 
38  rotor = "Default Rotor";
39  newlimit = false;
40 
41  setWindowTitle( tr( "Edit Rotor Calibration" ) );
42  setPalette( US_GuiSettings::frameColor() );
43  check = QIcon( US_Settings::appBaseDir() + "/etc/check.png" );
44 
45  QGridLayout* top = new QGridLayout( this );
46  top->setSpacing ( 2 );
47  top->setContentsMargins ( 2, 2, 2, 2 );
48 
49  QLabel* lb_instructions = us_label( tr("Instructions:"), -1 );
50  top->addWidget( lb_instructions, row, 0, 1, 1 );
51 
52  le_instructions = us_lineedit( "", -1 );
53  le_instructions->setReadOnly( true );
54  le_instructions->setText( tr("Please load a calibration data set..."));
55  top->addWidget( le_instructions, row++, 1 );
56 
57  // Radio buttons
59  connect( disk_controls, SIGNAL( changed ( bool ) ),
60  SLOT ( source_changed( bool ) ) );
61  top->addLayout( disk_controls, row++, 0 );
62 
63  pb_load = us_pushbutton( tr( "Load Calibration Data" ) );
64  connect( pb_load, SIGNAL( clicked() ), SLOT( load() ) );
65  top->addWidget( pb_load, row++, 0 );
66 
67  QLabel* lb_cell = us_label( tr("Current Cell:"), -1 );
68  int height = lb_instructions->size().height();
69  lb_cell->setMaximumHeight( height );
70  top->setColumnStretch( row, 0 );
71 
72  top->addWidget( lb_cell, row++, 0 );
73 
74  ct_cell = new QwtCounter(this); // Update range upon load
75  //ct_cell = us_counter ( 2, 0, 0 );
76  ct_cell->setStep( 1 );
77  top->addWidget( ct_cell, row++, 0 );
78 
79  QLabel* lb_channel = us_label( tr("Current Channel:"), -1 );
80  lb_channel->setMaximumHeight( height );
81  top->addWidget( lb_channel, row++, 0 );
82 
83  ct_channel = new QwtCounter (this); // Update range upon load
84  //ct_channel = us_counter ( 2, 0, 0 ); // Update range upon load
85  ct_channel->setStep( 1 );
86  top->addWidget( ct_channel, row++, 0);
87 
88  QGridLayout* lo_top = us_radiobutton( tr( "Top of channel" ), rb_top );
89  connect( rb_top, SIGNAL( clicked() ), SLOT( update_position() ) );
90  top->addLayout( lo_top, row++, 0 );
91 
92  QGridLayout* lo_bottom = us_radiobutton( tr( "Bottom of channel" ), rb_bottom );
93  connect( rb_bottom, SIGNAL( clicked() ), SLOT( update_position() ) );
94  top->addLayout( lo_bottom, row++, 0 );
95 
96  QGridLayout* lo_assigned = us_checkbox( tr( "Limits are assigned" ), cb_assigned, false );
97  connect (cb_assigned, SIGNAL (clicked()), this, SLOT (update_used()));
98  top->addLayout( lo_assigned, row++, 0 );
99 
100  pb_accept = us_pushbutton( tr( "Next" ) );
101  pb_accept->setEnabled( false );
102  connect( pb_accept, SIGNAL( clicked() ), SLOT( next() ) );
103  top->addWidget( pb_accept, row++, 0 );
104 
105  pb_calculate = us_pushbutton( tr( "Calculate" ) );
106  pb_calculate->setEnabled( false );
107  connect( pb_calculate, SIGNAL( clicked() ), SLOT( calculate() ) );
108  top->addWidget( pb_calculate, row++, 0 );
109 
110  //QLabel* lb_spacer = us_banner( tr( "" ) );
111  //lb_spacer->setMaximumHeight( height );
112  //top->addWidget( lb_spacer, row++, 0 );
113  top->addItem( new QSpacerItem( 0, 0 ), row++, 0 );
114 
115  pb_save = us_pushbutton( tr( "Save Rotor Calibration" ) );
116  connect( pb_save, SIGNAL( clicked() ), SLOT( save() ) );
117  pb_save->setEnabled( false );
118  top->addWidget( pb_save, row++, 0 );
119 
120  pb_view = us_pushbutton( tr( "View Calibration Report" ) );
121  connect( pb_view, SIGNAL( clicked() ), SLOT( view() ) );
122  pb_view->setEnabled( false );
123  top->addWidget( pb_view, row++, 0 );
124 
125  QPushButton* pb_help = us_pushbutton( tr( "Help" ) );
126  connect( pb_help, SIGNAL( clicked() ), SLOT( help() ) );
127  top->addWidget( pb_help, row++, 0 );
128 
129  pb_reset = us_pushbutton( tr( "Reset" ) );
130  pb_reset->setEnabled ( false );
131  connect( pb_reset, SIGNAL( clicked() ), SLOT( reset() ) );
132  top->addWidget( pb_reset, row++, 0 );
133 
134  QPushButton* pb_close = us_pushbutton( tr( "Close" ) );
135  connect( pb_close, SIGNAL( clicked() ), SLOT( close() ) );
136  top->addWidget( pb_close, row++, 0 );
137 
138  // Plot layout on right side of window
139  plot = new US_Plot( data_plot,
140  tr( "Intensity Data\n(Channel A in red, Channel B in green)" ),
141  tr( "Radius (in cm)" ), tr( "Intensity" ) );
142 
143  data_plot->setMinimumSize( 700, 400 );
144  data_plot->enableAxis( QwtPlot::xBottom, true );
145  data_plot->enableAxis( QwtPlot::yLeft , true );
146  data_plot->setAxisScale( QwtPlot::xBottom, 5.7, 7.3 );
147  data_plot->setAxisAutoScale( QwtPlot::yLeft );
148 
149  connect ( plot, SIGNAL ( zoomedCorners( QwtDoubleRect) ),
150  this, SLOT ( currentRect ( QwtDoubleRect) ) );
151 
152  top->addLayout( plot, 1, 1, row - 1, 1 );
153 
154  top->setColumnStretch( 0, 0 );
155  top->setColumnStretch( 1, 1 );
156 
157  pick = new US_PlotPicker( data_plot );
158 
159  // Set rubber band to display for Control+Left Mouse Button
160  pick->setRubberBand ( QwtPicker::VLineRubberBand );
161  pick->setMousePattern( QwtEventPattern::MouseSelect1, Qt::LeftButton, Qt::ControlModifier );
162 }
163 
165 {
166  avg.clear();
167  data.scanData.clear();
168  allData.clear();
169 
170  data_plot ->detachItems( QwtPlotItem::Rtti_PlotCurve );
171  data_plot ->clear();
172  data_plot ->replot();
173  plot->btnZoom ->setChecked( false );
174 
175  pb_load ->setEnabled( true );
176  pb_reset ->setEnabled( false );
177  pb_accept ->setEnabled( false );
178  pb_calculate ->setEnabled( false );
179  pb_save ->setEnabled( false );
180  pb_view ->setEnabled( false );
181 
182  cb_assigned ->setChecked( false );
183 
184  QPalette p = US_GuiSettings::pushbColor();
185 
186 // disconnect(ct_cell);
187 // disconnect(ct_channel);
188 // ct_cell ->setRange(0, 0, 1);
189 // ct_channel ->setRange(0, 0, 1);
190 // connect (ct_channel, SIGNAL(valueChanged (double)), this, SLOT(update_channel(double)));
191 // connect (ct_cell, SIGNAL(valueChanged (double)), this, SLOT(update_cell(double)));
192 
193  rotor = "Default Rotor";
194  le_instructions->setText("Please load a calibration data set...");
195 }
196 
197 // Process situation where the disk/db selection has changed
199 {
200  QStringList DB = US_Settings::defaultDB();
201 
202  if ( db && ( DB.size() < 5 ) )
203  {
204  QMessageBox::warning( this,
205  tr( "Attention" ),
206  tr( "There is no default database set." ) );
207  }
208 
209  load();
210  reset();
211 }
212 
213 // Changes the default data location
215 {
216  ( db ) ? disk_controls->set_db() : disk_controls->set_disk();
217 }
218 
219 // Function to load calibration data
221 {
222  if ( disk_controls->db() )
223  loadDB();
224 
225  else
226  loadDisk();
227 
228 }
229 
230 // load the experimental calibration dataset and store all data in allData,
231 // an array of all scans, cells, channels and wavelengths.
233 {
234  // Ask for data directory
235  workingDir = QFileDialog::getExistingDirectory( this,
236  tr("US3 Raw Data Directory"),
238  QFileDialog::DontResolveSymlinks );
239 
240  // Restore area beneath dialog
241  qApp->processEvents();
242 
243  if ( workingDir.isEmpty() ) return;
244 
245  workingDir.replace( "\\", "/" ); // WIN32 issue
246  if ( workingDir.right( 1 ) != "/" ) workingDir += "/"; // Ensure trailing /
247 
248  reset();
249 
250  QStringList components = workingDir.split( "/", QString::SkipEmptyParts );
251 
252  runID = components.last();
253 
254  QStringList nameFilters = QStringList( "*.auc" );
255 
256  QDir d( workingDir );
257 
258  files = d.entryList( nameFilters,
259  QDir::Files | QDir::Readable, QDir::Name );
260 
261  if ( files.size() == 0 )
262  {
263  QMessageBox::warning( this,
264  tr( "No Files Found" ),
265  tr( "There were no files of the form *.auc\n"
266  "found in the specified directory." ) );
267  return;
268  }
269 
270  // Look for cell / channel / wavelength combinations
271  maxcell=0;
272  maxchannel=0;
273  for ( int i = 0; i < files.size(); i++ )
274  {
275  QStringList part = files[ i ].split( "." );
276 
277  QString t = part[ 2 ] + " / " + part[ 3 ] + " / " + part[ 4 ];
278  if ((part[3] == "A") && (maxchannel < 1)) maxchannel = 1;
279  if ((part[3] == "B") && (maxchannel < 2)) maxchannel = 2;
280  if ((part[3] == "C") && (maxchannel < 3)) maxchannel = 3;
281  if ((part[3] == "D") && (maxchannel < 4)) maxchannel = 4;
282  if ((part[3] == "E") && (maxchannel < 5)) maxchannel = 5;
283  if ((part[3] == "F") && (maxchannel < 6)) maxchannel = 6;
284  if ((part[3] == "G") && (maxchannel < 7)) maxchannel = 7;
285  if ((part[3] == "H") && (maxchannel < 8)) maxchannel = 8;
286  if (maxcell < part[2].toInt())
287  {
288  maxcell = part[2].toInt();
289  }
290  if ( ! triples.contains( t ) ) triples << t;
291  }
292  ct_cell->setRange(1, maxcell, 1);
293  ct_channel->setRange(1, maxchannel, 1);
294 
295  // Read all data
296  if ( workingDir.right( 1 ) != "/" ) workingDir += "/"; // Ensure trailing /
297 
298  QString file;
299  foreach ( file, files )
300  {
301  QString filename = workingDir + file;
302 
303  int result = US_DataIO::readRawData( filename, data );
304  if ( result != US_DataIO::OK )
305  {
306  QMessageBox::warning( this,
307  tr( "UltraScan Error" ),
308  tr( "Could not read data file.\n" )
309  + US_DataIO::errorString( result ) + "\n" + filename );
310  return;
311  }
312 
313  allData << data;
314  data.scanData.clear();
315  }
316 
317  if ( allData.isEmpty() )
318  {
319  QMessageBox::warning( this,
320  tr( "UltraScan Error" ),
321  tr( "Could not read any data file." ) );
322  return;
323  }
324 
325  dataType = QString( QChar( data.type[ 0 ] ) )
326  + QString( QChar( data.type[ 1 ] ) );
327 
328  if ( dataType != "RI" )
329  {
330  if ( dataType != "FI" )
331  {
332  le_instructions->setText( tr("Attention - ") + runID +
333  tr(" is not absorbance or fluorescence intensity data,\nit is ") +
334  dataType + tr(" data - aborting."));
335  return;
336  }
337  }
338  Limit tmp_limit;
339  limit.clear();
340  for (int i=0; i<allData.size(); i++) // all the triples
341  {
342  tmp_limit.used[0] = false;
343  tmp_limit.used[1] = false;
344  limit.push_back(tmp_limit);
345  }
346 
347  current_triple = -1;
348  top_of_cell = false;
349  pb_reset->setEnabled ( true );
350  pb_accept->setEnabled( true );
351  next();
352 }
353 
355 {
356  US_Passwd pw;
357  QString masterPW = pw.getPasswd();
358  US_DB2 db( masterPW );
359 
360  if ( db.lastErrno() != US_DB2::OK )
361  {
362  QMessageBox::information( this,
363  tr( "Error" ),
364  tr( "Database connectivity error" ) );
365 
366  return;
367  }
368 
369  // Present a dialog to ask user which experiment to load
370  QString expID;
371  US_GetDBExp dialog( expID );
372  if ( dialog.exec() == QDialog::Rejected )
373  return;
374 
375  if ( expID == QString( "" ) )
376  return;
377 
378  // Restore area beneath dialog
379  qApp->processEvents();
380 
381  // Get the rawDataID's that correspond to this experiment
382  QStringList q( "get_rawDataIDs" );
383  q << expID;
384  db.query( q );
385 
386  QStringList rawDataIDs;
387  QStringList filenames;
388 
389  while ( db.next() )
390  {
391  rawDataIDs << db.value( 0 ).toString();
392  filenames << db.value( 2 ).toString();
393  }
394 
395  if ( rawDataIDs.size() < 1 )
396  {
397  QMessageBox::information( this,
398  tr( "Error" ),
399  tr( "There were no auc files found in the database." ) );
400  return;
401  }
402 
403  // Set the runID
404  QStringList parts = filenames[ 0 ].split( "." );
405  runID = parts[ 0 ];
406 
407  // Look for cell / channel / wavelength combinations
408  maxcell=0;
409  maxchannel=0;
410  for ( int i = 0; i < filenames.size(); i++ )
411  {
412  QStringList part = filenames[ i ].split( "." );
413 
414  QString t = part[ 2 ] + " / " + part[ 3 ] + " / " + part[ 4 ];
415  if ((part[3] == "A") && (maxchannel < 1)) maxchannel = 1;
416  if ((part[3] == "B") && (maxchannel < 2)) maxchannel = 2;
417  if ((part[3] == "C") && (maxchannel < 3)) maxchannel = 3;
418  if ((part[3] == "D") && (maxchannel < 4)) maxchannel = 4;
419  if ((part[3] == "E") && (maxchannel < 5)) maxchannel = 5;
420  if ((part[3] == "F") && (maxchannel < 6)) maxchannel = 6;
421  if ((part[3] == "G") && (maxchannel < 7)) maxchannel = 7;
422  if ((part[3] == "H") && (maxchannel < 8)) maxchannel = 8;
423  if (maxcell < part[2].toInt())
424  {
425  maxcell = part[2].toInt();
426  }
427  if ( ! triples.contains( t ) ) triples << t;
428  }
429  ct_cell->setRange(1, maxcell, 1);
430  ct_channel->setRange(1, maxchannel, 1);
431 
432  // Load the data
433  QDir dir;
434  QString tempdir = US_Settings::tmpDir() + "/";
435  if ( ! dir.exists( tempdir ) )
436  {
437  if ( ! dir.mkpath( tempdir ) )
438  {
439  qDebug() << "Error: Could not create temporary directory for auc files\n"
440  << tempdir;
441  return ;
442  }
443  }
444 
445  for ( int i = 0; i < rawDataIDs.size(); i++ )
446  {
447  QString filename = tempdir + filenames[ i ];
448  int readStatus = db.readBlobFromDB( filename, QString( "download_aucData" ), rawDataIDs[ i ].toInt() );
449  if ( readStatus != US_DB2::OK )
450  {
451  QMessageBox::warning( this,
452  tr( "Error" ),
453  tr( "Error downloading the data.\n" )
454  + db.lastError() + "\n" + filename );
455  return;
456  }
457 
458  int result = US_DataIO::readRawData( filename, data );
459  if ( result != US_DataIO::OK )
460  {
461  QMessageBox::warning( this,
462  tr( "Error" ),
463  tr( "Could not read data file.\n" )
464  + US_DataIO::errorString( result ) + "\n" + filename );
465  return;
466  }
467 
468  dataType = QString( QChar( data.type[ 0 ] ) )
469  + QString( QChar( data.type[ 1 ] ) );
470 
471  if (dataType != "RI")
472  {
473  if (dataType != "FI")
474  {
475  QMessageBox::information( this, tr( "Error" ),
476  runID + tr( " is not absorbance or fluorescence intensity data,\nit is ") +
477  dataType + tr(" data - aborting."));
478  return;
479  }
480  }
481 
482  allData << data;
483  data.scanData.clear();
484 
485  QFile( filename ).remove();
486  }
487 
488  Limit tmp_limit;
489  limit.clear();
490  for (int i=0; i<allData.size(); i++) // all the triples
491  {
492  tmp_limit.used[0] = false;
493  tmp_limit.used[1] = false;
494  limit.push_back(tmp_limit);
495  }
496 
497  current_triple = -1;
498  top_of_cell = false;
499  pb_reset->setEnabled ( true );
500  pb_accept->setEnabled( true );
501  next();
502 }
503 
504 // plot all selected limit regions. Routine will automatically
505 // filter out desired cells only, and show only upper or lower regions
507 {
508  data_plot->detachItems( QwtPlotItem::Rtti_PlotCurve );
509  data_plot->clear();
510  data_plot->setTitle(tr( "Intensity Data" ));
511  data_plot->setAxisTitle( QwtPlot::xBottom, tr( "Radius (in cm)" ) );
512  data_plot->setAxisTitle( QwtPlot::yLeft, tr( "Intensity" ) );
513  QwtPlotCurve* c1;
514  QwtPlotCurve* c2;
515  QPen channelAPen( Qt::red );
516  QPen channelBPen( Qt::green );
517  for (int j=0; j<allData[current_triple].scanData.size(); j++) //all scans in each triple
518  {
519  QString title="";
520  QString guid = US_Util::uuid_unparse( (uchar*)allData[current_triple].rawGUID );
521  char type[ 3 ];
522  type[ 2 ] = '\0';
523  memcpy( type, allData[current_triple].type, 2 );
524  QTextStream ts(&title);
525  ts << tr("Cell ") << allData[current_triple].cell
526  << tr( ", Channel ") << allData[current_triple].channel
527  << tr(", Speed ") << allData[current_triple].scanData[j].rpm
528  << tr(", Scan ") << j
529  << tr(", GUID ") << guid
530  << tr(", Type ") << type;
531  //qDebug() << title;
532  c1 = us_curve( data_plot, title );
533  c2 = us_curve( data_plot, title );
534  c1->setPaintAttribute( QwtPlotCurve::ClipPolygons, true );
535  c2->setPaintAttribute( QwtPlotCurve::ClipPolygons, true );
536  int size = (int) (allData[current_triple].pointCount()/2)-1;
537  double *x1 = new double [size];
538  double *y1 = new double [size];
539  double *x2 = new double [size];
540  double *y2 = new double [size];
541  for (int k=0; k<size; k++)
542  {
543  x1[k] = allData[current_triple].radius( k );
544  y1[k] = allData[current_triple].value( j, k );
545  }
546  for (int k=0; k<size; k++)
547  {
548  x2[k] = allData[current_triple].radius( size+k );
549  y2[k] = allData[current_triple].value( j, size+k );
550  }
551  ct_channel->disconnect();
552  if ((QString) allData[current_triple].channel == "A") ct_channel->setValue(1);
553  if ((QString) allData[current_triple].channel == "B") ct_channel->setValue(2);
554  if ((QString) allData[current_triple].channel == "C") ct_channel->setValue(3);
555  if ((QString) allData[current_triple].channel == "D") ct_channel->setValue(4);
556  if ((QString) allData[current_triple].channel == "E") ct_channel->setValue(5);
557  if ((QString) allData[current_triple].channel == "F") ct_channel->setValue(6);
558  if ((QString) allData[current_triple].channel == "G") ct_channel->setValue(7);
559  if ((QString) allData[current_triple].channel == "H") ct_channel->setValue(8);
560  connect (ct_channel, SIGNAL(valueChanged (double)), this, SLOT(update_channel(double)));
561  c1->setData( x1, y1, size);
562  c2->setData( x2, y2, size);
563  if (top_of_cell)
564  {
565  c1->setPen(channelAPen);
566  c2->setPen(channelBPen);
567  }
568  else
569  {
570  c1->setPen(channelBPen);
571  c2->setPen(channelAPen);
572  }
573  delete x1;
574  delete y1;
575  delete x2;
576  delete y2;
577  }
578  if (top_of_cell)
579  {
580  data_plot->setAxisScale( QwtPlot::xBottom, 5.7, 7.3 );
581  cb_assigned->setChecked(limit[current_triple].used[0]);
582  rb_top->setChecked( true );
583  }
584  else
585  {
586  data_plot->setAxisScale( QwtPlot::xBottom, 5.7, 7.3 );
587  cb_assigned->setChecked(limit[current_triple].used[1]);
588  rb_bottom->setChecked( true );
589  }
590  data_plot->replot();
591 }
592 
593 // check the limits of the zoomed region and store them in limits[],
594 // for each limit of the cells and counterbalance. If a limit has been defined,
595 // set the flag for this limit set to true. (disabled) Only assign rectangles that result
596 // from zoom actions when newlimit is true. Set newlimit to false immediately
597 // afterwards to prevent automatic zoom actions to override a zoom limit
598 void US_RotorCalibration::currentRect (QwtDoubleRect rect)
599 {
600  if (newlimit)
601  {
602  if (top_of_cell)
603  {
604  limit[current_triple].rect[0] = rect;
605  limit[current_triple].used[0] = true;
606  cb_assigned->setChecked( true );
607  }
608  else
609  {
610  limit[current_triple].rect[1] = rect;
611  limit[current_triple].used[1] = true;
612  cb_assigned->setChecked( true );
613  }
614  pb_calculate->setEnabled( true );
615  }
616  newlimit = false;
617 }
618 
619 
620 // Once a limit region has been zoomed the user will accept the limits and
621 // the routine will automatically cycle to the next limit region. If all limit
622 // regions have been assigned, the routine will automatically calculate the
623 // stretch coefficients.
624 // This function will set up the desired limit region for zooming to capture
625 // the corresponding limit[] entry
627 {
628  if (!top_of_cell)
629  {
630  current_triple ++;
631  if (current_triple == allData.size())
632  {
633  current_triple --; // make sure we don't go out of bound
634  le_instructions->setText("All vertical regions have been reviewed, calculating rotor calibration...");
635  pb_accept->setEnabled( false );
636  calculate();
637  return;
638  }
640  current_channel = (QString) allData[current_triple].channel;
641  ct_cell->disconnect();
642  ct_channel->disconnect();
643  ct_cell->setValue(allData[current_triple].cell);
644  if ((QString) allData[current_triple].channel == "A") ct_channel->setValue(1.0);
645  if ((QString) allData[current_triple].channel == "B") ct_channel->setValue(2.0);
646  if ((QString) allData[current_triple].channel == "C") ct_channel->setValue(3.0);
647  if ((QString) allData[current_triple].channel == "D") ct_channel->setValue(4.0);
648  if ((QString) allData[current_triple].channel == "E") ct_channel->setValue(5.0);
649  if ((QString) allData[current_triple].channel == "F") ct_channel->setValue(6.0);
650  if ((QString) allData[current_triple].channel == "G") ct_channel->setValue(7.0);
651  if ((QString) allData[current_triple].channel == "H") ct_channel->setValue(8.0);
652  connect (ct_cell, SIGNAL(valueChanged (double)), this, SLOT(update_cell(double)));
653  connect (ct_channel, SIGNAL(valueChanged (double)), this, SLOT(update_channel(double)));
654  }
656  pb_accept->setEnabled( true );
657  le_instructions->setText( tr( "Please zoom the left vertical region and click "
658  "\"Accept\" when done zooming..." ) );
659  update_plot();
660 }
661 
662 // calculates the stretch coefficients based on the averages that
663 // are gleaned from the limit regions applied over the corresponding raw data.
664 // If there are multiple scans for each cell at each speed, this routine
665 // will also average the averages from each scan for corresponding
666 // speed, cell, channel, top or bottom. Then the routine will calculate
667 // the differences between the first speed and all other speeds for each
668 // unique cell, channel, position, and speed entry. These differences will
669 // again be averaged for all entries that have the same speed. Those averages
670 // will be plotted and fitted to a 2nd order polynomial (quadratic function)
671 // to obtain the stretch function. The point (0,0) is added to the measured
672 // values to assure that the zeroth-order coefficient is close to zero.
674 {
675 
676  QString str = "";
677  avg.clear();
678  Average tmp_avg;
679 
680  // For each cell, channel, speed use the appropriate limits to average
681  // the points within the limits, producing two points for each scan, one
682  // for the top of the channel and one for the bottom of the channel. These
683  // points are stored in the avg structure together with the identifying
684  // cell, channel and speed information
685 
686  for ( int i = 0; i < allData.size(); i++ ) // all the triples
687  {
688  for ( int j = 0; j < allData[ i ].scanData.size(); j++ ) //all scans in each triple
689  {
690  if ( ( QString)allData[ i ].channel == "A" ) tmp_avg.channel = 0;
691  if ( ( QString)allData[ i ].channel == "B" ) tmp_avg.channel = 1;
692  if ( ( QString)allData[ i ].channel == "C" ) tmp_avg.channel = 2;
693  if ( ( QString)allData[ i ].channel == "D" ) tmp_avg.channel = 3;
694  if ( ( QString)allData[ i ].channel == "E" ) tmp_avg.channel = 4;
695  if ( ( QString)allData[ i ].channel == "F" ) tmp_avg.channel = 5;
696  if ( ( QString)allData[ i ].channel == "G" ) tmp_avg.channel = 6;
697  if ( ( QString)allData[ i ].channel == "H" ) tmp_avg.channel = 7;
698 
699  tmp_avg.cell = allData[ i ].cell;
700  tmp_avg.rpm = (int) allData[ i ].scanData[ j ].rpm;
701 
702  if ( limit[ i ].used[ 0 ] )
703  {
704  tmp_avg.top = findAverage( limit[i].rect[ 0 ], allData[i], j );
705  }
706  else
707  {
708  tmp_avg.top = 0.0;
709  }
710 
711  if ( limit[ i ].used[ 1 ] )
712  {
713  tmp_avg.bottom = findAverage( limit[ i ].rect[ 1 ], allData[ i ], j );
714  }
715  else
716  {
717  tmp_avg.bottom = 0.0;
718  }
719 
720  avg.push_back(tmp_avg);
721  }
722  }
723 
724  QVector< int > speeds;
725  speeds.clear();
726  QVector< int > cells;
727  cells.clear();
728 
729  // Find out how many unique speeds and cells there are in the experiment:
730 
731  for ( int i = 0; i < allData.size(); i++ ) // all the triples
732  {
733  for ( int j = 0; j < allData[ i ].scanData.size(); j++ ) //all scans in each triple
734  {
735  if ( ! speeds.contains( (int)allData[ i ].scanData[ j ].rpm ) )
736  {
737  speeds << (int)allData[ i ].scanData[ j ].rpm;
738  }
739 
740  if ( ! cells.contains( allData[ i ].cell ) )
741  {
742  cells << allData[ i ].cell;
743  }
744  }
745  }
746 
747  qSort( speeds ); // sort the speeds with the slowest being the first element
748  QVector< Average > avg2;
749  avg2.clear();
750 
751  // in order to average out the top and bottom values for multiple scans
752  // performed at the same speed, channel and cell we first find out how
753  // many points there are in each set, and save the results in tmp_avg.
754  // If there are no points for a corresponding cell, channel, top/bottom
755  // and speed, set the average for those to zero. Next, average by all
756  // points available and save the results in avg2.
757 
758  for ( int i = 0; i < speeds.size(); i++ )
759  {
760  for ( int j = 0; j < cells.size(); j++ )
761  {
762  for ( int k = 0; k < maxchannel; k++ )
763  {
764  tmp_avg.top = 0;
765  tmp_avg.bottom = 0;
766  tmp_avg.top_count = 0;
767  tmp_avg.bottom_count = 0;
768  tmp_avg.cell = j;
769  tmp_avg.channel = k;
770  tmp_avg.rpm = speeds[ i ];
771 
772  for ( int L = 0; L < avg.size(); L++ )
773  {
774  if ( avg[ L ].rpm == speeds[ i ] &&
775  avg[ L ].cell == cells[ j ] &&
776  avg[ L ].channel == k )
777  {
778  if ( avg[ L ].top != 0 )
779  {
780  tmp_avg.top += avg[ L ].top;
781  tmp_avg.top_count++;
782  }
783  if ( avg[ L ].bottom != 0 )
784  {
785  tmp_avg.bottom += avg[ L ].bottom;
786  tmp_avg.bottom_count++;
787  }
788  }
789  }
790 
791  if ( tmp_avg.top_count == 0 )
792  {
793  tmp_avg.top = 0;
794  }
795  else
796  {
797  tmp_avg.top = tmp_avg.top / tmp_avg.top_count;
798  }
799 
800  if ( tmp_avg.bottom_count == 0 )
801  {
802  tmp_avg.bottom = 0;
803  }
804  else
805  {
806  tmp_avg.bottom = tmp_avg.bottom / tmp_avg.bottom_count;
807  }
808 
809  avg2.push_back( tmp_avg );
810  }
811  }
812  }
813 
814  // Collect all averages for each cell, channel and speed in a 2-dimensional
815  // array where each row is another cell, channel or top and bottom position
816  // (reading[i]), and each column is another speed (reading[i][j]). On the
817  // second dimension the entries are ordered with increasing speed. Each
818  // 'reading' now contains the combined average of a respective cell,
819  // channel, top and bottom position, ordered by speed in the second
820  // dimension of 'reading'.
821 
822  QVector< double > entry_top;
823  QVector< double > entry_bottom;
824 
825  for ( int i = 0; i < reading.size(); i++ )
826  {
827  reading[ i ].clear();
828  }
829 
830  reading.clear();
831 
832  for ( int i = 0; i < maxcell; i++ ) //cells
833  {
834  for ( int j = 0; j < maxchannel; j++ ) //channels
835  {
836  entry_top.clear();
837  entry_bottom.clear();
838 
839  for ( int k = 0; k < speeds.size(); k++ )
840  {
841  for ( int L = 0; L < avg2.size(); L++ )
842  {
843  if ( avg2[ L ].rpm == speeds[ k ] &&
844  avg2[ L ].cell == i &&
845  avg2[ L ].channel == j )
846  {
847  entry_top << avg2[ L ].top;
848  entry_bottom << avg2[ L ].bottom;
849  }
850  }
851  }
852 
853  reading << entry_top;
854  reading << entry_bottom;
855  }
856  }
857 
858  for ( int i = 0; i < reading.size(); i++ )
859  {
860  str = "";
861  QTextStream ts( &str );
862 
863  for ( int j = 0; j < reading[ i ].size(); j++ )
864  {
865  if ( (int) reading[ i ][ j ] != 0 )
866  ts << reading[ i ][ j ] << "\t";
867  }
868  }
869 
870  // For each speed, generate the differences by subtracting the position of
871  // the lowest speed.
872 
873  for ( int i = 0; i < reading.size(); i++ )
874  {
875  str = "";
876  QTextStream ts( &str );
877 
878  for ( int j = 1; j < reading[ i ].size(); j++ )
879  {
880  if ( (int)reading[ i ][ j ] != 0 )
881  ts << reading[ i ][ j ] - reading[ i ][ 0 ]<< "\t";
882  }
883  }
884 
885 
886  // Calculate the averages and standard deviations for all entries for a
887  // given speed:
888 
889  stretch_factors.clear();
890  std_dev.clear();
891 
892  for ( int i = 1; i < speeds.size(); i++ )
893  {
894  entry_top.clear();
895  double sum1 = 0.0;
896  int k = 0;
897 
898  for ( int j = 0; j < reading.size(); j++ )
899  {
900  if ( i < reading[ j ].size() && (int)reading[ j ][ i ] != 0 )
901  {
902  entry_top << reading[ j ][ i ] - reading[ j ][ 0 ];
903  sum1 += reading[ j ][ i ] - reading[ j ][ 0 ];
904  k++;
905  }
906  }
907 
908  stretch_factors << sum1 /k; // save the average of all values for this speed.
909 
910  double sum2 = 0.0;
911 
912  for ( int j = 0; j < k; j++ )
913  {
914  sum2 += sq( entry_top[ j ] - sum1 / k );
915  }
916 
917  std_dev << sqrt( sum2 / k );
918  }
919 
920  int size = stretch_factors.size();
921 
922  x .resize( size );
923  y .resize( size );
924  sd1.resize( size );
925  sd2.resize( size );
926 
927  for ( int i = 0; i < size; i++ )
928  {
929  x[ i ] = speeds[ i ];
930  y[ i ] = stretch_factors[ i ];
931  }
932 
933  if ( ! US_Matrix::lsfit( coef, x.data(), y.data(), size, 3 ) )
934  {
935  QMessageBox::warning( this,
936  tr( "Data Problem" ),
937  tr( "The data is inadequate for this fit order" ) );
938  }
939 
940  // since the calculations were done with the lowest speed (which isn't zero) as a reference,
941  // the intercept at rpm=zero should now be negative, which reflects the stretching difference
942  // between zero rpm and the lowest speed used here as a reference. To correct for this error,
943  // the zeroth-order term needs to be added as an offset to all stretch values and the readings
944  // need to be refit, to hopefully give a vanishing zeroth order term.
945 
946  for ( int i = 0; i < size; i++ )
947  {
948  y [ i ] = stretch_factors[ i ] - coef[ 0 ];
949  sd1[ i ] = y[ i ] + std_dev[ i ];
950  sd2[ i ] = y[ i ] - std_dev[ i ];
951  }
952 
953  plot->btnZoom->setChecked( false );
954  data_plot->clear();
955  data_plot->replot();
956  QwtPlotCurve* c1;
957  QwtPlotCurve* c2;
958  QwtPlotCurve* c3;
959  QwtPlotCurve* c4;
960 
961  QwtSymbol sym1;
962  QwtSymbol sym2;
963 
964  sym1.setStyle( QwtSymbol::Ellipse );
965  sym1.setBrush( QColor( Qt::cyan ) );
966  sym1.setPen ( QColor( Qt::white ) );
967  sym1.setSize( 10 );
968 
969  sym2.setStyle( QwtSymbol::Cross );
970  sym2.setBrush( QColor( Qt::white ) );
971  sym2.setPen ( QColor( Qt::white ) );
972  sym2.setSize( 10 );
973 
974  c1 = us_curve( data_plot, "Rotor Stretch" );
975  c1->setData ( x.data(), y.data(), size );
976  c1->setSymbol( sym1 );
977  c1->setStyle ( QwtPlotCurve::NoCurve );
978 
979  c2 = us_curve( data_plot, "+std Dev" );
980  c2->setData ( x.data(), sd1.data(), size );
981  c2->setSymbol( sym2 );
982  c2->setStyle ( QwtPlotCurve::NoCurve );
983 
984  c3 = us_curve( data_plot, "+std Dev" );
985  c3->setData ( x.data(), sd2.data(), size );
986  c3->setSymbol( sym2 );
987  c3->setStyle ( QwtPlotCurve::NoCurve );
988 
989  if ( ! US_Matrix::lsfit( coef, x.data(), y.data(), size, 3 ) )
990  {
991  QMessageBox::warning( this,
992  tr( "Data Problem" ),
993  tr( "The data is inadequate for this fit order" ) );
994  }
995 
996  QVector< double > xfit( 501 );
997  QVector< double > yfit( 501 );
998 
999  for ( int i = 0; i < 501; i++ )
1000  {
1001  xfit[ i ] = (double) i * 60000.0 / 500.0;
1002  yfit[ i ] = coef[ 0 ] + coef[ 1 ] * xfit[ i ] + coef[ 2 ] * sq( xfit[ i ] );
1003  }
1004 
1005  c4 = us_curve( data_plot, "fit" );
1006  c4->setData ( xfit.data(), yfit.data(), 501 );
1007  c4->setStyle ( QwtPlotCurve::Lines );
1008  c4->setPen ( QColor( Qt::yellow ) );
1009 
1010  data_plot->setTitle(tr( "Rotor Stretch\n"
1011  "(Error bars = 1 standard deviation)" ) );
1012 
1013  data_plot->setAxisTitle( QwtPlot::xBottom, tr( "Revolutions per minute" ) );
1014  data_plot->setAxisTitle( QwtPlot::yLeft, tr( "Stretch (in cm)" ) );
1015  data_plot->setAxisAutoScale( QwtPlot::xBottom );
1016  data_plot->setAxisAutoScale( QwtPlot::yLeft );
1017  data_plot->replot();
1018 
1019  pb_save->setEnabled( true );
1020  pb_view->setEnabled( true );
1021 
1022  QDateTime now = QDateTime::currentDateTime();
1023  fileText = "CALIBRATION REPORT FOR ROTOR: " + rotor + "\nPERFORMED ON: " + now.toString();
1024  fileText += "\n\nCalibration is based on data from run: " + runID;
1025  fileText += "\n\nThe following equation was fitted to the measured "
1026  "stretch values for this rotor:\n\n";
1027 
1028  fileText += "Stretch = " + QString("%1").arg(coef[0], 0, 'e', 5 ) + " + "
1029  + QString("%1").arg(coef[1], 0, 'e', 5 ) + " rpm + "
1030  + QString("%1").arg(coef[2], 0, 'e', 5 ) + " rpm^2\n\n";
1031 
1032  fileText += "Below is a listing of the stretching values as a function of speed:\n\n";
1033  fileText += "Speed: Stretch (cm): Standard Dev.:\n";
1034 
1035  fileText += QString( "%1" ).arg( 0, 5, 10 ) + " "
1036  + QString( "%1" ).arg( 0.0, 0, 'e', 5 ) + " "
1037  + QString( "%1" ).arg( 0.0, 0, 'e', 5 ) + "\n";
1038 
1039  for ( int i = 0; i < size; i++ )
1040  {
1041  fileText += QString( "%1" ).arg( speeds[ i + 1 ], 5, 10) + " "
1042  + QString( "%1").arg( y[ i ], 0, 'e', 5 ) + " "
1043  + QString( "%1").arg( std_dev[ i ], 0, 'e', 5 ) + "\n";
1044  }
1045  fileText += "\nBased on these stretching factors, the bottom of each cell and channel at ";
1046  fileText += "rest is estimated to be as follows:\n\n";
1047  fileText += "Cell: Channel: Top: Bottom: Length: Center:\n\n";
1048 
1049  double top_avg = 0.0;
1050  double bottom_avg = 0.0;
1051  double length_avg = 0.0;
1052  double center_avg = 0.0;
1053  int top_count = 0;
1054  int bottom_count = 0;
1055  int length_count = 0;
1056  int center_count = 0;
1057 
1058  for ( int j = 0; j < cells.size(); j++ )
1059  {
1060  for ( int k = 0; k < maxchannel; k++ )
1061  {
1062  double sum1 = 0.0;
1063  double sum2 = 0.0;
1064  int m = 0;
1065  int n = 0;
1066 
1067  for ( int i = 0; i < speeds.size(); i++ )
1068  {
1069  for ( int L = 0; L < avg2.size(); L++ )
1070  {
1071  if ( avg2[ L ].rpm == speeds[ i ] &&
1072  avg2[ L ].cell == j &&
1073  avg2[ L ].channel == k )
1074  {
1075  if ( (int) avg2[ L ].top != 0 )
1076  {
1077  sum1 += avg2[ L ].top - ( coef[ 1 ] * speeds[ i ] +
1078  coef[ 2 ] * sq( speeds[ i ] ) );
1079  m++;
1080  }
1081 
1082  if ( (int)avg2[ L ].bottom != 0 )
1083  {
1084  sum2 += avg2[ L ].bottom - ( coef[ 1 ] * speeds[ i ] +
1085  coef[ 2 ] * sq( speeds[ i ] ) );
1086  n++;
1087  }
1088  }
1089  }
1090  }
1091 
1092  fileText += QString( "%1" ).arg( j + 1, 2, 10 ) + " "
1093  + QString( "%1" ).arg( k + 1, 2, 10 ) + " ";
1094 
1095  if ( m > 0 )
1096  {
1097  fileText += QString( "%1" ).arg( sum1 / m, 0, 'e', 5 ) + " ";
1098 
1099  if ( (cells.size() == 4 && j !=3) || (cells.size() == 8 && j != 7) )
1100  {
1101  top_avg += sum1 / m;
1102  top_count++;
1103  }
1104  }
1105  else
1106  {
1107  fileText += " N/D ";
1108  }
1109 
1110  if ( n > 0 )
1111  {
1112  fileText += QString("%1").arg(sum2/n, 0, 'e', 5 ) + " ";
1113 
1114  if ( (cells.size() == 4 && j !=3) || (cells.size() == 8 && j != 7) )
1115  {
1116  bottom_avg += sum2 / n;
1117  bottom_count++;
1118  }
1119  }
1120  else
1121  {
1122  fileText += " N/D ";
1123  }
1124 
1125  if ( n > 0 && m > 0 )
1126  {
1127  fileText += QString( "%1" ).arg( (sum2 / n) - ( sum1 / m ), 0, 'e', 5 ) + " " +
1128  QString( "%1" ).arg( (sum1 / m) +
1129  ( ( sum2 / n) - ( sum1 / m ) ) / 2.0, 0, 'e', 5 ) + "\n";
1130 
1131  if ( (cells.size() == 4 && j != 3) || (cells.size() == 8 && j != 7) )
1132  {
1133  length_avg += ( sum2 / n ) - ( sum1 / m );
1134  length_count++;
1135  center_avg += ( sum1 / m) + ( ( sum2 / n ) - ( sum1 / m ) ) / 2.0;
1136  center_count++;
1137  }
1138  }
1139  else
1140  {
1141  fileText += " N/D N/D \n";
1142  }
1143  }
1144  }
1145 
1146  fileText += "_______________________________________________________________\n";
1147  fileText += "Avgs. for CPs: ";
1148  fileText += QString( "%1" ).arg( top_avg / top_count, 0, 'e', 5 ) + " ";
1149  fileText += QString( "%1" ).arg( bottom_avg / bottom_count, 0, 'e', 5 ) + " ";
1150  fileText += QString( "%1" ).arg( length_avg / length_count, 0, 'e', 5 ) + " ";
1151  fileText += QString( "%1" ).arg( center_avg / center_count, 0, 'e', 5 ) + "\n\n";
1152 }
1153 
1154 
1155 double US_RotorCalibration::findAverage( QwtDoubleRect rect,
1156  US_DataIO::RawData data, int i )
1157 {
1158  double average = 0.0;
1159  int j = 0, k = 0;
1160  while (data.xvalues[j] < rect.right() && j < data.xvalues.size()-1)
1161  {
1162  // Note: rect.bottom() is actually the upper limit of the bounding rectangle,
1163  // and rect.top() is the lower limit of the bounding rectangle - weird screen coordinates?
1164  if ( data.xvalues[j] > rect.left() &&
1165  data.scanData[i].rvalues[j] < rect.bottom() &&
1166  data.scanData[i].rvalues[j] > rect.top() )
1167  {
1168  average += data.xvalues[j];
1169  k++;
1170  }
1171  j++;
1172  }
1173  if (k == 0)
1174  {
1175  return 0.0;
1176  }
1177  else
1178  {
1179  return (average/(double)k);
1180  }
1181 }
1182 
1184 {
1185  // load up calibration data
1187 
1188  // ID would be added later
1189  // This is a GUID for the calibration profile
1190  current.GUID = US_Util::new_guid();
1191  current.coeff1 = coef[ 1 ];
1192  current.coeff2 = coef[ 2 ];
1193  current.report = fileText;
1194  current.lastUpdated = QDate::currentDate();
1195  current.omega2t = 0.0; // replace ??
1196 
1197  // Let's verify that the experiment GUID is in the db
1198  // and matches the current runID
1199  US_Passwd pw;
1200  QString masterPW = pw.getPasswd();
1201  US_DB2 db( masterPW );
1202 
1203  if ( db.lastErrno() != US_DB2::OK )
1204  {
1205  QMessageBox::information( this,
1206  tr( "Error" ),
1207  tr( "Database connectivity error" ) );
1208 
1209  return;
1210  }
1211 
1212  // Let's see if we can find the run ID
1213  QStringList q( "get_experiment_info_by_runID" );
1214  q << runID
1215  << QString::number( US_Settings::us_inv_ID() );
1216  db.query( q );
1217 
1218  if ( db.lastErrno() == US_DB2::NOROWS )
1219  {
1220  QMessageBox::information( this,
1221  tr( "Error" ),
1222  tr( "The current runID cannot be found in the database" ) );
1223 
1224  return;
1225  }
1226 
1227  // Ok, let's get more info
1228  db.next();
1229  current.calibrationExperimentID = db.value( 1 ).toString().toInt();
1230  current.calibrationExperimentGUID = db.value( 2 ).toString();
1231  current.rotorID = db.value( 6 ).toString().toInt();
1232 
1233  q.clear();
1234  q << "get_rotor_info"
1235  << QString::number( current.rotorID );
1236  db.query( q );
1237  db.next();
1238  current.rotorGUID = db.value( 0 ).toString();
1239 
1240  US_RotorGui* rotorDialog = new US_RotorGui(
1241  current, // calibration data
1242  true, // this is a new calibration
1243  false, // signal wanted
1244  US_Disk_DB_Controls::DB ); // disk or db
1245 
1246  rotorDialog->exec();
1247 
1248 }
1249 
1250 // locate the corresponding triple for a particular cell/channel combination
1252 {
1253  pb_accept->setEnabled( true );
1254  current_triple = 0;
1255  while(current_triple < allData.size())
1256  {
1257  if (current_cell == allData[current_triple].cell
1258  && current_channel == (QString) allData[current_triple].channel)
1259  {
1260  break;
1261  }
1262  current_triple ++;
1263  }
1264  if (top_of_cell)
1265  {
1266  cb_assigned->setChecked(limit[current_triple].used[0]);
1267  }
1268  else
1269  {
1270  cb_assigned->setChecked(limit[current_triple].used[1]);
1271  }
1272  if (current_triple == allData.size()-1)
1273  {
1274  pb_accept->setEnabled( false );
1275  }
1276 }
1277 
1279 {
1280  cb_assigned->setChecked( false );
1281  if (top_of_cell)
1282  {
1283  limit[current_triple].used[0] = false;
1284  }
1285  else
1286  {
1287  limit[current_triple].used[1] = false;
1288  }
1289 }
1290 
1292 {
1293  newlimit = false;
1294  plot->btnZoom->setChecked( false );
1295  plotAll();
1296  plot->btnZoom->setChecked( true );
1297  newlimit = true;
1298 }
1299 
1301 {
1302  current_cell = (int) val;
1303  findTriple();
1304  update_plot();
1305 }
1306 
1308 {
1309  if ((int) val == 1)
1310  {
1311  current_channel = "A";
1312  }
1313  else
1314  {
1315  current_channel = "B";
1316  }
1317  findTriple();
1318  update_plot();
1319 }
1320 
1322 {
1323  top_of_cell = rb_top->isChecked();
1324  update_plot();
1325 }
1326 
1328 {
1329  US_Editor *edit = new US_Editor ( US_Editor::LOAD, true );
1330  edit->setWindowTitle( tr("Rotor Calibration Report") );
1331  edit->move( this->pos() + QPoint( 100, 100 ) );
1332  edit->resize( 600, 500 );
1333  edit->e->setFont( US_Widgets::fixedFont() );
1334  edit->e->setText( fileText );
1335  edit->show();
1336 }
1337