├── .gitignore ├── README.md ├── tool_ascope.py ├── LICENSE ├── PlotInfo.h ├── PlotInfo.cpp ├── AScope.ui ├── AScope.h └── AScope.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | *.o 3 | moc_* 4 | ui_* 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ascope 2 | Source code for a Qt class providing a radar A-scope display. Also provided is a SCons tool file to build the library as dependency `ascope` within an EOL SCons build environment. 3 | -------------------------------------------------------------------------------- /tool_ascope.py: -------------------------------------------------------------------------------- 1 | # -*- python -*- 2 | 3 | from SCons.Script import Environment, Split, Export 4 | 5 | tools = Split(""" 6 | qtcore 7 | qtgui 8 | qtt_scopeplot 9 | qtt_knob 10 | doxygen 11 | fftw 12 | """) 13 | 14 | env = Environment(tools=['default'] + tools) 15 | 16 | # This will create ui_AScope.h 17 | env.Uic(['AScope.ui',]) 18 | 19 | sources = Split(""" 20 | AScope.cpp 21 | PlotInfo.cpp 22 | """) 23 | 24 | headers = Split(""" 25 | AScope.h 26 | PlotInfo.h 27 | """) 28 | 29 | env['DOXYFILE_DICT'].update({'PROJECT_NAME': 'Ascope'}) 30 | html = env.Apidocs(sources + headers) 31 | 32 | ascope = env.Library('ascope', sources) 33 | 34 | env.Default(ascope) 35 | 36 | tooldir = env.Dir('.').srcnode().abspath # this directory 37 | 38 | 39 | def ascope(env): 40 | env.AppendUnique(CPPPATH=[tooldir]) 41 | env.AppendLibrary('ascope') 42 | env.AppendDoxref('ascope') 43 | env.Require(tools) 44 | 45 | 46 | Export('ascope') 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE 2 | ======= 3 | 4 | Copyright (c) 1990 - 2016, University Corporation for Atmospheric 5 | Research (UCAR), Boulder, Colorado, USA. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | 1) If the software is modified to produce derivative works, such 12 | modified software should be clearly marked, so as not to confuse it 13 | with the copyright holder's original. 14 | 15 | 2) Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | 18 | 3) Redistributions in binary form must reproduce the above copyright 19 | notice, this list of conditions and the following disclaimer in the 20 | documentation and/or other materials provided with the distribution. 21 | 22 | 4) Neither the name of the copyright holder nor the names of its 23 | contributors, if any, may be used to endorse or promote products 24 | derived from this software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | -------------------------------------------------------------------------------- /PlotInfo.h: -------------------------------------------------------------------------------- 1 | // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 2 | // ** Copyright UCAR (c) 1990 - 2016 3 | // ** University Corporation for Atmospheric Research (UCAR) 4 | // ** National Center for Atmospheric Research (NCAR) 5 | // ** Boulder, Colorado, USA 6 | // ** BSD licence applies - redistribution and use in source and binary 7 | // ** forms, with or without modification, are permitted provided that 8 | // ** the following conditions are met: 9 | // ** 1) If the software is modified to produce derivative works, 10 | // ** such modified software should be clearly marked, so as not 11 | // ** to confuse it with the version available from UCAR. 12 | // ** 2) Redistributions of source code must retain the above copyright 13 | // ** notice, this list of conditions and the following disclaimer. 14 | // ** 3) Redistributions in binary form must reproduce the above copyright 15 | // ** notice, this list of conditions and the following disclaimer in the 16 | // ** documentation and/or other materials provided with the distribution. 17 | // ** 4) Neither the name of UCAR nor the names of its contributors, 18 | // ** if any, may be used to endorse or promote products derived from 19 | // ** this software without specific prior written permission. 20 | // ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS 21 | // ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 22 | // ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 23 | // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 24 | #ifndef PLOTINFOINC_ 25 | #define PLOTINFOINC_ 26 | 27 | #include 28 | 29 | class PlotInfo { 30 | public: 31 | PlotInfo(); 32 | PlotInfo(int id, int displayType, std::string shortName, std::string longName, 33 | double gainMin, double gainMax, double gainCurrent, 34 | double offsetMin, double offsetMax, double offsetCurrent); 35 | virtual ~PlotInfo(); 36 | 37 | int getId(); 38 | int getDisplayType(); 39 | 40 | void setGain(double min, double max, double current); 41 | void setOffset(double min, double Max, double current); 42 | 43 | double getGainMin(); 44 | double getGainMax(); 45 | double getGainCurrent(); 46 | 47 | double getOffsetMin(); 48 | double getOffsetMax(); 49 | double getOffsetCurrent(); 50 | 51 | std::string getShortName(); 52 | std::string getLongName(); 53 | 54 | /// Set the autoscale flag 55 | /// @param flag True if autscale requested. 56 | void autoscale(bool flag); 57 | 58 | /// @return True if an autoscale is needed. 59 | bool autoscale(); 60 | 61 | protected: 62 | int _id; 63 | int _displayType; 64 | std::string _shortName; 65 | std::string _longName; 66 | double _gainMin; 67 | double _gainMax; 68 | double _gainCurrent; 69 | double _offsetMin; 70 | double _offsetMax; 71 | double _offsetCurrent; 72 | bool _autoscale; 73 | }; 74 | #endif 75 | -------------------------------------------------------------------------------- /PlotInfo.cpp: -------------------------------------------------------------------------------- 1 | // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 2 | // ** Copyright UCAR (c) 1990 - 2016 3 | // ** University Corporation for Atmospheric Research (UCAR) 4 | // ** National Center for Atmospheric Research (NCAR) 5 | // ** Boulder, Colorado, USA 6 | // ** BSD licence applies - redistribution and use in source and binary 7 | // ** forms, with or without modification, are permitted provided that 8 | // ** the following conditions are met: 9 | // ** 1) If the software is modified to produce derivative works, 10 | // ** such modified software should be clearly marked, so as not 11 | // ** to confuse it with the version available from UCAR. 12 | // ** 2) Redistributions of source code must retain the above copyright 13 | // ** notice, this list of conditions and the following disclaimer. 14 | // ** 3) Redistributions in binary form must reproduce the above copyright 15 | // ** notice, this list of conditions and the following disclaimer in the 16 | // ** documentation and/or other materials provided with the distribution. 17 | // ** 4) Neither the name of UCAR nor the names of its contributors, 18 | // ** if any, may be used to endorse or promote products derived from 19 | // ** this software without specific prior written permission. 20 | // ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS 21 | // ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 22 | // ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 23 | // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 24 | #include "PlotInfo.h" 25 | 26 | //////////////////////////////////////////////////////// 27 | PlotInfo::PlotInfo(): 28 | _id(0), 29 | _displayType(0), 30 | _shortName(""), 31 | _longName(""), 32 | _gainMin(-1), 33 | _gainMax(1), 34 | _gainCurrent(0), 35 | _offsetMin(-1), 36 | _offsetMax(1), 37 | _offsetCurrent(0), 38 | _autoscale(true) 39 | { 40 | } 41 | //////////////////////////////////////////////////////// 42 | PlotInfo::PlotInfo(int id, int displayType, std::string shortName, std::string longName, 43 | double gainMin, double gainMax, double gainCurrent, 44 | double offsetMin, double offsetMax, double offsetCurrent): 45 | _id(id), 46 | _displayType(displayType), 47 | _shortName(shortName), 48 | _longName(longName), 49 | _gainMin(gainMin), 50 | _gainMax(gainMax), 51 | _gainCurrent(gainCurrent), 52 | _offsetMin(offsetMin), 53 | _offsetMax(offsetMax), 54 | _offsetCurrent(offsetCurrent), 55 | _autoscale(true) 56 | { 57 | } 58 | 59 | //////////////////////////////////////////////////////// 60 | 61 | PlotInfo::~PlotInfo() 62 | { 63 | } 64 | 65 | //////////////////////////////////////////////////////// 66 | void 67 | PlotInfo::setGain(double min, double max, double current) 68 | { 69 | _gainMin = min; 70 | _gainMax = max; 71 | _gainCurrent = current; 72 | } 73 | 74 | //////////////////////////////////////////////////////// 75 | void 76 | PlotInfo::setOffset(double min, double max, double current) 77 | { 78 | _offsetMin = min; 79 | _offsetMax = max; 80 | _offsetCurrent = current; 81 | } 82 | 83 | //////////////////////////////////////////////////////// 84 | double 85 | PlotInfo::getGainMin() 86 | { 87 | return _gainMin; 88 | } 89 | 90 | //////////////////////////////////////////////////////// 91 | double 92 | PlotInfo::getGainMax() 93 | { 94 | return _gainMax; 95 | } 96 | 97 | //////////////////////////////////////////////////////// 98 | double 99 | PlotInfo::getGainCurrent() 100 | { 101 | return _gainCurrent; 102 | } 103 | 104 | //////////////////////////////////////////////////////// 105 | double 106 | PlotInfo::getOffsetMin() 107 | { 108 | return _offsetMin; 109 | } 110 | 111 | //////////////////////////////////////////////////////// 112 | double 113 | PlotInfo::getOffsetMax() 114 | { 115 | return _offsetMax; 116 | } 117 | 118 | //////////////////////////////////////////////////////// 119 | double 120 | PlotInfo::getOffsetCurrent() 121 | { 122 | return _offsetCurrent; 123 | } 124 | 125 | //////////////////////////////////////////////////////// 126 | std::string 127 | PlotInfo::getShortName() 128 | { 129 | return _shortName; 130 | } 131 | 132 | //////////////////////////////////////////////////////// 133 | std::string 134 | PlotInfo::getLongName() 135 | { 136 | return _longName; 137 | } 138 | //////////////////////////////////////////////////////// 139 | int 140 | PlotInfo::getId() 141 | { 142 | return _id; 143 | } 144 | //////////////////////////////////////////////////////// 145 | int 146 | PlotInfo::getDisplayType() 147 | { 148 | return _displayType; 149 | } 150 | //////////////////////////////////////////////////////// 151 | void 152 | PlotInfo::autoscale(bool b) { 153 | _autoscale = b; 154 | } 155 | //////////////////////////////////////////////////////// 156 | bool 157 | PlotInfo::autoscale() { 158 | return _autoscale; 159 | } 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /AScope.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AScope 4 | 5 | 6 | 7 | 0 8 | 0 9 | 930 10 | 618 11 | 12 | 13 | 14 | AScope 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 0 23 | 24 | 25 | 26 | 27 | 600 28 | 600 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 6 39 | 40 | 41 | 0 42 | 43 | 44 | 45 | 46 | 47 | 0 48 | 0 49 | 50 | 51 | 52 | 53 | 0 54 | 0 55 | 56 | 57 | 58 | Channel 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | 0 68 | 69 | 70 | 71 | Product 72 | 73 | 74 | 75 | 6 76 | 77 | 78 | 9 79 | 80 | 81 | 82 | 83 | 84 | 0 85 | 0 86 | 87 | 88 | 89 | 90 | 200 91 | 200 92 | 93 | 94 | 95 | 96 | 16777215 97 | 16777215 98 | 99 | 100 | 101 | Select the displayed product. 102 | 103 | 104 | 105 | Chan 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 0 124 | 0 125 | 126 | 127 | 128 | Display Mode 129 | 130 | 131 | 132 | 133 | 134 | 135 | 0 136 | 0 137 | 138 | 139 | 140 | Gate Number 141 | 142 | 143 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 0 152 | 0 153 | 154 | 155 | 156 | Select the gate for the one gate display. 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 0 165 | 0 166 | 167 | 168 | 169 | Block size 170 | 171 | 172 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 0 181 | 0 182 | 183 | 184 | 185 | Set the number of samples for one gate mode. 186 | 187 | 188 | 189 | 190 | 191 | 192 | Along Beam 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 6 203 | 204 | 205 | 0 206 | 207 | 208 | 209 | 210 | 211 | 0 212 | 0 213 | 214 | 215 | 216 | Power (DB) 217 | 218 | 219 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 0 228 | 0 229 | 230 | 231 | 232 | 0 233 | 234 | 235 | Qt::AlignCenter 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 6 247 | 248 | 249 | 0 250 | 251 | 252 | 253 | 254 | 255 | 0 256 | 0 257 | 258 | 259 | 260 | Enable time series windowing for the power spectrum. 261 | 262 | 263 | Windowing 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 0 272 | 0 273 | 274 | 275 | 276 | Pause the display. 277 | 278 | 279 | Pause 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 0 288 | 0 289 | 290 | 291 | 292 | Enable X grid display. 293 | 294 | 295 | X Grid 296 | 297 | 298 | true 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 0 307 | 0 308 | 309 | 310 | 311 | Enable Y grid display. 312 | 313 | 314 | Y Grid 315 | 316 | 317 | true 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 6 329 | 330 | 331 | 0 332 | 333 | 334 | 335 | 336 | 337 | 0 338 | 0 339 | 340 | 341 | 342 | Auto scale the display. 343 | 344 | 345 | Auto Scale 346 | 347 | 348 | 349 | 350 | 351 | 352 | Save the display image to a file. 353 | 354 | 355 | Save Image 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 6 365 | 366 | 367 | 0 368 | 369 | 370 | 371 | 372 | 6 373 | 374 | 375 | 0 376 | 377 | 378 | 379 | 380 | 381 | 0 382 | 0 383 | 384 | 385 | 386 | 387 | 30 388 | 0 389 | 390 | 391 | 392 | 393 | 20 394 | 16777215 395 | 396 | 397 | 398 | Scroll the display up. 399 | 400 | 401 | ^ 402 | 403 | 404 | true 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 0 413 | 0 414 | 415 | 416 | 417 | 418 | 30 419 | 0 420 | 421 | 422 | 423 | 424 | 30 425 | 16777215 426 | 427 | 428 | 429 | Scroll the display down. 430 | 431 | 432 | v 433 | 434 | 435 | true 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 0 446 | 0 447 | 448 | 449 | 450 | 451 | 100 452 | 0 453 | 454 | 455 | 456 | 457 | 200 458 | 16777215 459 | 460 | 461 | 462 | Change the vertical range of the display. 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | QFrame::StyledPanel 472 | 473 | 474 | QFrame::Raised 475 | 476 | 477 | 478 | 479 | 480 | 481 | Qt::Vertical 482 | 483 | 484 | 485 | 20 486 | 17 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 0 496 | 0 497 | 498 | 499 | 500 | 501 | 1 502 | 503 | 504 | 505 | 24 506 | 507 | 508 | false 509 | 510 | 511 | Qt::Horizontal 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | Knob 522 | QWidget 523 |
Knob.h
524 |
525 | 526 | ScopePlot 527 | QWidget 528 |
ScopePlot.h
529 |
530 |
531 | 532 | 533 |
534 | -------------------------------------------------------------------------------- /AScope.h: -------------------------------------------------------------------------------- 1 | // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 2 | // ** Copyright UCAR (c) 1990 - 2016 3 | // ** University Corporation for Atmospheric Research (UCAR) 4 | // ** National Center for Atmospheric Research (NCAR) 5 | // ** Boulder, Colorado, USA 6 | // ** BSD licence applies - redistribution and use in source and binary 7 | // ** forms, with or without modification, are permitted provided that 8 | // ** the following conditions are met: 9 | // ** 1) If the software is modified to produce derivative works, 10 | // ** such modified software should be clearly marked, so as not 11 | // ** to confuse it with the version available from UCAR. 12 | // ** 2) Redistributions of source code must retain the above copyright 13 | // ** notice, this list of conditions and the following disclaimer. 14 | // ** 3) Redistributions in binary form must reproduce the above copyright 15 | // ** notice, this list of conditions and the following disclaimer in the 16 | // ** documentation and/or other materials provided with the distribution. 17 | // ** 4) Neither the name of UCAR nor the names of its contributors, 18 | // ** if any, may be used to endorse or promote products derived from 19 | // ** this software without specific prior written permission. 20 | // ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS 21 | // ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 22 | // ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 23 | // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 24 | #ifndef PROFILERSCOPE_H_ 25 | #define PROFILERSCOPE_H_ 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | // Components from the QtToolbox 41 | #include "ScopePlot.h" 42 | #include "Knob.h" 43 | 44 | // The designer generated header file. 45 | #include "ui_AScope.h" 46 | 47 | // PlotInfo knows the characteristics of a plot 48 | #include "PlotInfo.h" 49 | 50 | /** 51 | AScope provides a traditional real-time Ascope display of 52 | eldora time series data and computed products. It is implemented 53 | with Qt, and uses the QtToolbox::ScopePlot as the primary display. 54 | I&Q, I versus Q, IQ power spectrum, and computed product displays 55 | are available. The data can be displayed either along the beam 56 | for all gates, or in time for one gate. Users may select the 57 | fft block size and the gate to be displayed. 58 | 59 | AScope is simply a data consumer; it does not know 60 | anything about the data provider. Signals and slots 61 | used to coordinate with other components. 62 | 63 | It is the responsibility of the data provider to feed data 64 | at a desired rate. AScope will attempt to render all data 65 | delivered. 66 | 67 | There are two data areas, _I/_Q and _fftwData, that are dynamically 68 | resized depending upon the operational mode of the scope. 69 | 70 | _I and _Q contain the I/Q values that are being analyzed. They are 71 | filled as data is delivered to newTSItemSlot. They will be either 72 | be the same as the selected block size, if operating in fixed gate 73 | mode, or the number of gates if operating along beam mode. 74 | 75 | _fftwData is sized to the currently selected block size. Thus this will 76 | match _I/_Q sizes when operating in fixed gate mode. When in along beam mode, 77 | this probably not match the number of gates. The _fftwData will be zero padded 78 | if it is larger than _I/_Q size. If smaller, it will just be filled with 79 | the leading data from _I/_Q. 80 | 81 | Thus, _I/Q gets resized when the block size is changed, if not operating 82 | in along beam mode. It gets resized to the number of gates, 83 | when switching into along beam mode. It gets resized to the block size 84 | when leaving along beam mode. 85 | 86 | Likewise, _fftwData gets resized when the block size changes. A new 87 | fftwPlan gets created at the same time. 88 | 89 | The processing of incoming data will be handled differently depending 90 | upon the type of plot that is currently selected. For instance, if 91 | a time series or I vs Q plot by gate is chosen, I and Q are collected 92 | along the specified gate and displayed. If a power spectrum plot is 93 | chosen, the I and Q data are collected and then a power spectrum is 94 | computed. And so on. This work is done in newTSItemSlot(), and then 95 | the collected data are sent on to proper display method. 96 | 97 | A small QFrame in the controls area is provided for users to add their 98 | own status widgets, branding, etc. 99 | **/ 100 | class AScope : public QWidget, private Ui::AScope { 101 | Q_OBJECT 102 | 103 | /// Time series plot types.Exactly one of these type 104 | /// plots will be created. 105 | enum TS_PLOT_TYPES { 106 | TS_AMPLITUDE_PLOT, ///< time series amplitude plot 107 | TS_POWER_PLOT, ///< time series power plot 108 | TS_IANDQ_PLOT, ///< time series I and Q plot 109 | TS_IVSQ_PLOT, ///< time series I versus Q plot 110 | TS_SPECTRUM_PLOT ///< time series power spectrum plot 111 | }; 112 | 113 | public: 114 | /// The timeseries type for importing data. The actual data 115 | /// are passed by reference, hopefully eliminating an 116 | /// unnecessary copy. 117 | class TimeSeries { 118 | public: 119 | // Data types we deal with. 120 | enum TsDataTypeEnum { VOIDDATA, FLOATDATA, SHORTDATA }; 121 | 122 | /* 123 | * The default constructor sets dataType to VOIDDATA, and this 124 | * value must be set to the correct type by the user before trying 125 | * to extract data using the i() and q() methods. 126 | */ 127 | TimeSeries(); 128 | TimeSeries(TsDataTypeEnum type); 129 | // Get I values by pulse number and gate. 130 | inline double i(int pulse, int gate) const; 131 | // Get I values by pulse number and gate. 132 | inline double q(int pulse, int gate) const; 133 | /// I and Q for each beam is in a vector containing I,Q for each gate. 134 | /// IQbeams contains pointers to each IQ vector for all 135 | /// of the beams in the timeseries. The length of the timeseries 136 | /// can be found from IQbeams.size(). The data types pointed to 137 | /// are defined by our dataType. 138 | std::vector IQbeams; 139 | /// Data type of the pointers in IQbeams 140 | TsDataTypeEnum dataType; 141 | /// The number of gates 142 | int gates; 143 | /// The channel id 144 | int chanId; 145 | /// The sample rate, in Hz 146 | double sampleRateHz; 147 | /// An opaque pointer that can be used to store 148 | /// anything that the caller wants to track along 149 | /// with the TimeSeries. This will be useful when 150 | /// the TimeSeries is returned to the owner, 151 | /// if for example when an associated object such as a 152 | /// DDS sample needs to be returned to DDS. 153 | void* handle; 154 | }; 155 | 156 | /// TimeSeries subclasses for short* and float* data pointers 157 | class ShortTimeSeries : public TimeSeries { 158 | public: 159 | ShortTimeSeries() : TimeSeries(TimeSeries::SHORTDATA) {} 160 | }; 161 | 162 | class FloatTimeSeries : public TimeSeries { 163 | public: 164 | FloatTimeSeries() : TimeSeries(TimeSeries::FLOATDATA) {} 165 | }; 166 | 167 | /// Constructor 168 | /// @param refreshRateHz The rate at which we want the display to 169 | /// update. Data will be (nominally) collected at this rate. 170 | /// @param saveDir The default directory to save images in 171 | /// @param parent The parent widget. 172 | AScope( 173 | double refreshRateHz = 25, 174 | std::string saveDir = ".", 175 | QWidget* parent = 0); 176 | /// Destructor 177 | virtual ~AScope(); 178 | /// @return The user frame, available for adding your 179 | /// own interface elements. Putting large widgets 180 | /// in this area will really mess up the overall 181 | /// layout of the scope. 182 | QFrame* userFrame(); 183 | 184 | signals: 185 | /// emit this signal to alert the client that we 186 | /// are finished with this item. AScope::TimeSeries 187 | /// contains an opaque handle that the client can 188 | /// use to keep track of this item between the 189 | /// triggering of newTSItemSlot() and the emitting 190 | /// of returnTSItem(). 191 | void returnTSItem(AScope::TimeSeries pItem); 192 | 193 | public slots: 194 | /// Feed new timeseries data via this slot. 195 | /// @param pItem This contains some metadata and pointers to I/Q data 196 | void newTSItemSlot(AScope::TimeSeries pItem); 197 | /// Call when the plot type is changed. This function 198 | /// must determine which of the two families of 199 | /// plots, _tsPlotInfo, or _productPlotInfo, the 200 | /// previous and new plot types belong to. 201 | virtual void plotTypeSlot(int plotType); 202 | /// call to save the current plotting parameters for the 203 | /// current plot type, and reload the parameters for the 204 | /// the new plot type. 205 | void plotTypeChange(PlotInfo* pi, TS_PLOT_TYPES plotType); 206 | /// A different tab has been selected. Change the plot type to the 207 | /// currently selected button on that tab. 208 | void tabChangeSlot(QWidget* w); 209 | /// The gain knob value has changed. 210 | virtual void gainChangeSlot(double); 211 | /// slide the plot up. 212 | virtual void upSlot(); 213 | /// Slide the plot down. 214 | virtual void dnSlot(); 215 | /// Initiate an autoscale. A flag is set; during the next 216 | /// pulse reception an autoscale computation is made. 217 | virtual void autoScaleSlot(); 218 | /// Save the scope display to a PNG file. 219 | void saveImageSlot(); 220 | /// Pause the plotting. Any received data are ignored. 221 | /// @param p True to enable pause. 222 | void pauseSlot(bool p); 223 | /// Select the channel 224 | /// @param c The channel (0-3) 225 | void channelSlot(int c); 226 | /// Select the gate 227 | /// @param index The index from the combo box of the selected gate. 228 | void gateChoiceSlot(int index); 229 | /// Select the block size 230 | /// @param size The block size. It must be a power of two. 231 | void blockSizeSlot(int size); 232 | /// Enable/disable windowing 233 | void windowSlot(bool); 234 | /// Select long beam display 235 | void alongBeamSlot(bool); 236 | 237 | /// Get the current block size 238 | unsigned int getBlockSize() const { return _blockSize; } 239 | 240 | protected: 241 | /// Initialize the block size choices. The minimum 242 | /// size will be 32. The max size will be 4096. 243 | /// @todo allow the max (and min?) block sizes to 244 | /// be confiurable. 245 | void initBlockSizes(); 246 | /// Allocate the fftw space and create then plan. 247 | /// Existing space and plan are returned first. 248 | /// Set up the hammimg window coefficients. 249 | /// @param size The fft length. 250 | void initFFT(int size); 251 | /// Initialize the gate selection 252 | /// @param gates The number of gates. 253 | void initGates(int gates); 254 | /// Initialize the channel selection 255 | /// @param channels The number of channels. 256 | void initChans(int channels); 257 | /// Emit a signal announcing the desired gate mode, 258 | /// either along beam, or one gate. The channel select, 259 | /// gate choice and (for one gate mode) data block 260 | /// size will be part of the emitted signal. 261 | void dataMode(); 262 | /// Send the data for the current plot type to the ScopePlot. 263 | void displayData(); 264 | /// Setup the hamming coefficients. 265 | /// @param size The number of coefficients. 266 | void hammingSetup(int size); 267 | /// Apply the hamming filter. 268 | void doHamming(); 269 | /// Autoscale based on a set of data. 270 | /// @param data The data series to be analyzed. 271 | /// @param displayType The type of plot that the data is scaled for. 272 | void autoScale( 273 | std::vector& data, 274 | TS_PLOT_TYPES displayType); 275 | /// Autoscale based on two sets of data. 276 | /// @param data1 The first data series to be analyzed. 277 | /// @param data2 The second data series to be analyzed. 278 | /// @param displayType The type of plot that the data is scaled for. 279 | void autoScale( 280 | std::vector& data1, 281 | std::vector& data2, 282 | TS_PLOT_TYPES displayType); 283 | /// Initialize the combo box choices and FFTs. 284 | /// @param channels The number of channels, 285 | /// @param gates The number of gates 286 | void initCombos(int channels, int gates); 287 | /// Adjust the _graphRange and _graphOffset values. 288 | /// @param min Desired scale minimum 289 | /// @param max Desired scale maximum 290 | /// @param displayType The type of plot that the data is scaled for. 291 | void adjustGainOffset( 292 | double min, 293 | double max, 294 | TS_PLOT_TYPES displayType); 295 | /// Process time series data. 296 | /// @param Idata The I values 297 | /// @param Qdata The Q values 298 | void processTimeSeries( 299 | std::vector& Idata, 300 | std::vector& Qdata); 301 | /// Compute the power spectrum. The input values will come 302 | /// I[]and Q[], the power spectrum will be written to 303 | /// _spectrum[] 304 | /// @param Idata The I time series. 305 | /// @param Qdata The Q time series. 306 | /// @return The zero moment 307 | double powerSpectrum( 308 | std::vector& Idata, 309 | std::vector& Qdata); 310 | /// initialize all of the book keeping structures 311 | /// for the various plots. 312 | void initPlots(); 313 | /// add a ts tab to the plot type selection tab widget. 314 | /// Radio buttons are created for all of specified 315 | /// plty types, and grouped into one button group. 316 | /// _tsPlotInfo provides the label information for 317 | /// the radio buttons. 318 | /// @param tabName The title for the tab. 319 | /// @param types A set of the desired TS_PLOT_TYPES types 320 | /// @return The button group that the inserted buttons 321 | /// belong to. 322 | QButtonGroup* addTSTypeTab( 323 | std::string tabName, 324 | std::set types); 325 | /// Calculate the zeroth moment, using the time 326 | /// series for input. 327 | double zeroMomentFromTimeSeries( 328 | std::vector& I, 329 | std::vector& Q); 330 | // The builtin timer will be used to calculate beam statistics. 331 | void timerEvent(QTimerEvent*); 332 | /// For each TS_PLOT_TYPES, there will be an entry in this map. 333 | std::map _tsPlotInfo; 334 | /// This set contains PLOTTYPEs for all timeseries plots 335 | std::set _timeSeriesPlots; 336 | /// save the button group for each tab, 337 | /// so that we can find the selected button 338 | /// and change the plot type when tabs are switched. 339 | std::vector _tabButtonGroups; 340 | /// This set contains PLOTTYPEs for all raw data plots 341 | std::set _pulsePlots; 342 | /// Holds Y data to display for TimeSeries display 343 | std::vector Y; 344 | /// Holds I data to display for I vs. Q 345 | std::vector I; 346 | /// Holds Q data to display for I vs. Q display 347 | std::vector Q; 348 | /// Holds power spectrum values for display. 349 | std::vector _spectrum; 350 | // how often to update the display 351 | double _refreshIntervalHz; 352 | /// Set true when a plot is chosen which shows results 353 | /// from IQ data. If a plot of products is chosen, 354 | /// it is false. 355 | bool _IQplot; 356 | /// The current selected plot type. 357 | TS_PLOT_TYPES _tsPlotType; 358 | /// The hamming window coefficients 359 | std::vector _hammingCoefs; 360 | /// The possible block/fftw size choices. 361 | std::vector _blockSizeChoices; 362 | /// The fftw plan. This is a handle used by 363 | /// the fftw routines. 364 | //std::vector _fftwPlan; 365 | fftw_plan _fftwPlan; 366 | /// The fftw data array. The fft will 367 | // be performed in place, so both input data 368 | /// and results are stored here. 369 | //std::vector _fftwData; 370 | fftw_complex* _fftwData; 371 | // power correction factor applied to (uncorrected) powerSpectrum() output 372 | double _powerCorrection; 373 | /// The current block size 374 | unsigned int _blockSize; 375 | /// Set true if the Hamming window should be applied 376 | bool _doHamming; 377 | /// The button group for channel selection 378 | QButtonGroup* _chanButtonGroup; 379 | /// Palette for making the leds green 380 | QPalette _greenPalette; 381 | /// Platette for making the leds red 382 | QPalette _redPalette; 383 | /// Set true if the plot graphics are paused 384 | bool _paused; 385 | /// The signal power, computed directly from the I&Q 386 | /// data, or from the power spectrum 387 | double _zeroMoment; 388 | /// The choice of channels (0-3) 389 | int _channel; 390 | /// The selected gate, zero based. 391 | int _gateChoice; 392 | /// Set false to cause initialization of blocksize and 393 | /// gate choices when the first data is received. 394 | bool _combosInitialized; 395 | /// Set true if data are to be taken along the beam. Otherwise 396 | /// data are taken at the specified gate 397 | bool _alongBeam; 398 | /// cumulative error count 399 | int _errorCount[3]; 400 | /// last pulse number 401 | long long _lastPulseNum[3]; 402 | double _knobGain; 403 | double _knobOffset; 404 | double _xyGraphRange; 405 | double _xyGraphCenter; 406 | double _specGraphRange; 407 | double _specGraphCenter; 408 | // storage to collect incoming I values 409 | std::vector _I; 410 | // storage to collect incoming Q values 411 | std::vector _Q; 412 | // the next index of the incoming location to fill in _I and _Q 413 | unsigned int _nextIQ; 414 | /// The number of gates. Initially zero, it is diagnosed from the data stream 415 | int _gates; 416 | /// set true when we want to start capturing the next incoming data 417 | bool _capture; 418 | /// The directory where images are saved. 419 | std::string _saveDir; 420 | /// The sample rate in Hz. 421 | double _sampleRateHz; 422 | }; 423 | 424 | 425 | #endif /*PROFSCOPE_H_*/ 426 | -------------------------------------------------------------------------------- /AScope.cpp: -------------------------------------------------------------------------------- 1 | // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 2 | // ** Copyright UCAR (c) 1990 - 2016 3 | // ** University Corporation for Atmospheric Research (UCAR) 4 | // ** National Center for Atmospheric Research (NCAR) 5 | // ** Boulder, Colorado, USA 6 | // ** BSD licence applies - redistribution and use in source and binary 7 | // ** forms, with or without modification, are permitted provided that 8 | // ** the following conditions are met: 9 | // ** 1) If the software is modified to produce derivative works, 10 | // ** such modified software should be clearly marked, so as not 11 | // ** to confuse it with the version available from UCAR. 12 | // ** 2) Redistributions of source code must retain the above copyright 13 | // ** notice, this list of conditions and the following disclaimer. 14 | // ** 3) Redistributions in binary form must reproduce the above copyright 15 | // ** notice, this list of conditions and the following disclaimer in the 16 | // ** documentation and/or other materials provided with the distribution. 17 | // ** 4) Neither the name of UCAR nor the names of its contributors, 18 | // ** if any, may be used to endorse or promote products derived from 19 | // ** this software without specific prior written permission. 20 | // ** DISCLAIMER: THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS 21 | // ** OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 22 | // ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 23 | // *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 24 | #include "AScope.h" 25 | #include "ScopePlot.h" 26 | #include "Knob.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | 50 | #include 51 | #include 52 | #include 53 | 54 | #include 55 | 56 | #if __GNUC__ > 4 57 | using std::isnan; 58 | using std::isinf; 59 | using std::isinf; 60 | #endif 61 | 62 | 63 | ////////////////////////////////////////////////////////////////////// 64 | AScope::AScope(double refreshRateHz, std::string saveDir, QWidget* parent ) : 65 | QWidget(parent), 66 | _refreshIntervalHz(refreshRateHz), 67 | _IQplot(true), 68 | _fftwData(0), 69 | _blockSize(64), 70 | _paused(false), 71 | _zeroMoment(0.0), 72 | _channel(0), 73 | _gateChoice(0), 74 | _combosInitialized(false), 75 | _alongBeam(false), 76 | _nextIQ(0), 77 | _gates(0), 78 | _capture(true), 79 | _saveDir(saveDir), 80 | _sampleRateHz(10.0e6) 81 | { 82 | // Set up our form 83 | setupUi(this); 84 | 85 | // Let's be reasonable with the refresh rate. 86 | if (refreshRateHz < 1.0) { 87 | refreshRateHz = 1.0; 88 | } 89 | 90 | // initialize running statistics 91 | for (int i = 0; i < 3; i++) { 92 | // _pulseCount[i] = 0; 93 | // _prevPulseCount[i] = 0; 94 | _errorCount[i] = 0; 95 | _lastPulseNum[i] = 0; 96 | } 97 | 98 | // create a button group for the channels 99 | _chanButtonGroup = new QButtonGroup; 100 | 101 | // connect the controls 102 | connect(_autoScale, SIGNAL(released()), this, SLOT(autoScaleSlot())); 103 | connect(_gainKnob, SIGNAL(valueChanged(double)), this, SLOT(gainChangeSlot(double))); 104 | connect(_up, SIGNAL(released()), this, SLOT(upSlot())); 105 | connect(_dn, SIGNAL(released()), this, SLOT(dnSlot())); 106 | connect(_saveImage, SIGNAL(released()), this, SLOT(saveImageSlot())); 107 | connect(_pauseButton, SIGNAL(toggled(bool)), this, SLOT(pauseSlot(bool))); 108 | connect(_windowButton, SIGNAL(toggled(bool)), this, SLOT(windowSlot(bool))); 109 | connect(_gateNumber, SIGNAL(valueChanged(int)), this, SLOT(gateChoiceSlot(int))); 110 | connect(_alongBeamCheck, SIGNAL(toggled(bool)), this, SLOT(alongBeamSlot(bool))); 111 | connect(_blockSizeCombo, SIGNAL(activated(int)), this, SLOT(blockSizeSlot(int))); 112 | connect(_chanButtonGroup, SIGNAL(buttonReleased(int)), this, SLOT(channelSlot(int))); 113 | 114 | connect(_xGrid, SIGNAL(toggled(bool)), _scopePlot, SLOT(enableXgrid(bool))); 115 | connect(_yGrid, SIGNAL(toggled(bool)), _scopePlot, SLOT(enableYgrid(bool))); 116 | 117 | // set the checkbox selections 118 | _pauseButton->setChecked(false); 119 | _xGrid->setChecked(true); 120 | _yGrid->setChecked(true); 121 | 122 | // initialize the book keeping for the plots. 123 | // This also sets up the radio buttons 124 | // in the plot type tab widget 125 | initPlots(); 126 | 127 | _gainKnob->setRange(-7, 7); 128 | _gainKnob->setTitle("Gain"); 129 | 130 | // set the minor ticks 131 | _gainKnob->setScaleMaxMajor(5); 132 | _gainKnob->setScaleMaxMinor(5); 133 | 134 | // initialize the activity bar 135 | _activityBar->setRange(0, 100); 136 | _activityBar->setValue(0); 137 | 138 | _xyGraphRange = 1; 139 | _xyGraphCenter = 0.0; 140 | _knobGain = 0.0; 141 | _knobOffset = 0.0; 142 | _specGraphRange = 120.0; 143 | _specGraphCenter = -40.0; 144 | 145 | // set up the palettes 146 | _greenPalette = this->palette(); 147 | _greenPalette.setColor(this->backgroundRole(), QColor("green")); 148 | _redPalette = _greenPalette; 149 | _redPalette.setColor(this->backgroundRole(), QColor("red")); 150 | 151 | // The initial plot type will be I and Q timeseries 152 | plotTypeSlot(TS_IANDQ_PLOT); 153 | 154 | 155 | // start the statistics timer 156 | int interval = (int)(1000/_refreshIntervalHz); 157 | if (interval < 1) { 158 | // require at least 1 ms pauses. 159 | interval = 1; 160 | } 161 | startTimer(interval); 162 | 163 | // let the data sources get themselves ready 164 | sleep(1); 165 | 166 | } 167 | ////////////////////////////////////////////////////////////////////// 168 | AScope::~AScope() { 169 | } 170 | 171 | ////////////////////////////////////////////////////////////////////// 172 | void AScope::initCombos(int channels, int gates) { 173 | 174 | // initialize the fft numerics 175 | initBlockSizes(); 176 | 177 | // initialize the number of gates. 178 | initGates(gates); 179 | 180 | // initialize the channels 181 | initChans(channels); 182 | } 183 | 184 | ////////////////////////////////////////////////////////////////////// 185 | void AScope::initGates(int gates) { 186 | // populate the gate selection spin box 187 | _gateNumber->setMinimum(0); 188 | _gateNumber->setMaximum(gates-1); 189 | } 190 | 191 | ////////////////////////////////////////////////////////////////////// 192 | void AScope::initChans(int channels) { 193 | 194 | // create the channel seletion radio buttons. 195 | 196 | QVBoxLayout *vbox = new QVBoxLayout; 197 | _chanBox->setLayout(vbox); 198 | 199 | for (int c = 0; c < channels; c++) { 200 | // create the button and add to the layout 201 | QString l = QString("Chan %1").arg(c); 202 | QRadioButton* r = new QRadioButton(l); 203 | vbox->addWidget(r); 204 | // add it to the button group, with the channel 205 | // number as the id 206 | _chanButtonGroup->addButton(r, c); 207 | // select the first button 208 | if (c == 0) { 209 | r->setChecked(true); 210 | _channel = 0; 211 | } else { 212 | r->setChecked(false); 213 | } 214 | } 215 | } 216 | ////////////////////////////////////////////////////////////////////// 217 | void AScope::initBlockSizes() { 218 | 219 | // configure the block/fft size selection 220 | /// @todo add logic to insure that smallest fft size is a power of two. 221 | int fftSize = 8; 222 | int maxFftSize = 4096; 223 | for (; fftSize <= maxFftSize; fftSize = fftSize*2) { 224 | _blockSizeChoices.push_back(fftSize); 225 | QString l = QString("%1").arg(fftSize); 226 | _blockSizeCombo->addItem(l, QVariant(fftSize)); 227 | } 228 | 229 | // initialize items that depend on the block size selection 230 | // (fftw and hamming coefficients) 231 | _blockSizeCombo->setCurrentIndex(5); 232 | blockSizeSlot(5); 233 | 234 | } 235 | 236 | ////////////////////////////////////////////////////////////////////// 237 | void AScope::initFFT(int size) { 238 | 239 | // return existing structures, if we have them 240 | if (_fftwData) { 241 | fftw_destroy_plan(_fftwPlan); 242 | fftw_free(_fftwData); 243 | } 244 | 245 | // allocate space 246 | _fftwData = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)* size); 247 | 248 | // create the plan. 249 | _fftwPlan = fftw_plan_dft_1d(size, _fftwData, _fftwData, 250 | FFTW_FORWARD, 251 | FFTW_ESTIMATE); 252 | 253 | hammingSetup(size); 254 | } 255 | 256 | 257 | ////////////////////////////////////////////////////////////////////// 258 | void AScope::saveImageSlot() { 259 | QString f = _saveDir.c_str(); 260 | 261 | QFileDialog d( this, tr("Save AScope Image"), f, 262 | tr("PNG files (*.png);;All files (*.*)")); 263 | d.setFileMode(QFileDialog::AnyFile); 264 | d.setViewMode(QFileDialog::Detail); 265 | d.setAcceptMode(QFileDialog::AcceptSave); 266 | d.setOption(QFileDialog::DontConfirmOverwrite, false); 267 | d.setDefaultSuffix("png"); 268 | d.setDirectory(f); 269 | 270 | f = "AScope-"; 271 | f += QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss"); 272 | f += ".png"; 273 | d.selectFile(f); 274 | if (d.exec()) { 275 | QStringList saveNames = d.selectedFiles(); 276 | _scopePlot->saveImageToFile(saveNames[0].toStdString()); 277 | f = d.directory().absolutePath(); 278 | _saveDir = f.toStdString(); 279 | } 280 | } 281 | ////////////////////////////////////////////////////////////////////// 282 | void AScope::processTimeSeries( 283 | std::vector& Idata, 284 | std::vector& Qdata) { 285 | 286 | // if we are not plotting time series, ignore 287 | if (!_IQplot) 288 | return; 289 | 290 | switch (_tsPlotType) { 291 | // power spectrum plot 292 | case TS_SPECTRUM_PLOT: { 293 | // compute the power spectrum 294 | _zeroMoment = powerSpectrum(Idata, Qdata); 295 | displayData(); 296 | break; 297 | } 298 | // I Q in time or I versus Q 299 | case TS_AMPLITUDE_PLOT: 300 | Y.resize(Idata.size()); 301 | for (unsigned int i = 0; i < Y.size(); i++) { 302 | Y[i] = sqrt(Idata[i]*Idata[i] + Qdata[i]*Qdata[i]); 303 | } 304 | _zeroMoment = zeroMomentFromTimeSeries(Idata, Qdata); 305 | displayData(); 306 | break; 307 | case TS_POWER_PLOT: 308 | Y.resize(Idata.size()); 309 | for (unsigned int i = 0; i < Y.size(); i++) { 310 | double sq = Idata[i]*Idata[i] + Qdata[i]*Qdata[i]; 311 | Y[i] = 10 * log10(std::max(sq, 1e-12)); 312 | } 313 | _zeroMoment = zeroMomentFromTimeSeries(Idata, Qdata); 314 | displayData(); 315 | break; 316 | case TS_IVSQ_PLOT: 317 | case TS_IANDQ_PLOT:{ 318 | I.resize(Idata.size()); 319 | Q.resize(Qdata.size()); 320 | I = Idata; 321 | Q = Qdata; 322 | _zeroMoment = zeroMomentFromTimeSeries(Idata, Qdata); 323 | displayData(); 324 | break; 325 | } 326 | default: 327 | // ignore others 328 | break; 329 | } 330 | } 331 | 332 | ////////////////////////////////////////////////////////////////////// 333 | void AScope::displayData() { 334 | double yBottom = _xyGraphCenter - _xyGraphRange; 335 | double yTop = _xyGraphCenter + _xyGraphRange; 336 | 337 | QString l = QString("%1").arg(_zeroMoment, 6, 'f', 1); 338 | _powerDB->setText(l); 339 | 340 | // Time series data display 341 | PlotInfo* pi = &_tsPlotInfo[_tsPlotType]; 342 | 343 | std::string xlabel; 344 | TS_PLOT_TYPES displayType = 345 | (TS_PLOT_TYPES) pi->getDisplayType(); 346 | switch (displayType) { 347 | case TS_AMPLITUDE_PLOT: 348 | if (pi->autoscale()) { 349 | autoScale(Y, displayType); 350 | pi->autoscale(false); 351 | } 352 | xlabel = std::string("Time"); 353 | _scopePlot->TimeSeries(Y, yBottom, yTop, 1, xlabel, "Amplitude"); 354 | break; 355 | case TS_POWER_PLOT: 356 | if (pi->autoscale()) { 357 | autoScale(Y, displayType); 358 | pi->autoscale(false); 359 | } 360 | xlabel = std::string("Time"); 361 | _scopePlot->TimeSeries(Y, yBottom, yTop, 1, xlabel, "Power"); 362 | break; 363 | case TS_IANDQ_PLOT: 364 | if (pi->autoscale()) { 365 | autoScale(I, Q, displayType); 366 | pi->autoscale(false); 367 | } 368 | xlabel = std::string("Time"); 369 | _scopePlot->IandQ(I, Q, yBottom, yTop, 1, xlabel, "I - Q"); 370 | break; 371 | case TS_IVSQ_PLOT: 372 | if (pi->autoscale()) { 373 | autoScale(I, Q, displayType); 374 | pi->autoscale(false); 375 | } 376 | _scopePlot->IvsQ(I, Q, yBottom, yTop, 1, "I", "Q"); 377 | break; 378 | case TS_SPECTRUM_PLOT: 379 | if (pi->autoscale()) { 380 | autoScale(_spectrum, displayType); 381 | pi->autoscale(false); 382 | } 383 | _scopePlot->Spectrum( 384 | _spectrum, 385 | _specGraphCenter -_specGraphRange/2.0, 386 | _specGraphCenter +_specGraphRange/2.0, 387 | _sampleRateHz, 388 | false, 389 | "Frequency (Hz)", 390 | "Power (dB)"); 391 | break; 392 | } 393 | } 394 | 395 | ////////////////////////////////////////////////////////////////////// 396 | double AScope::powerSpectrum( 397 | std::vector& Idata, 398 | std::vector& Qdata) { 399 | 400 | _spectrum.resize(_blockSize); 401 | 402 | unsigned int n = (Idata.size() <_blockSize) ? Idata.size(): _blockSize; 403 | for (unsigned int j = 0; j < n; j++) { 404 | // transfer the data to the fftw input space 405 | _fftwData[j][0] = Idata[j]; 406 | _fftwData[j][1] = Qdata[j]; 407 | } 408 | // zero pad if necessary 409 | for (unsigned int j = n; j < _blockSize; j++) { 410 | _fftwData[j][0] = 0; 411 | _fftwData[j][1] = 0; 412 | } 413 | 414 | // apply the hamming window to the time series 415 | if (_doHamming) { 416 | doHamming(); 417 | } 418 | 419 | // caclulate the fft 420 | fftw_execute(_fftwPlan); 421 | 422 | double zeroMoment = 0.0; 423 | 424 | // reorder and copy the results into _spectrum 425 | 426 | int nHalf = _blockSize / 2; 427 | double nSq = (double) _blockSize * (double) _blockSize; 428 | 429 | for (unsigned int i = 0; i < _blockSize; i++) { 430 | 431 | double pow = 432 | _fftwData[i][0] * _fftwData[i][0] + 433 | _fftwData[i][1] * _fftwData[i][1]; 434 | 435 | zeroMoment += pow; 436 | 437 | pow /= nSq; 438 | pow = 10.0*log10(pow); 439 | _spectrum[(i + nHalf) % _blockSize] = pow; 440 | 441 | } // i 442 | 443 | zeroMoment /= nSq; 444 | zeroMoment = 10.0*log10(zeroMoment); 445 | 446 | return zeroMoment; 447 | } 448 | 449 | //////////////////////////////////////////////////////////////////// 450 | void AScope::plotTypeSlot(int /*plotType*/) { 451 | 452 | // find out the index of the current page 453 | int pageNum = _typeTab->currentIndex(); 454 | 455 | // get the radio button id of the currently selected button 456 | // on that page. 457 | int ptype = _tabButtonGroups[pageNum]->checkedId(); 458 | 459 | // change to a raw plot type 460 | TS_PLOT_TYPES tstype = (TS_PLOT_TYPES)ptype; 461 | plotTypeChange( &_tsPlotInfo[tstype], tstype); 462 | } 463 | 464 | ////////////////////////////////////////////////////////////////////// 465 | void AScope::tabChangeSlot(QWidget* /*w*/) { 466 | // find out the index of the current page 467 | int pageNum = _typeTab->currentIndex(); 468 | 469 | // get the radio button id of the currently selected button 470 | // on that page. 471 | int ptype = _tabButtonGroups[pageNum]->checkedId(); 472 | 473 | // change to a raw plot type 474 | TS_PLOT_TYPES plotType = (TS_PLOT_TYPES)ptype; 475 | plotTypeChange( &_tsPlotInfo[plotType], plotType); 476 | } 477 | 478 | //////////////////////////////////////////////////////////////////// 479 | void AScope::plotTypeChange( 480 | PlotInfo* pi, 481 | TS_PLOT_TYPES newPlotType) { 482 | 483 | // save the gain and offset of the current plot type 484 | PlotInfo* currentPi; 485 | currentPi = &_tsPlotInfo[_tsPlotType]; 486 | currentPi->setGain(pi->getGainMin(), pi->getGainMax(), _knobGain); 487 | currentPi->setOffset(pi->getOffsetMin(), pi->getOffsetMax(), _xyGraphCenter); 488 | 489 | // restore gain and offset for new plot type 490 | gainChangeSlot(pi->getGainCurrent()); 491 | _xyGraphCenter = pi->getOffsetCurrent(); 492 | 493 | // set the knobs for the new plot type 494 | _gainKnob->setValue(_knobGain); 495 | 496 | _tsPlotType = newPlotType; 497 | 498 | } 499 | 500 | //////////////////////////////////////////////////////////////////// 501 | void AScope::initPlots() { 502 | 503 | _pulsePlots.insert(TS_AMPLITUDE_PLOT); 504 | _pulsePlots.insert(TS_POWER_PLOT); 505 | _pulsePlots.insert(TS_IANDQ_PLOT); 506 | _pulsePlots.insert(TS_IVSQ_PLOT); 507 | _pulsePlots.insert(TS_SPECTRUM_PLOT); 508 | 509 | _tsPlotInfo[TS_AMPLITUDE_PLOT] = PlotInfo(1, TS_AMPLITUDE_PLOT, "I and Q", "Amplitude", -5.0, 5.0, 0.0, -5.0, 5.0, 0.0); 510 | _tsPlotInfo[TS_POWER_PLOT] = PlotInfo(1, TS_POWER_PLOT, "I and Q", "Power", -5.0, 5.0, 0.0, -5.0, 5.0, 0.0); 511 | _tsPlotInfo[TS_IANDQ_PLOT] = PlotInfo(2, TS_IANDQ_PLOT, "I and Q", "I and Q", -5.0, 5.0, 0.0, -5.0, 5.0, 0.0); 512 | _tsPlotInfo[TS_IVSQ_PLOT] = PlotInfo(3, TS_IVSQ_PLOT, "I vs Q", "I vs Q", -5.0, 5.0, 0.0, -5.0, 5.0, 0.0); 513 | _tsPlotInfo[TS_SPECTRUM_PLOT] = PlotInfo(4, TS_SPECTRUM_PLOT, "Power Spectrum", "Power Spectrum", -5.0, 5.0, 0.0, -5.0, 5.0, 0.0); 514 | 515 | // remove the one tab that was put there by designer 516 | _typeTab->removeTab(0); 517 | 518 | // add tabs, and save the button group for 519 | // for each tab. This code is here to support 520 | // addition of new tabs for grouping display types, 521 | // such as an I and Q tab, a Products tab, etc. 522 | // Right now it is only creating an I and Q tab. 523 | QButtonGroup* pGroup; 524 | 525 | pGroup = addTSTypeTab("I & Q", _pulsePlots); 526 | _tabButtonGroups.push_back(pGroup); 527 | 528 | connect(_typeTab, SIGNAL(currentChanged(QWidget *)), 529 | this, SLOT(tabChangeSlot(QWidget*))); 530 | } 531 | 532 | ////////////////////////////////////////////////////////////////////// 533 | QButtonGroup* AScope::addTSTypeTab( 534 | std::string tabName, 535 | std::set types) { 536 | // The page that will be added to the tab widget 537 | QWidget* pPage = new QWidget; 538 | // the layout manager for the page, will contain the buttons 539 | QVBoxLayout* pVbox = new QVBoxLayout; 540 | // the button group manager, which has nothing to do with rendering 541 | QButtonGroup* pGroup = new QButtonGroup; 542 | 543 | std::set::iterator i; 544 | 545 | for (i = types.begin(); i != types.end(); i++) { 546 | // create the radio button 547 | int id = _tsPlotInfo[*i].getDisplayType(); 548 | QRadioButton* pRadio = new QRadioButton; 549 | const QString label = _tsPlotInfo[*i].getLongName().c_str(); 550 | pRadio->setText(label); 551 | 552 | // put the button in the button group 553 | pGroup->addButton(pRadio, id); 554 | // assign the button to the layout manager 555 | pVbox->addWidget(pRadio); 556 | 557 | // set the first radio button of the group 558 | // to be selected. 559 | if (i == types.begin()) { 560 | pRadio->setChecked(true); 561 | } 562 | } 563 | // associate the layout manager with the page 564 | pPage->setLayout(pVbox); 565 | 566 | // put the page on the tab 567 | _typeTab->insertTab(-1, pPage, tabName.c_str()); 568 | 569 | // connect the button released signal to our plot type change slot. 570 | connect(pGroup, SIGNAL(buttonReleased(int)), this, SLOT(plotTypeSlot(int))); 571 | 572 | return pGroup; 573 | } 574 | 575 | ////////////////////////////////////////////////////////////////////// 576 | void AScope::timerEvent(QTimerEvent*) { 577 | _capture = true; 578 | } 579 | 580 | ////////////////////////////////////////////////////////////////////// 581 | void AScope::gainChangeSlot( 582 | double gain) { 583 | 584 | // keep a local copy of the gain knob value 585 | _knobGain = gain; 586 | 587 | _specGraphRange = pow(10.0, gain+2.0); 588 | 589 | _xyGraphRange = pow(10.0, -gain); 590 | 591 | _gainKnob->setValue(gain); 592 | 593 | } 594 | 595 | ////////////////////////////////////////////////////////////////////// 596 | void AScope::upSlot() { 597 | bool spectrum = false; 598 | 599 | if (_IQplot) { 600 | PlotInfo* pi = &_tsPlotInfo[_tsPlotType]; 601 | if (pi->getDisplayType() == ScopePlot::SPECTRUM) { 602 | spectrum = true; 603 | } 604 | } 605 | 606 | if (!spectrum) { 607 | _xyGraphCenter -= 0.03*_xyGraphRange; 608 | } else { 609 | _specGraphCenter -= 0.03*_specGraphRange; 610 | } 611 | displayData(); 612 | } 613 | 614 | ////////////////////////////////////////////////////////////////////// 615 | void AScope::dnSlot() { 616 | 617 | bool spectrum = false; 618 | 619 | if (_IQplot) { 620 | PlotInfo* pi = &_tsPlotInfo[_tsPlotType]; 621 | if (pi->getDisplayType() == ScopePlot::SPECTRUM) { 622 | spectrum = true; 623 | } 624 | } 625 | 626 | if (!spectrum) { 627 | _xyGraphCenter += 0.03*_xyGraphRange; 628 | } else { 629 | _specGraphCenter += 0.03*_specGraphRange; 630 | } 631 | 632 | displayData(); 633 | } 634 | 635 | ////////////////////////////////////////////////////////////////////// 636 | void AScope::autoScale( 637 | std::vector& data, 638 | AScope::TS_PLOT_TYPES displayType) { 639 | 640 | if (data.size() == 0) 641 | return; 642 | 643 | // find the min and max 644 | double min = *std::min_element(data.begin(), data.end()); 645 | double max = *std::max_element(data.begin(), data.end()); 646 | 647 | // adjust the gains 648 | adjustGainOffset(min, max, displayType); 649 | } 650 | 651 | ////////////////////////////////////////////////////////////////////// 652 | void AScope::autoScale( 653 | std::vector& data1, 654 | std::vector& data2, 655 | AScope::TS_PLOT_TYPES displayType) { 656 | 657 | if (data1.size() == 0 || data2.size() == 0) 658 | return; 659 | 660 | // find the min and max 661 | double min1 = *std::min_element(data1.begin(), data1.end()); 662 | double min2 = *std::min_element(data2.begin(), data2.end()); 663 | double min = std::min(min1, min2); 664 | 665 | double max1 = *std::max_element(data1.begin(), data1.end()); 666 | double max2 = *std::max_element(data2.begin(), data2.end()); 667 | double max = std::max(max1, max2); 668 | 669 | if (displayType == TS_IANDQ_PLOT || displayType == TS_IVSQ_PLOT) { 670 | max = std::max(-min, max); 671 | min = -max; 672 | } 673 | 674 | // adjust the gains 675 | adjustGainOffset(min, max, displayType); 676 | } 677 | 678 | ////////////////////////////////////////////////////////////////////// 679 | void AScope::adjustGainOffset( 680 | double min, 681 | double max, 682 | AScope::TS_PLOT_TYPES displayType) { 683 | 684 | if (displayType == TS_SPECTRUM_PLOT) { 685 | // currently in spectrum plot mode 686 | _specGraphCenter = min + (max-min)/2.0; 687 | _specGraphRange = 3*(max-min); 688 | _knobGain = -log10(_specGraphRange); 689 | } else { 690 | double factor = 0.8; 691 | _xyGraphCenter = (min+max)/2.0; 692 | _xyGraphRange = (1/factor)*(max - min)/2.0; 693 | if (min == max || 694 | isnan(min) || 695 | isnan(max) || 696 | isinf(min) || 697 | isinf(max)) 698 | _xyGraphRange = 1.0; 699 | //std::cout << "min:"<setValue(_knobGain); 702 | } 703 | } 704 | 705 | ////////////////////////////////////////////////////////////////////// 706 | void 707 | AScope::newTSItemSlot(AScope::TimeSeries pItem) { 708 | 709 | int chanId = pItem.chanId; 710 | int tsLength = pItem.IQbeams.size(); 711 | _gates = pItem.gates; 712 | _sampleRateHz = pItem.sampleRateHz; 713 | 714 | if (!_combosInitialized) { 715 | // initialize the combo selectors 716 | initCombos(4, _gates); 717 | _combosInitialized = true; 718 | 719 | } 720 | 721 | if (chanId == _channel && !_paused && _capture) { 722 | // extract the time series from the DDS sample 723 | if (_alongBeam) { 724 | _I.resize(_gates); 725 | _Q.resize(_gates); 726 | _nextIQ = 0; 727 | for (int i = 0; i < _gates; i++) { 728 | _I[_nextIQ] = pItem.i(0, _nextIQ); 729 | _Q[_nextIQ] = pItem.q(0, _nextIQ); 730 | _nextIQ++; 731 | } 732 | } else { 733 | for (int t = 0; t < tsLength; t++) { 734 | _I[_nextIQ] = pItem.i(t, _gateChoice); 735 | _Q[_nextIQ] = pItem.q(t, _gateChoice); 736 | _nextIQ++; 737 | if (_nextIQ == _I.size()) { 738 | break; 739 | } 740 | } 741 | } 742 | 743 | // now see if we have collected enough samples 744 | if (_nextIQ == _I.size()) { 745 | // process the time series 746 | processTimeSeries(_I, _Q); 747 | _nextIQ = 0; 748 | _capture = false; 749 | } 750 | } 751 | 752 | // return the DDS item 753 | emit returnTSItem(pItem); 754 | 755 | // bump the activity bar 756 | _activityBar->setValue((_activityBar->value()+1) % 100); 757 | } 758 | 759 | ////////////////////////////////////////////////////////////////////// 760 | void AScope::autoScaleSlot() { 761 | PlotInfo* pi; 762 | 763 | pi = &_tsPlotInfo[_tsPlotType]; 764 | 765 | pi->autoscale(true); 766 | } 767 | 768 | ////////////////////////////////////////////////////////////////////// 769 | void AScope::pauseSlot( 770 | bool p) { 771 | _paused = p; 772 | } 773 | 774 | ////////////////////////////////////////////////////////////////////// 775 | void AScope::channelSlot(int c) { 776 | _channel = c; 777 | } 778 | 779 | ////////////////////////////////////////////////////////////////////// 780 | void AScope::gateChoiceSlot(int index) { 781 | _gateChoice = index; 782 | } 783 | 784 | ////////////////////////////////////////////////////////////////////// 785 | void AScope::blockSizeSlot(int index) { 786 | unsigned int size = _blockSizeChoices[index]; 787 | 788 | // Reconfigure fftw if the size has changed 789 | if (size != _blockSize) { 790 | initFFT(size); 791 | // save the size 792 | _blockSize = size; 793 | } 794 | 795 | // If not in alongBeam mode, reconfigure _I and _Q capture 796 | if (!_alongBeam) { 797 | _I.resize(_blockSize); 798 | _Q.resize(_blockSize); 799 | _nextIQ = 0; 800 | } 801 | } 802 | 803 | //////////////////////////////////////////////////////////////////////// 804 | double AScope::zeroMomentFromTimeSeries( 805 | std::vector& I, 806 | std::vector& Q) { 807 | double p = 0; 808 | int n = I.size(); 809 | 810 | for (unsigned int i = 0; i < I.size(); i++) { 811 | p += I[i]*I[i] + Q[i]*Q[i]; 812 | } 813 | 814 | p /= n; 815 | p = 10.0*log10(p); 816 | return p; 817 | } 818 | 819 | //////////////////////////////////////////////////////////////////////// 820 | void 821 | AScope::doHamming() { 822 | 823 | for (unsigned int i = 0; i < _blockSize; i++) { 824 | _fftwData[i][0] *= _hammingCoefs[i]; 825 | _fftwData[i][1] *= _hammingCoefs[i]; 826 | } 827 | } 828 | 829 | //////////////////////////////////////////////////////////////////////// 830 | void 831 | AScope::hammingSetup(int size) { 832 | 833 | _hammingCoefs.resize(size); 834 | 835 | for (int i = 0; i < size; i++) { 836 | _hammingCoefs[i] = 0.54 - 0.46*(cos(2.0*M_PI*i/(size-1))); 837 | } 838 | 839 | } 840 | 841 | //////////////////////////////////////////////////////////////////////// 842 | void 843 | AScope::windowSlot(bool flag) { 844 | _doHamming = flag; 845 | } 846 | 847 | //////////////////////////////////////////////////////////////////////// 848 | void 849 | AScope::alongBeamSlot(bool flag) { 850 | _alongBeam = flag; 851 | 852 | // If changing into alongBeam mode, set _I and _Q 853 | // to the number of gates. Otherwise, set them to the 854 | // blocksize. 855 | if (_alongBeam) { 856 | _I.resize(_gates); 857 | _Q.resize(_gates); 858 | _nextIQ = 0; 859 | _gateNumber->setEnabled(false); 860 | } else { 861 | _I.resize(_blockSize); 862 | _Q.resize(_blockSize); 863 | _gateNumber->setEnabled(true); 864 | } 865 | _nextIQ = 0; 866 | } 867 | 868 | //////////////////////////////////////////////////////////////////////// 869 | AScope::TimeSeries::TimeSeries(): 870 | dataType(VOIDDATA) 871 | { 872 | } 873 | 874 | //////////////////////////////////////////////////////////////////////// 875 | AScope::TimeSeries::TimeSeries(TsDataTypeEnum type): 876 | dataType(type) 877 | { 878 | sampleRateHz = 10.0e6; 879 | } 880 | 881 | //////////////////////////////////////////////////////////////////////// 882 | double AScope::TimeSeries::i(int pulse, int gate) const { 883 | switch (dataType) { 884 | case FLOATDATA: 885 | return(static_cast(IQbeams[pulse])[2 * gate]); 886 | case SHORTDATA: 887 | return(static_cast(IQbeams[pulse])[2 * gate]); 888 | default: 889 | std::cerr << "Attempt to extract data from " << 890 | "AScope::TimeSeries with data type unset!" << std::endl; 891 | abort(); 892 | } 893 | } 894 | 895 | //////////////////////////////////////////////////////////////////////// 896 | double AScope::TimeSeries::q(int pulse, int gate) const { 897 | switch (dataType) { 898 | case FLOATDATA: 899 | return(static_cast(IQbeams[pulse])[2 * gate + 1]); 900 | case SHORTDATA: 901 | return(static_cast(IQbeams[pulse])[2 * gate + 1]); 902 | default: 903 | std::cerr << "Attempt to extract data from " << 904 | "AScope::TimeSeries with data type unset!" << std::endl; 905 | abort(); 906 | } 907 | } 908 | 909 | ////////////////////////////////////////////////////////////////////// 910 | QFrame* AScope::userFrame() { 911 | return _userFrame; 912 | } 913 | 914 | 915 | --------------------------------------------------------------------------------