Changeset 11075 in ntrip
- Timestamp:
- Oct 5, 2026, 6:39:54 PM (6 hours ago)
- Location:
- trunk/BNC
- Files:
-
- 5 added
- 12 edited
-
Example_Configs/31_SsrQC.bnc (modified) (1 diff)
-
src/bnccore.cpp (modified) (2 diffs)
-
src/bnchelp.html (modified) (5 diffs)
-
src/bncmain.cpp (modified) (1 diff)
-
src/bncsettings.cpp (modified) (1 diff)
-
src/bncwindow.cpp (modified) (7 diffs)
-
src/bncwindow.h (modified) (1 diff)
-
src/combination/bnccomb.h (modified) (1 diff)
-
src/ssrQc.cpp (modified) (18 diffs)
-
src/ssrQc.h (modified) (3 diffs)
-
tests/README.md (modified) (1 diff)
-
tests/cases/ssr_qc/case.json (modified) (2 diffs)
-
tests/cases/ssr_qc_select (added)
-
tests/cases/ssr_qc_select/case.json (added)
-
tests/cases/ssr_qc_select/expected (added)
-
tests/cases/ssr_qc_select/expected/SSRA00CNE1_S_20262750000_01D_QC.txt.gz (added)
-
tests/cases/ssr_qc_select/expected/SSRA00GMV0_S_20262750000_01D_QC.txt.gz (added)
Legend:
- Unmodified
- Added
- Removed
-
trunk/BNC/Example_Configs/31_SsrQC.bnc
r11071 r11075 23 23 corrPath= 24 24 corrPort= 25 ssrQcFile=Output/SsrQC.txt 25 ssrQcPath=Output 26 ssrQcStreams=ALL 27 ssrQcRef=CONSENSUS 26 28 ssrQcIntr=1 hour 27 29 ephIntr=1 day -
trunk/BNC/src/bnccore.cpp
r11066 r11075 838 838 delete _ssrQc; _ssrQc = 0; 839 839 bncSettings settings; 840 QString fileName = settings.value("ssrQcFile").toString();841 if ( fileName.isEmpty()) {840 QString dirName = settings.value("ssrQcPath").toString(); 841 if (dirName.isEmpty()) { 842 842 return; 843 843 } 844 expandEnvVar(fileName); 844 expandEnvVar(dirName); 845 QStringList streams = settings.value("ssrQcStreams").toString().split(QRegularExpression("[ ,;]+"), Qt::SkipEmptyParts); 846 if (streams.isEmpty()) { 847 streams << "ALL"; 848 } 845 849 QStringList hlp = settings.value("ssrQcIntr").toString().split(' ', Qt::SkipEmptyParts); 846 850 double interval = 3600.0; … … 851 855 } 852 856 } 853 _ssrQc = new t_ssrQc( fileName, interval);857 _ssrQc = new t_ssrQc(dirName, interval, streams, settings.value("ssrQcRef").toString().trimmed()); 854 858 } 855 859 -
trunk/BNC/src/bnchelp.html
r11072 r11075 147 147 2.8.2 <a href="#corrint">Interval</a><br> 148 148 2.8.3 <a href="#corrport">Port</a><br> 149 2.8.4 <a href="#ssrqc">QC Logfile, QC Interval</a><br>149 2.8.4 <a href="#ssrqc">QC Directory, QC Streams, QC Reference, QC Interval</a><br> 150 150 2.8.4 <a href="#corrwait">Wait for Full Corr Epoch</a><br> 151 151 2.9 <a href="#syncout"><b>Feed Engine</b></a><br> … … 2245 2245 The configuration reads a Broadcast Ephemeris stream and three Broadcast Correction streams in 2246 2246 IGS-SSR format (SSRA00CNE1, SSRA00CHC1) and RTCM-SSR format (Galileo HAS, SSRA00EUH0; needs an account 2247 at the Galileo HAS caster). An hourly QC report per streamand system is saved in the QC logfile2248 ' SsrQC.txt'. The configuration can be applied to a raw file as well:2247 at the Galileo HAS caster). Hourly QC reports per system are saved in one file per stream in directory 2248 'Output'. The configuration can be applied to a raw file as well: 2249 2249 bnc --conf 31_SsrQC.bnc --file <rawFile> --key ssrQcIntr "5 min" 2250 2250 </li> … … 5066 5066 5067 5067 <p> 5068 <h4 id="ssrqc">2.8.4 QC Logfile, QC Interval - optional</h4>5068 <h4 id="ssrqc">2.8.4 QC Directory, QC Streams, QC Reference, QC Interval - optional</h4> 5069 5069 </p> 5070 5070 <p> 5071 5071 BNC can check the quality of all Broadcast Correction streams it decodes, in real time or when reading a raw file 5072 ('--file'). Specify the full path to a 'QC Logfile' to activate this function, and select the 'QC Interval' for 5073 the reports (default: 1 hour). At the end of each interval and at the end of the program, a report is written per 5074 stream and system. Broadcast ephemerides must be received as well, e.g. via stream 'BCEP00BKG0'. 5072 ('--file'). Specify a 'QC Directory' to activate this function. 'QC Streams' selects the streams to be checked: 5073 'ALL' (default) for all decoded Broadcast Correction streams, or a comma separated list of stream names, e.g. 5074 'SSRA00CNE1,SSRA00GMV0'. 'QC Reference' selects the reference of the stream comparison (see below). Select 5075 the 'QC Interval' for the reports (default: 1 hour). One daily file 5076 '<stream>_S_<YYYYDDD>0000_01D_QC.txt' is written per stream; at the end of each interval and at the 5077 end of the program, a report per system is appended. Broadcast ephemerides must be received as well, e.g. via 5078 stream 'BCEP00BKG0'. 5075 5079 </p> 5076 5080 <p> … … 5115 5119 </p> 5116 5120 <pre> 5117 StreamSSRA00GMV05121 === SSR QC SSRA00GMV0 2026-10-02 11:40:00 - 2026-10-02 11:45:00 === 5118 5122 Sys Type Epochs Interval nom/med/max [s] Gaps Out-of-order Latency med/max [s] Sats 5119 5123 G ORBIT 47 1 / 5.0 / 5.0 0 0 11.0 / 12.0 32 … … 5124 5128 G ionosphere-free combination B_IF of the reference signal biases: std. dev. 0.586 m, max |B_IF| 2.058 m (32 satellites) 5125 5129 G largest orbit corrections: radial 1.06 m (G11), along/cross 2.05 m (G11) 5130 </pre> 5131 <p> 5132 <b>Stream comparison:</b> In real time no ground truth is available. Therefore every 30 seconds (with a delay of 60 5133 seconds, so that the corrections of all streams have arrived), the orbits and clocks of each checked stream are 5134 compared with a reference, selected with option 'QC Reference': 5135 </p> 5136 <ul> 5137 <li>'CONSENSUS' (default): per satellite the median of all other streams (at least two). A single erroneous stream 5138 does not affect it, and no combination is needed. The combined stream 'INTERNAL' is not part of the consensus, 5139 as it is derived from the other streams.</li> 5140 <li>the name of a stream, e.g. 'INTERNAL' for the result of the combination (see section 'Combination') or the 5141 stream of a selected AC. Note that an AC contributing to the combination is compared partly with itself, and 5142 that the combined orbits are those of the master AC (orbit differences of the master AC are zero).</li> 5143 </ul> 5144 <p> 5145 For each stream the satellite positions and clocks are computed from the broadcast ephemeris and the latest 5146 orbit and clock corrections at the comparison epoch. 5147 </p> 5148 <ul> 5149 <li>Orbits are only compared between streams referring to the same reference point: Antenna Phase Center (stream 5150 names 'SSRA...') or Center of Mass ('SSRC...'); the differences are given in radial, along-track and cross-track 5151 direction.</li> 5152 <li>Clocks are referred to the reference signals by adding the ionosphere-free linear combination B_IF of the code 5153 biases of the reference signals of the stream (as in the combination). Clocks of satellites without the code 5154 biases of both reference signals are not compared. A common clock offset per stream, system and epoch (the 5155 clock datum) is removed.</li> 5156 <li>The signal-in-space difference SIS = radial orbit difference - clock difference is the part of the orbit and 5157 clock difference affecting the user's range.</li> 5158 </ul> 5159 <p> 5160 The report gives per system the RMS of the differences and, for clock and SIS, the standard deviation (STD) about 5161 the mean of each satellite. Constant differences per satellite (a large RMS with a small STD) are typical between 5162 ACs, as each AC realizes the satellite clocks and code biases with its own datum; a user applies the clocks and 5163 biases of one AC consistently. Satellites whose RMS exceeds three times the median of the system (at least 5 cm) 5164 are listed as deviating, e.g.: 5165 </p> 5166 <pre> 5167 G comparison with consensus of the other streams (27 satellites), RMS (STD about the satellite means) [m]: 5168 clock 0.689 (STD 0.008), radial 0.020, along 0.036, cross 0.022, SIS (radial - clock) 0.687 (STD 0.008) 5169 G satellites deviating from the reference, RMS SIS [m]: G02(0.38) G16(0.33) G20(0.91) G26(3.36) 5126 5170 </pre> 5127 5171 <p> -
trunk/BNC/src/bncmain.cpp
r11066 r11075 210 210 " corrIntr {File interval [character string: 1 min|2 min|5 min|10 min|15 min|30 min|1 hour|1 day]}\n" 211 211 " corrPort {Output port [integer number]}\n" 212 " ssrQcFile {SSR QC logfile, full path [character string]}\n" 213 " ssrQcIntr {SSR QC report interval [character string: 5 min|15 min|30 min|1 hour|1 day]}\n" 212 " ssrQcPath {Directory for SSR QC files, one per stream [character string]}\n" 213 " ssrQcStreams {Streams to check [character string: ALL or comma separated list of stream names]}\n" 214 " ssrQcRef {Reference of the stream comparison [character string: CONSENSUS or a stream name, e.g. INTERNAL]}\n" 215 " ssrQcIntr {SSR QC report interval [character string: 5 min|15 min|30 min|1 hour|1 day]}\n" 214 216 "\n" 215 217 "Feed Engine Panel keys:\n" -
trunk/BNC/src/bncsettings.cpp
r11066 r11075 138 138 setValue_p("corrIntr", "1 day"); 139 139 setValue_p("corrPort", ""); 140 setValue_p("ssrQcFile", ""); 140 setValue_p("ssrQcPath", ""); 141 setValue_p("ssrQcStreams", "ALL"); 142 setValue_p("ssrQcRef", "CONSENSUS"); 141 143 setValue_p("ssrQcIntr", "1 hour"); 142 144 // Feed Engine -
trunk/BNC/src/bncwindow.cpp
r11066 r11075 298 298 } 299 299 _corrPortLineEdit = new QLineEdit(settings.value("corrPort").toString()); 300 _ssrQcFileLineEdit = new QLineEdit(settings.value("ssrQcFile").toString()); 300 _ssrQcPathLineEdit = new QLineEdit(settings.value("ssrQcPath").toString()); 301 _ssrQcRefLineEdit = new QLineEdit(settings.value("ssrQcRef").toString().isEmpty() ? QString("CONSENSUS") : settings.value("ssrQcRef").toString()); 302 _ssrQcStreamsLineEdit = new QLineEdit(settings.value("ssrQcStreams").toString().isEmpty() ? QString("ALL") : settings.value("ssrQcStreams").toString()); 301 303 _ssrQcIntrComboBox = new QComboBox(); 302 304 _ssrQcIntrComboBox->setEditable(false); … … 309 311 _ssrQcIntrComboBox->setCurrentIndex(_ssrQcIntrComboBox->findText("1 hour")); 310 312 } 311 connect(_ssrQc FileLineEdit, SIGNAL(textChanged(const QString&)),313 connect(_ssrQcPathLineEdit, SIGNAL(textChanged(const QString&)), 312 314 this, SLOT(slotBncTextChanged())); 313 315 … … 916 918 cLayout->addWidget(new QLabel("Port"), 3, 0); 917 919 cLayout->addWidget(_corrPortLineEdit, 3, 1); 918 cLayout->addWidget(new QLabel("QC Logfile"), 4, 0); 919 cLayout->addWidget(_ssrQcFileLineEdit, 4, 1, 1, 30); 920 cLayout->addWidget(new QLabel("QC Interval"), 5, 0); 921 cLayout->addWidget(_ssrQcIntrComboBox, 5, 1); 922 cLayout->addWidget(new QLabel(""), 6, 1); 920 cLayout->addWidget(new QLabel("QC Directory"), 4, 0); 921 cLayout->addWidget(_ssrQcPathLineEdit, 4, 1, 1, 30); 922 cLayout->addWidget(new QLabel("QC Streams"), 5, 0); 923 cLayout->addWidget(_ssrQcStreamsLineEdit, 5, 1, 1, 30); 924 cLayout->addWidget(new QLabel("QC Reference"), 6, 0); 925 cLayout->addWidget(_ssrQcRefLineEdit, 6, 1, 1, 30); 926 cLayout->addWidget(new QLabel("QC Interval"), 7, 0); 927 cLayout->addWidget(_ssrQcIntrComboBox, 7, 1); 928 cLayout->addWidget(new QLabel(""), 8, 1); 923 929 cLayout->setRowStretch(7, 999); 924 930 cgroup->setLayout(cLayout); … … 1588 1594 _corrPathLineEdit->setWhatsThis(tr("<p>Specify a directory for saving Broadcast Ephemeris Correction files.</p><p>If the specified directory does not exist, BNC will not create the files. <i>[key: corrPath]</i></p>")); 1589 1595 _corrIntrComboBox->setWhatsThis(tr("<p>Select the length of Broadcast Ephemeris Correction files. <i>[key: corrIntr]</i></p>")); 1590 _ssrQcFileLineEdit->setWhatsThis(tr("<p>Specify the full path to a logfile for the quality control of all decoded Broadcast Correction streams: epochs, update intervals, gaps, latency, completeness, IODs, code biases of the reference signals, clock jumps and orbit correction size.</p><p>Default is an empty option field, meaning that no SSR QC is done. <i>[key: ssrQcFile]</i></p>")); 1596 _ssrQcPathLineEdit->setWhatsThis(tr("<p>Specify a directory for the quality control of decoded Broadcast Correction streams: epochs, update intervals, gaps, latency, completeness, IODs, code and phase bias signals, code biases of the reference signals, clock jumps and orbit correction size. One daily file is written per stream.</p><p>Default is an empty option field, meaning that no SSR QC is done. <i>[key: ssrQcPath]</i></p>")); 1597 _ssrQcRefLineEdit->setWhatsThis(tr("<p>Specify the reference for the comparison of the orbits and clocks of the checked streams: 'CONSENSUS' for the median of all other streams, or the name of a stream, e.g. 'INTERNAL' for the combination.</p><p>Default is 'CONSENSUS'. <i>[key: ssrQcRef]</i></p>")); 1598 _ssrQcStreamsLineEdit->setWhatsThis(tr("<p>Specify the Broadcast Correction streams to be checked, as a comma separated list of stream names (e.g. SSRA00CNE1,SSRA00GMV0), or 'ALL' for all decoded streams.</p><p>Default is 'ALL'. <i>[key: ssrQcStreams]</i></p>")); 1591 1599 _ssrQcIntrComboBox->setWhatsThis(tr("<p>Select the interval of the SSR QC reports. <i>[key: ssrQcIntr]</i></p>")); 1592 1600 _corrPortLineEdit->setWhatsThis(tr("<p>BNC can produce Broadcast Ephemeris Corrections on your local host through an IP port.</p><p>Specify a port number here to activate this function. <i>[key: corrPort]</i></p>")); … … 1863 1871 delete _corrPathLineEdit; 1864 1872 delete _corrIntrComboBox; 1865 delete _ssrQcFileLineEdit; 1873 delete _ssrQcPathLineEdit; 1874 delete _ssrQcStreamsLineEdit; 1875 delete _ssrQcRefLineEdit; 1866 1876 delete _ssrQcIntrComboBox; 1867 1877 delete _corrPortLineEdit; … … 2391 2401 settings.setValue("corrPath", _corrPathLineEdit->text()); 2392 2402 settings.setValue("corrIntr", _corrIntrComboBox->currentText()); 2393 settings.setValue("ssrQcFile", _ssrQcFileLineEdit->text()); 2403 settings.setValue("ssrQcPath", _ssrQcPathLineEdit->text()); 2404 settings.setValue("ssrQcStreams", _ssrQcStreamsLineEdit->text()); 2405 settings.setValue("ssrQcRef", _ssrQcRefLineEdit->text()); 2394 2406 settings.setValue("ssrQcIntr", _ssrQcIntrComboBox->currentText()); 2395 2407 settings.setValue("corrPort", _corrPortLineEdit->text()); … … 2927 2939 enableWidget(enable, _corrIntrComboBox); 2928 2940 } 2929 if (sender() == 0 || sender() == _ssrQcFileLineEdit) { 2930 enableWidget(!_ssrQcFileLineEdit->text().isEmpty(), _ssrQcIntrComboBox); 2941 if (sender() == 0 || sender() == _ssrQcPathLineEdit) { 2942 enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcIntrComboBox); 2943 enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcStreamsLineEdit); 2944 enableWidget(!_ssrQcPathLineEdit->text().isEmpty(), _ssrQcRefLineEdit); 2931 2945 } 2932 2946 -
trunk/BNC/src/bncwindow.h
r11066 r11075 151 151 QLineEdit* _ephPathLineEdit; 152 152 QLineEdit* _corrPathLineEdit; 153 QLineEdit* _ssrQcFileLineEdit; 153 QLineEdit* _ssrQcPathLineEdit; 154 QLineEdit* _ssrQcStreamsLineEdit; 155 QLineEdit* _ssrQcRefLineEdit; 154 156 QLineEdit* _miscMountLineEdit; 155 157 QLineEdit* _miscPortLineEdit; -
trunk/BNC/src/combination/bnccomb.h
r11067 r11075 37 37 bncComb(const bncComb&) = delete; 38 38 bncComb& operator=(const bncComb&) = delete; 39 static bool exists() {return instance != 0;} 39 40 static void destruct() { 40 41 delete instance; -
trunk/BNC/src/ssrQc.cpp
r11073 r11075 45 45 #include "bncutils.h" 46 46 #include "bncconst.h" 47 #include "bncsettings.h" 48 #include "combination/bnccomb.h" 47 49 48 50 using namespace std; … … 51 53 const double MAX_CLK_JUMP = 0.5; // [m] clock change beyond its rate between consecutive epochs 52 54 const double EPH_WARMUP = 120.0; // [s] no ephemeris checks at the start 55 const double CMP_SAMPL = 30.0; // [s] sampling of the stream comparison 56 const double CMP_DELAY = 60.0; // [s] epochs compared when data this much newer arrived 57 const double HIST_LENGTH = 300.0; // [s] corrections kept for the comparison 58 const double DEV_MIN = 0.05; // [m] deviating satellites: RMS above max(DEV_MIN, 59 const double DEV_FACTOR = 3.0; // DEV_FACTOR x median RMS of the system) 60 61 double tKey(const bncTime& tt) { 62 return tt.gpsw() * 604800.0 + tt.gpssec(); 63 } 53 64 const char* TYPE_NAME[] = {"ORBIT", "CLOCK", "CODE_BIAS", "PHASE_BIAS"}; 54 65 … … 91 102 // Constructor 92 103 //////////////////////////////////////////////////////////////////////////// 93 t_ssrQc::t_ssrQc(const QString& fileName, double interval) : _ephUser(true) { 94 _fileName = fileName; 104 t_ssrQc::t_ssrQc(const QString& dirName, double interval, const QStringList& streams, 105 const QString& reference) : _ephUser(true) { 106 _dirName = dirName; 107 _reference = reference.isEmpty() ? QString("CONSENSUS") : reference; 108 bncSettings settings; 109 QStringList cmbStreams = settings.value("cmbStreams").toStringList(); 110 if (!cmbStreams.isEmpty()) { 111 _cmbMaster = cmbStreams[0].split(' ', Qt::SkipEmptyParts).value(0); 112 } 95 113 _interval = interval > 0.0 ? interval : 3600.0; 96 _out = new ofstream(fileName.toLocal8Bit().data()); 114 for (const QString& sta : streams) { 115 if (sta.toUpper() == "ALL") { 116 _streams.clear(); 117 break; 118 } 119 _streams.insert(sta); 120 } 97 121 98 122 connect(this, SIGNAL(newMessage(QByteArray,bool)), BNC_CORE, SLOT(slotMessage(const QByteArray,bool))); … … 105 129 connect(BNC_CORE, SIGNAL(newPhaseBiases(QList<t_satPhaseBias>)), 106 130 this, SLOT(slotNewPhaseBiases(QList<t_satPhaseBias>)), Qt::DirectConnection); 107 108 *_out << "SSR QC of decoded correction streams, report interval " << _interval << " s" << endl 109 << "Checks: epochs and update intervals, gaps (interval > 2 x max(declared, median)), latency," 110 << " completeness w.r.t. healthy broadcast ephemerides," << endl 111 << "IOD matching, code biases of the reference signals, clock jumps > " << MAX_CLK_JUMP 112 << " m, largest orbit corrections" << endl; 131 // combined corrections (stream INTERNAL) 132 if (bncComb::exists()) { 133 connect(BNC_CMB, SIGNAL(newOrbCorrections(QList<t_orbCorr>)), 134 this, SLOT(slotNewOrbCorrections(QList<t_orbCorr>)), Qt::DirectConnection); 135 connect(BNC_CMB, SIGNAL(newClkCorrections(QList<t_clkCorr>)), 136 this, SLOT(slotNewClkCorrections(QList<t_clkCorr>)), Qt::DirectConnection); 137 } 138 } 139 140 // Stream selected for the QC 141 //////////////////////////////////////////////////////////////////////////// 142 bool t_ssrQc::selected(const string& staID) const { 143 return _streams.isEmpty() || _streams.contains(QString::fromStdString(staID)); 144 } 145 146 // Daily QC file of a stream: <staID>_S_<YYYYDDD>0000_01D_QC.txt 147 //////////////////////////////////////////////////////////////////////////// 148 ofstream& t_ssrQc::streamOut(const QString& staID, const bncTime& time) { 149 unsigned year, month, day; 150 time.civil_date(year, month, day); 151 QDate date(year, month, day); 152 QString name = QDir(_dirName).filePath(QString("%1_S_%2%3%4_01D_QC.txt").arg(staID) 153 .arg(year).arg(date.dayOfYear(), 3, 10, QChar('0')).arg("0000")); 154 if (_outNames.value(staID) != name) { 155 delete _outs.value(staID, 0); 156 bool exists = QFile::exists(name); 157 ofstream* out = new ofstream(name.toLocal8Bit().data(), ios_base::out | ios_base::app); 158 if (!exists) { 159 *out << "SSR QC of stream " << staID.toStdString() << ", report interval " << _interval << " s" << endl 160 << "Checks: epochs and update intervals, gaps (interval > 2 x max(declared, median)), latency," 161 << " completeness w.r.t. healthy broadcast ephemerides," << endl 162 << "IOD matching, code and phase bias signals, code biases of the reference signals, clock jumps > " 163 << MAX_CLK_JUMP << " m, largest orbit corrections" << endl; 164 } 165 _outs[staID] = out; 166 _outNames[staID] = name; 167 } 168 return *_outs[staID]; 113 169 } 114 170 … … 118 174 QMutexLocker locker(&_mutex); 119 175 writeReport(true); 120 delete _out; 176 for (ofstream* out : _outs) { 177 delete out; 178 } 121 179 } 122 180 … … 144 202 //////////////////////////////////////////////////////////////////////////// 145 203 void t_ssrQc::checkInterval(const bncTime& time) { 146 if (!_startTime.valid()) {147 _startTime = time;148 }149 204 if (!_intervalStart.valid()) { 150 205 double sec = floor(time.gpssec() / _interval) * _interval; 151 206 _intervalStart = bncTime(time.gpsw(), sec); 207 } 208 // stream comparison of the epochs already complete for all streams 209 if (!_nextCmpEpoch.valid()) { 210 double sec = ceil(time.gpssec() / CMP_SAMPL) * CMP_SAMPL; 211 _nextCmpEpoch = bncTime(time.gpsw(), sec); 212 } 213 while (time - _nextCmpEpoch >= CMP_DELAY) { 214 compareEpoch(_nextCmpEpoch); 215 _nextCmpEpoch = _nextCmpEpoch + CMP_SAMPL; 152 216 } 153 217 if (time - _intervalStart >= _interval) { … … 165 229 void t_ssrQc::newEpoch(const string& staID, char sys, e_type type, const bncTime& time, 166 230 unsigned updateInt, const QString& prn) { 231 QString sta = QString::fromStdString(staID); 232 if (!_staStart.contains(sta)) { 233 _staStart[sta] = time; 234 } 235 if (!_staLast.contains(sta) || _staLast[sta] < time) { 236 _staLast[sta] = time; 237 } 167 238 t_epoStat& stat = _epoStat[key(staID, sys, type)]; 168 239 if (updateInt < unsigned(ssrUpdateInt.size())) { … … 185 256 // Broadcast ephemeris the correction refers to 186 257 //////////////////////////////////////////////////////////////////////////// 187 void t_ssrQc::checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time) { 258 void t_ssrQc::checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time, 259 const string& staID) { 188 260 // not before all broadcast ephemerides may have been received 189 if (time - _sta rtTime< EPH_WARMUP) {261 if (time - _staStart[QString::fromStdString(staID)] < EPH_WARMUP) { 190 262 return; 191 263 } … … 211 283 QMutexLocker locker(&_mutex); 212 284 for (const t_orbCorr& corr : orbCorrections) { 285 if (selected(corr._staID) || corr._staID == _reference.toStdString()) { 286 storeHist(&corr, 0); 287 } 288 if (!selected(corr._staID)) continue; 213 289 QString prn = QString::fromStdString(corr._prn.toString()); 214 290 checkInterval(corr._time); … … 216 292 t_satStat& stat = _satStat[QString::fromStdString(corr._staID) + ' ' + prn]; 217 293 stat.numOrb += 1; 218 checkEph(stat, QString::fromStdString(corr._prn.toInternalString()), corr._iod, corr._time); 294 checkEph(stat, QString::fromStdString(corr._prn.toInternalString()), corr._iod, corr._time, corr._staID); 219 295 double radial = fabs(corr._xr[0]); 220 296 double alongCross = max(fabs(corr._xr[1]), fabs(corr._xr[2])); … … 229 305 QMutexLocker locker(&_mutex); 230 306 for (const t_clkCorr& corr : clkCorrections) { 307 if (selected(corr._staID) || corr._staID == _reference.toStdString()) { 308 storeHist(0, &corr); 309 } 310 if (!selected(corr._staID)) continue; 231 311 QString prn = QString::fromStdString(corr._prn.toString()); 232 312 checkInterval(corr._time); … … 234 314 t_satStat& stat = _satStat[QString::fromStdString(corr._staID) + ' ' + prn]; 235 315 stat.numClk += 1; 236 checkEph(stat, QString::fromStdString(corr._prn.toInternalString()), corr._iod, corr._time); 316 checkEph(stat, QString::fromStdString(corr._prn.toInternalString()), corr._iod, corr._time, corr._staID); 237 317 238 318 // jump: change between consecutive epochs (same IOD) beyond the clock rate … … 260 340 QMutexLocker locker(&_mutex); 261 341 for (const t_satCodeBias& bias : codeBiases) { 342 bool sel = selected(bias._staID); 343 if (!sel && bias._staID != _reference.toStdString()) continue; 262 344 char sys = bias._prn.system(); 263 345 QString prn = QString::fromStdString(bias._prn.toString()); 264 checkInterval(bias._time); 265 newEpoch(bias._staID, sys, CBIAS, bias._time, bias._updateInt, prn); 346 if (sel) { 347 checkInterval(bias._time); 348 newEpoch(bias._staID, sys, CBIAS, bias._time, bias._updateInt, prn); 349 } 266 350 QString staSys = QString("%1 %2").arg(bias._staID.c_str()).arg(sys); 267 351 QMap<QString, double> values; … … 302 386 QMutexLocker locker(&_mutex); 303 387 for (const t_satPhaseBias& bias : phaseBiases) { 388 if (!selected(bias._staID)) continue; 304 389 checkInterval(bias._time); 305 390 newEpoch(bias._staID, bias._prn.system(), PBIAS, bias._time, bias._updateInt, … … 312 397 } 313 398 399 // Reference point of the orbits of a stream: APC (SSRA...), CoM (SSRC...); 400 // the combined stream INTERNAL refers to that of its master AC (the first 401 // AC of the combination) 402 //////////////////////////////////////////////////////////////////////////// 403 char t_ssrQc::refPoint(const QString& staID) const { 404 QString sta = (staID == "INTERNAL") ? _cmbMaster : staID; 405 if (sta.startsWith("SSRA")) return 'A'; 406 if (sta.startsWith("SSRC")) return 'C'; 407 return '?'; 408 } 409 410 // Keep the recent orbit and clock corrections of a stream 411 //////////////////////////////////////////////////////////////////////////// 412 void t_ssrQc::storeHist(const t_orbCorr* orb, const t_clkCorr* clk) { 413 if (orb) { 414 QMap<double, t_orbCorr>& hist = _orbHist[QString::fromStdString(orb->_staID)] 415 [QString::fromStdString(orb->_prn.toInternalString())]; 416 hist[tKey(orb->_time)] = *orb; 417 while (!hist.isEmpty() && hist.firstKey() < tKey(orb->_time) - HIST_LENGTH) { 418 hist.erase(hist.begin()); 419 } 420 } 421 if (clk) { 422 QMap<double, t_clkCorr>& hist = _clkHist[QString::fromStdString(clk->_staID)] 423 [QString::fromStdString(clk->_prn.toInternalString())]; 424 hist[tKey(clk->_time)] = *clk; 425 while (!hist.isEmpty() && hist.firstKey() < tKey(clk->_time) - HIST_LENGTH) { 426 hist.erase(hist.begin()); 427 } 428 } 429 } 430 431 // Compare the orbits and clocks of all streams at epoch tt 432 //////////////////////////////////////////////////////////////////////////// 433 void t_ssrQc::compareEpoch(const bncTime& tt) { 434 double tk = tKey(tt); 435 436 // Satellite positions and clocks of all streams (latest corrections <= tt) 437 QMap<QString, QMap<QString, t_satCrd> > crd; // stream, PRN (external) 438 QMapIterator<QString, QMap<QString, QMap<double, t_clkCorr> > > itSta(_clkHist); 439 while (itSta.hasNext()) { 440 itSta.next(); 441 const QString& staID = itSta.key(); 442 QMapIterator<QString, QMap<double, t_clkCorr> > itPrn(itSta.value()); 443 while (itPrn.hasNext()) { 444 itPrn.next(); 445 const QString& prnInt = itPrn.key(); 446 QMap<double, t_clkCorr>::const_iterator itC = itPrn.value().upperBound(tk); 447 if (itC == itPrn.value().constBegin()) { 448 continue; 449 } 450 const t_clkCorr& clk = (--itC).value(); 451 const QMap<double, t_orbCorr>& orbHist = _orbHist[staID][prnInt]; 452 QMap<double, t_orbCorr>::const_iterator itO = orbHist.upperBound(tk); 453 if (itO == orbHist.constBegin()) { 454 continue; 455 } 456 const t_orbCorr& orb = (--itO).value(); 457 t_eph* eph = 0; 458 t_eph* ephLast = _ephUser.ephLast(prnInt); 459 t_eph* ephPrev = _ephUser.ephPrev(prnInt); 460 if (ephLast && ephLast->IOD() == orb._iod) eph = ephLast; 461 else if (ephPrev && ephPrev->IOD() == orb._iod) eph = ephPrev; 462 if (!eph || clk._iod != orb._iod) { 463 continue; 464 } 465 eph->setOrbCorr(&orb); 466 eph->setClkCorr(&clk); 467 ColumnVector xc(6), vv(3); 468 if (eph->getCrd(tt, xc, vv, true) != success) { 469 continue; 470 } 471 t_satCrd& sc = crd[staID][QString::fromStdString(clk._prn.toString())]; 472 sc.xx = xc.Rows(1,3); 473 sc.vv = vv; 474 sc.clk = xc(4) * t_CST::c; 475 sc.refPoint = refPoint(staID); 476 // clock referred to the reference signals (as in the combination): 477 // + B_IF of the stream's code biases; the combination (INTERNAL) 478 // refers to them already 479 sc.clkOK = (staID == "INTERNAL"); 480 QString satKey = staID + ' ' + QString::fromStdString(clk._prn.toString()); 481 if (!sc.clkOK && _satStat.contains(satKey) && _satStat[satKey].refBiasOK) { 482 sc.clk += _satStat[satKey].biasIF; 483 sc.clkOK = true; 484 } 485 } 486 } 487 488 // Satellites per system 489 QMap<char, QSet<QString> > satsSys; 490 QMapIterator<QString, QMap<QString, t_satCrd> > itCrd(crd); 491 while (itCrd.hasNext()) { 492 itCrd.next(); 493 for (const QString& prn : itCrd.value().keys()) { 494 satsSys[prn[0].toLatin1()].insert(prn); 495 } 496 } 497 498 QMapIterator<char, QSet<QString> > itSys(satsSys); 499 while (itSys.hasNext()) { 500 itSys.next(); 501 const QSet<QString>& sats = itSys.value(); 502 QStringList staList; 503 for (const QString& staID : crd.keys()) { 504 for (const QString& prn : sats) { 505 if (crd[staID].contains(prn)) { 506 staList << staID; 507 break; 508 } 509 } 510 } 511 512 // Clock datum: offset of each stream w.r.t. the per-satellite median of 513 // all streams (or w.r.t. the reference stream) 514 QMap<QString, double> offset; 515 for (const QString& staID : staList) { 516 QVector<double> diffs; 517 for (const QString& prn : sats) { 518 if (!crd[staID].contains(prn) || !crd[staID][prn].clkOK) continue; 519 if (_reference == "CONSENSUS") { 520 QVector<double> all; 521 for (const QString& oo : staList) { 522 if (oo != "INTERNAL" && crd[oo].contains(prn) && crd[oo][prn].clkOK) all << crd[oo][prn].clk; 523 } 524 if (all.size() >= 3) diffs << crd[staID][prn].clk - median(all); 525 } 526 else if (crd.value(_reference).contains(prn) && crd[_reference][prn].clkOK) { 527 diffs << crd[staID][prn].clk - crd[_reference][prn].clk; 528 } 529 } 530 if (!diffs.isEmpty()) { 531 offset[staID] = median(diffs); 532 } 533 } 534 535 // Differences of each selected stream to its reference 536 for (const QString& staID : staList) { 537 if (!selected(staID.toStdString()) || staID == _reference) { 538 continue; 539 } 540 QMap<QString, double> dClk; 541 QMap<QString, ColumnVector> dRac; 542 for (const QString& prn : sats) { 543 if (!crd[staID].contains(prn)) continue; 544 const t_satCrd& sc = crd[staID][prn]; 545 QStringList refs; 546 if (_reference == "CONSENSUS") { 547 for (const QString& oo : staList) { 548 // the combination (INTERNAL) is derived from the other streams 549 if (oo != staID && oo != "INTERNAL" && crd[oo].contains(prn)) refs << oo; 550 } 551 if (refs.size() < 2) continue; 552 refs.clear(); 553 for (const QString& oo : staList) { 554 if (oo != staID && oo != "INTERNAL" && crd[oo].contains(prn)) refs << oo; 555 } 556 } 557 else { 558 if (!crd.value(_reference).contains(prn)) continue; 559 refs << _reference; 560 } 561 QVector<double> clks, xs[3]; 562 for (const QString& oo : refs) { 563 const t_satCrd& rc = crd[oo][prn]; 564 if (rc.clkOK && (oo == _reference || offset.contains(oo))) { 565 clks << rc.clk - (oo == _reference ? 0.0 : offset[oo]); 566 } 567 if (rc.refPoint == sc.refPoint && sc.refPoint != '?') { 568 for (int ii = 0; ii < 3; ii++) xs[ii] << rc.xx[ii]; 569 } 570 } 571 bool clkCmp = sc.clkOK && offset.contains(staID) && 572 (_reference == "CONSENSUS" ? clks.size() >= 2 : clks.size() == 1); 573 if (clkCmp) { 574 dClk[prn] = sc.clk - offset[staID] - median(clks); 575 } 576 else if (!sc.clkOK) { 577 _cmpStat[staID][prn].clkNotReferred += 1; 578 } 579 if (xs[0].size() == refs.size() || (xs[0].size() >= 2 && _reference == "CONSENSUS")) { 580 ColumnVector dx(3); 581 for (int ii = 0; ii < 3; ii++) dx[ii] = sc.xx[ii] - median(xs[ii]); 582 ColumnVector er = sc.xx / sc.xx.NormFrobenius(); 583 ColumnVector ec = crossproduct(sc.xx, sc.vv); 584 ec /= ec.NormFrobenius(); 585 ColumnVector ea = crossproduct(ec, er); 586 ColumnVector rac(3); 587 rac[0] = DotProduct(dx, er); 588 rac[1] = DotProduct(dx, ea); 589 rac[2] = DotProduct(dx, ec); 590 dRac[prn] = rac; 591 } 592 } 593 if (dClk.isEmpty() && dRac.isEmpty()) { 594 continue; 595 } 596 // remaining common clock offset of this stream and epoch 597 double off = dClk.isEmpty() ? 0.0 : median(dClk.values().toVector()); 598 _cmpEpochs[staID] += 1; 599 QSet<QString> prns; 600 for (const QString& prn : dClk.keys()) prns.insert(prn); 601 for (const QString& prn : dRac.keys()) prns.insert(prn); 602 for (const QString& prn : prns) { 603 t_cmpStat& stat = _cmpStat[staID][prn]; 604 if (dRac.contains(prn)) { 605 const ColumnVector& rac = dRac[prn]; 606 stat.numOrb += 1; 607 stat.sR += rac[0] * rac[0]; 608 stat.sA += rac[1] * rac[1]; 609 stat.sC += rac[2] * rac[2]; 610 } 611 if (dClk.contains(prn)) { 612 double dc = dClk[prn] - off; 613 stat.numClk += 1; 614 stat.sClk += dc * dc; 615 stat.mClk += dc; 616 if (dRac.contains(prn)) { 617 double sis = dRac[prn][0] - dc; 618 stat.numSis += 1; 619 stat.sSis += sis * sis; 620 stat.mSis += sis; 621 } 622 } 623 } 624 } 625 } 626 } 627 314 628 // Write the report of the current interval and reset the statistics 315 629 //////////////////////////////////////////////////////////////////////////// 316 630 void t_ssrQc::writeReport(bool final) { 317 if ( !_out ||_epoStat.isEmpty()) {631 if (_epoStat.isEmpty()) { 318 632 return; 319 633 } 320 634 bncTime endTime = final ? _lastTime : _intervalStart + _interval; 321 *_out << endl << "=== SSR QC " << _intervalStart.datestr() << ' ' << _intervalStart.timestr(0)322 << " - " << endTime.datestr() << ' ' << endTime.timestr(0)323 << (final ? " (end of data)" : "") << " ===" << endl;324 635 325 636 // Healthy and current broadcast ephemerides per system … … 347 658 QList<char> systems = itSta.value().values(); 348 659 std::sort(systems.begin(), systems.end()); 349 *_out << endl << "Stream " << staID.toStdString() << endl 350 << " Sys Type Epochs Interval nom/med/max [s] Gaps Out-of-order" 660 ofstream& out = streamOut(staID, _intervalStart); 661 if (final) { 662 endTime = _staLast.value(staID, _lastTime); 663 } 664 out << endl << "=== SSR QC " << staID.toStdString() << ' ' 665 << _intervalStart.datestr() << ' ' << _intervalStart.timestr(0) 666 << " - " << endTime.datestr() << ' ' << endTime.timestr(0) 667 << (final ? " (end of data)" : "") << " ===" << endl 668 << " Sys Type Epochs Interval nom/med/max [s] Gaps Out-of-order" 351 669 << " Latency med/max [s] Sats" << endl; 352 670 for (char sys : systems) { … … 369 687 double maxLat = stat.latencies.isEmpty() ? 0.0 : 370 688 *std::max_element(stat.latencies.begin(), stat.latencies.end()); 371 *_out << " " << sys << " " << left << setw(10) << TYPE_NAME[type] << right689 out << " " << sys << " " << left << setw(10) << TYPE_NAME[type] << right 372 690 << setw(8) << stat.numEpo 373 691 << setw(10) << stat.nominal << " /" << setw(6) << setprecision(1) << fixed << medInt … … 484 802 .arg(maxRadial, 0, 'f', 2).arg(maxRadialPrn).arg(maxAlongCross, 0, 'f', 2).arg(maxAlongCrossPrn); 485 803 } 804 // stream comparison 805 if (_cmpStat.contains(staID)) { 806 unsigned nSat = 0, nClk = 0, nOrb = 0, nSis = 0; 807 double sClk = 0.0, sR = 0.0, sA = 0.0, sC = 0.0, sSis = 0.0; 808 double vClk = 0.0, vSis = 0.0; // sums of squares about the satellite means 809 QMap<QString, double> rmsSis, rmsClk, rmsOrb; 810 QMap<QString, unsigned> notReferred; 811 QMapIterator<QString, t_cmpStat> itC(_cmpStat[staID]); 812 while (itC.hasNext()) { 813 itC.next(); 814 if (itC.key()[0].toLatin1() != sys) continue; 815 const t_cmpStat& st = itC.value(); 816 if (st.numClk == 0 && st.numOrb == 0) { 817 if (st.clkNotReferred) notReferred[itC.key()] = 0; 818 continue; 819 } 820 ++nSat; 821 if (st.clkNotReferred) notReferred[itC.key()] = 0; 822 nClk += st.numClk; sClk += st.sClk; 823 nOrb += st.numOrb; sR += st.sR; sA += st.sA; sC += st.sC; 824 nSis += st.numSis; sSis += st.sSis; 825 if (st.numClk) vClk += st.sClk - st.mClk * st.mClk / st.numClk; 826 if (st.numSis) vSis += st.sSis - st.mSis * st.mSis / st.numSis; 827 if (st.numSis) rmsSis[itC.key()] = sqrt(st.sSis / st.numSis); 828 else if (st.numClk) rmsClk[itC.key()] = sqrt(st.sClk / st.numClk); 829 if (st.numOrb) rmsOrb[itC.key()] = sqrt((st.sR + st.sA + st.sC) / st.numOrb); 830 } 831 if (nSat) { 832 QString refText = (_reference == "CONSENSUS") ? QString("consensus of the other streams") 833 : QString("stream %1").arg(_reference); 834 QStringList parts; 835 if (nClk) parts << QString("clock %1 (STD %2)").arg(sqrt(sClk / nClk), 0, 'f', 3) 836 .arg(sqrt(max(0.0, vClk) / nClk), 0, 'f', 3); 837 if (nOrb) parts << QString("radial %1, along %2, cross %3") 838 .arg(sqrt(sR / nOrb), 0, 'f', 3).arg(sqrt(sA / nOrb), 0, 'f', 3) 839 .arg(sqrt(sC / nOrb), 0, 'f', 3); 840 if (nSis) parts << QString("SIS (radial - clock) %1 (STD %2)").arg(sqrt(sSis / nSis), 0, 'f', 3) 841 .arg(sqrt(max(0.0, vSis) / nSis), 0, 'f', 3); 842 lines << QString("comparison with %1 (%2 satellites), RMS (STD about the satellite means) [m]: %3") 843 .arg(refText).arg(nSat).arg(parts.join(", ")); 844 if (!nOrb) { 845 lines << "orbits not compared: no other stream with the same reference point (APC/CoM)"; 846 } 847 addSats("clocks not compared (code biases of the reference signals missing)", notReferred); 848 auto deviating = [&lines](const QString& what, const QMap<QString, double>& rms) { 849 if (rms.size() < 2) return; 850 double med = median(rms.values().toVector()); 851 QStringList dev; 852 QMapIterator<QString, double> itR(rms); 853 while (itR.hasNext()) { 854 itR.next(); 855 if (itR.value() > max(DEV_MIN, DEV_FACTOR * med)) { 856 dev << QString("%1(%2)").arg(itR.key()).arg(itR.value(), 0, 'f', 2); 857 } 858 } 859 if (!dev.isEmpty()) { 860 lines << QString("satellites deviating from the reference, RMS %1 [m]: %2").arg(what).arg(dev.join(" ")); 861 } 862 }; 863 deviating("SIS", rmsSis); 864 deviating("clock", rmsClk); 865 deviating("orbit (3D)", rmsOrb); 866 } 867 } 486 868 for (const QString& line : lines) { 487 *_out << " " << sys << " " << line.toStdString() << endl;488 } 489 } 490 }491 _out->flush();492 493 emit newMessage(QString("SSR QC: report %1 %2 written to % 3")869 out << " " << sys << " " << line.toStdString() << endl; 870 } 871 } 872 out.flush(); 873 } 874 875 emit newMessage(QString("SSR QC: report %1 %2 of %3 stream(s) written to %4") 494 876 .arg(QString::fromStdString(_intervalStart.datestr())) 495 877 .arg(QString::fromStdString(_intervalStart.timestr(0))) 496 .arg(_fileName).toLatin1(), false); 878 .arg(streams.size()).arg(_dirName).toLatin1(), false); 879 880 _cmpStat.clear(); 881 _cmpEpochs.clear(); 497 882 498 883 // reset, keeping the state needed across intervals -
trunk/BNC/src/ssrQc.h
r11073 r11075 18 18 19 19 public: 20 t_ssrQc(const QString& fileName, double interval); 20 t_ssrQc(const QString& dirName, double interval, const QStringList& streams, 21 const QString& reference); 21 22 ~t_ssrQc(); 22 23 … … 69 70 void newEpoch(const std::string& staID, char sys, e_type type, const bncTime& time, 70 71 unsigned updateInt, const QString& prn); 71 void checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time); 72 void checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time, 73 const std::string& staID); 72 74 void checkInterval(const bncTime& time); 73 75 void writeReport(bool final); … … 77 79 78 80 QMutex _mutex; 79 QString _fileName; 80 std::ofstream* _out; 81 bool selected(const std::string& staID) const; 82 83 // Stream comparison: every CMP_SAMPL seconds, the orbits and clocks of each 84 // stream are compared with a reference: the consensus (median) of all 85 // other streams, or a selected stream (e.g. INTERNAL, the combination) 86 class t_cmpStat { // per stream and satellite 87 public: 88 t_cmpStat() : numClk(0), numOrb(0), numSis(0), clkNotReferred(0), 89 sClk(0.0), sR(0.0), sA(0.0), sC(0.0), sSis(0.0), mClk(0.0), mSis(0.0) {} 90 unsigned numClk; 91 unsigned numOrb; 92 unsigned numSis; 93 unsigned clkNotReferred; // epochs without reference signal biases 94 double sClk, sR, sA, sC, sSis; // sums of squares [m^2] 95 double mClk, mSis; // sums [m] 96 }; 97 class t_satCrd { // satellite position, velocity, clock of a stream 98 public: 99 ColumnVector xx; // [m] 100 ColumnVector vv; // [m/s] 101 double clk; // [m], referred to the reference signals (+ B_IF) if clkOK 102 bool clkOK; 103 char refPoint; // 'A' APC, 'C' CoM, '?' unknown 104 }; 105 void storeHist(const t_orbCorr* orb, const t_clkCorr* clk); 106 void compareEpoch(const bncTime& tt); 107 char refPoint(const QString& staID) const; 108 QString _reference; // "CONSENSUS" or stream name 109 QString _cmbMaster; // mountpoint of the combination's first AC 110 bncTime _nextCmpEpoch; 111 QMap<QString, QMap<QString, QMap<double, t_orbCorr> > > _orbHist; // stream, PRN (internal), time 112 QMap<QString, QMap<QString, QMap<double, t_clkCorr> > > _clkHist; 113 QMap<QString, QMap<QString, t_cmpStat> > _cmpStat; // stream, PRN 114 QMap<QString, unsigned> _cmpEpochs; // stream: compared epochs 115 std::ofstream& streamOut(const QString& staID, const bncTime& time); 116 117 QString _dirName; 118 QSet<QString> _streams; // selected streams, empty: all 119 QMap<QString, std::ofstream*> _outs; // per stream 120 QMap<QString, QString> _outNames; // per stream 81 121 double _interval; 82 122 bncTime _intervalStart; 83 123 bncTime _lastTime; 84 bncTime _startTime; // first correction epoch 124 QMap<QString, bncTime> _staStart; // first correction epoch per stream 125 QMap<QString, bncTime> _staLast; // latest correction epoch per stream 85 126 bncEphUser _ephUser; 86 127 QMap<QString, t_epoStat> _epoStat; // key "STA SYS TYPE" -
trunk/BNC/tests/README.md
r11069 r11075 79 79 * `ssr_rtcm_SSRA00CNE0`, `ssr_rtcm_WHU1_OSBC`: RTCM 1059-1270; these test 80 80 whichever class `RTCM3Decoder.cpp` selects for non-4076 SSR. 81 * `ssr_qc`: SSR QC report (`ssrQcFile`, 5 min interval) of the 7 AC streams of 81 * `ssr_qc`, `ssr_qc_select`: SSR QC (31_SsrQC, one file per stream, 5 min interval; 82 all streams resp. `ssrQcStreams SSRA00CNE1,SSRA00GMV0`) of the 7 AC streams of 82 83 `data/combi_multi_261002_5min.raw`. 83 84 -
trunk/BNC/tests/cases/ssr_qc/case.json
r11074 r11075 1 1 { 2 "description": "31_SsrQC replay: SSR QC report of the 7 AC streams of data/combi_multi_261002_5min.raw, 5 min report interval", 2 "description": "31_SsrQC replay: SSR QC reports (one file per stream, ALL streams) of the 7 AC streams of data/combi_multi_261002_5min.raw, 5 min report interval", 3 3 "config": "Example_Configs/31_SsrQC.bnc", 4 4 "raw": "data/combi_multi_261002_5min.raw", … … 11 11 ], 12 12 "outputs": [ 13 { 14 "glob": "Output/SsrQC.txt", 15 "name": "ssrqc.txt" 16 } 13 "Output/*_QC.txt" 17 14 ] 18 15 }
Note:
See TracChangeset
for help on using the changeset viewer.
