LCOV - code coverage report
Current view: top level - imageanalysis/IO - RegionTextParser.cc (source / functions) Hit Total Coverage
Test: ctest_coverage.info Lines: 629 872 72.1 %
Date: 2023-11-06 10:06:49 Functions: 26 27 96.3 %

          Line data    Source code
       1             : //# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       2             : //# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
       3             : //# License for more details.
       4             : //#
       5             : //# You should have received a copy of the GNU Library General Public License
       6             : //# along with this library; if not, write to the Free Software Foundation,
       7             : //# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
       8             : //#
       9             : //# Correspondence concerning AIPS++ should be addressed as follows:
      10             : //#        Internet email: aips2-request@nrao.edu.
      11             : //#        Postal address: AIPS++ Project Office
      12             : //#                        National Radio Astronomy Observatory
      13             : //#                        520 Edgemont Road
      14             : //#                        Charlottesville, VA 22903-2475 USA
      15             : //#
      16             : 
      17             : #include <imageanalysis/IO/RegionTextParser.h>
      18             : 
      19             : #include <casacore/casa/IO/RegularFileIO.h>
      20             : #include <casacore/coordinates/Coordinates/DirectionCoordinate.h>
      21             : #include <casacore/coordinates/Coordinates/SpectralCoordinate.h>
      22             : #include <imageanalysis/Annotations/AnnAnnulus.h>
      23             : #include <imageanalysis/Annotations/AnnCenterBox.h>
      24             : #include <imageanalysis/Annotations/AnnCircle.h>
      25             : #include <imageanalysis/Annotations/AnnEllipse.h>
      26             : #include <imageanalysis/Annotations/AnnLine.h>
      27             : #include <imageanalysis/Annotations/AnnPolygon.h>
      28             : #include <imageanalysis/Annotations/AnnRectBox.h>
      29             : #include <imageanalysis/Annotations/AnnRotBox.h>
      30             : #include <imageanalysis/Annotations/AnnSymbol.h>
      31             : #include <imageanalysis/Annotations/AnnText.h>
      32             : #include <imageanalysis/Annotations/AnnVector.h>
      33             : #include <imageanalysis/IO/ParameterParser.h>
      34             : 
      35             : #include <casacore/measures/Measures/MCDirection.h>
      36             : #include <casacore/measures/Measures/MDirection.h>
      37             : #include <casacore/measures/Measures/VelocityMachine.h>
      38             : 
      39             : #include <iomanip>
      40             : #include <casacore/casa/BasicSL/STLIO.h>
      41             : 
      42             : #define _ORIGIN "RegionTextParser::" + String(__FUNCTION__) + ": "
      43             : 
      44             : using namespace casacore;
      45             : namespace casa {
      46             : 
      47             : const Int RegionTextParser::CURRENT_VERSION = 0;
      48             : const Regex RegionTextParser::MAGIC("^#CRTF");
      49             : 
      50             : const String RegionTextParser::sOnePair = "[[:space:]]*\\[[^\\[,]+,[^\\[,]+\\][[:space:]]*";
      51             : const String RegionTextParser::bTwoPair = "\\[" + sOnePair
      52             :         + "," + sOnePair;
      53             : // explicit onePair at the end because that one should not be followed by a comma
      54             : const String RegionTextParser::sNPair = "\\[(" + sOnePair
      55             :         + ",)+" + sOnePair + "\\]";
      56             : const Regex RegionTextParser::startOnePair("^" + sOnePair);
      57             : const Regex RegionTextParser::startNPair("^" + sNPair);
      58             : 
      59          34 : RegionTextParser::RegionTextParser(
      60             :     const String& filename, const CoordinateSystem& csys,
      61             :     const IPosition& imShape,
      62             :     const Int requireAtLeastThisVersion,
      63             :     const String& prependRegion,
      64             :     const String& globalOverrideChans, const String& globalOverrrideStokes,
      65             :     Bool verbose, bool requireImageRegion
      66          34 : ) : _csys(csys), _log(new LogIO()), _currentGlobals(),
      67             :     _lines(), _globalKeysToApply(), _fileVersion(-1), _imShape(imShape),
      68         108 :     _regions(0), _verbose(verbose) {
      69          68 :     RegularFile file(filename);
      70          34 :     if (! file.exists()) {
      71             :         throw AipsError(
      72           0 :             _ORIGIN + "File " + filename + " does not exist."
      73           0 :         );
      74             :     }
      75          34 :     if (! file.isReadable()) {
      76             :         throw AipsError(
      77           0 :             _ORIGIN + "File " + filename + " is not readable."
      78           0 :         );
      79             :     }
      80          34 :     if (! _csys.hasDirectionCoordinate()) {
      81             :         throw AipsError(
      82           0 :             _ORIGIN
      83           0 :             + "Coordinate system does not have a direction coordinate"
      84           0 :         );
      85             :     }
      86          34 :     _setInitialGlobals();
      87          34 :     _setOverridingChannelRange(globalOverrideChans);
      88          34 :     _setOverridingCorrelations(globalOverrrideStokes);
      89          68 :     RegularFileIO fileIO(file);
      90          34 :     Int bufSize = 4096;
      91          68 :     std::unique_ptr<char> buffer(new char[bufSize]);
      92             :     int nRead;
      93          68 :     String contents;
      94          34 :     if (! prependRegion.empty()) {
      95           0 :         contents = prependRegion + "\n";
      96             :     }
      97          34 :     while ((nRead = fileIO.read(bufSize, buffer.get( ), false)) == bufSize) {
      98           0 :         String chunk(buffer.get( ), bufSize);
      99           0 :         if (_fileVersion < 0) {
     100           0 :             _determineVersion(chunk, filename, requireAtLeastThisVersion);
     101             :         }
     102           0 :         contents += chunk;
     103             :     }
     104             :     // get the last chunk
     105          68 :     String chunk(buffer.get( ), nRead);
     106          34 :     if (_fileVersion < 0) {
     107          34 :         _determineVersion(chunk, filename, requireAtLeastThisVersion);
     108             :     }
     109          29 :     contents += chunk;
     110          29 :     _parse(contents, filename, requireImageRegion);
     111          29 : }
     112             : 
     113          40 : RegionTextParser::RegionTextParser(
     114             :     const CoordinateSystem& csys, const IPosition& imShape,
     115             :     const String& text, const String& prependRegion,
     116             :     const String& globalOverrideChans, const String& globalOverrrideStokes,
     117             :     Bool verbose, Bool requireImageRegion
     118          40 : ) : _csys(csys), _log(new LogIO()), _currentGlobals(), _lines(),
     119             :     _globalKeysToApply(), _fileVersion(-1), _imShape(imShape), _regions(0),
     120          88 :     _verbose(verbose) {
     121          40 :     if (! _csys.hasDirectionCoordinate()) {
     122             :         throw AipsError(
     123           0 :             _ORIGIN + "Coordinate system has no direction coordinate"
     124           0 :         );
     125             :     }
     126          40 :     _setInitialGlobals();
     127          40 :     _setOverridingChannelRange(globalOverrideChans);
     128          39 :     _setOverridingCorrelations(globalOverrrideStokes);
     129          76 :     _parse(prependRegion.empty() ? text : prependRegion + "\n" + text, "", requireImageRegion);
     130          39 : }
     131             : 
     132          68 : RegionTextParser::~RegionTextParser() {}
     133             : 
     134           0 : Int RegionTextParser::getFileVersion() const {
     135           0 :     ThrowIf(
     136             :         _fileVersion < 0,
     137             :         "File version not associated with simple text strings"
     138             :     );
     139           0 :     return _fileVersion;
     140             : }
     141             : 
     142          69 : vector<AsciiAnnotationFileLine> RegionTextParser::getLines() const {
     143          69 :     return _lines;
     144             : }
     145             : 
     146          34 : void RegionTextParser::_determineVersion(
     147             :     const String& chunk, const String& filename,
     148             :     const Int requireAtLeastThisVersion
     149             : ) {
     150          34 :     *_log << LogOrigin("RegionTextParser", __FUNCTION__);
     151          34 :     if (_fileVersion >= 0) {
     152             :         // already determined
     153           0 :         return;
     154             :     }
     155          64 :     ThrowIf(
     156             :         ! chunk.contains(MAGIC),
     157             :         _ORIGIN + "File " + filename
     158             :         + " does not contain CASA region text file magic value"
     159             :     );
     160          58 :     Regex version(MAGIC.regexp() + "v[0-9]+");
     161          29 :     if (chunk.contains(version)) {
     162          29 :         const auto vString = chunk.substr(6);
     163          29 :         auto done = false;
     164          29 :         auto count = 1;
     165          29 :         auto oldVersion = -2000;
     166          87 :         while (! done) {
     167             :             try {
     168          58 :                 _fileVersion = String::toInt(vString.substr(0, count));
     169          58 :                 ++count;
     170          58 :                 if (_fileVersion == oldVersion) {
     171          29 :                     done = true;
     172             :                 }
     173             :                 else {
     174          29 :                     oldVersion = _fileVersion;
     175             :                 }
     176             :             }
     177           0 :             catch (const AipsError&) {
     178           0 :                 done = true;
     179             :             }
     180             :         }
     181          29 :         if (_fileVersion < requireAtLeastThisVersion) {
     182           0 :             *_log << _ORIGIN << "File version " << _fileVersion
     183             :                 << " is less than required version "
     184           0 :                 << requireAtLeastThisVersion << LogIO::EXCEPTION;
     185             :         }
     186          29 :         if (_fileVersion > CURRENT_VERSION) {
     187           0 :             *_log << _ORIGIN << "File version " << _fileVersion
     188             :                 << " is greater than the most recent version of the spec ("
     189             :                 << CURRENT_VERSION
     190             :                 << "). Did you bring this file with you when you traveled "
     191             :                 << "here from the future perhaps? Unfortunately we don't "
     192           0 :                 << "support such possibilities yet." << LogIO::EXCEPTION;
     193             :         }
     194          87 :         *_log << LogIO::NORMAL << _ORIGIN << "Found spec version "
     195          58 :             << _fileVersion << LogIO::POST;
     196             :     }
     197             :     else {
     198           0 :         *_log << LogIO::WARN << _ORIGIN << "File " << filename
     199             :             << " does not contain a CASA Region Text File spec version. "
     200             :             << "The current spec version, " << CURRENT_VERSION << " will be assumed. "
     201             :             << "WE STRONGLY SUGGEST YOU INCLUDE THE SPEC VERSION IN THIS FILE TO AVOID "
     202             :             << "POSSIBLE FUTURE BACKWARD INCOMPATIBILTY ISSUES. Simply ensure the first line "
     203           0 :             << "of the file is '#CRTFv" << CURRENT_VERSION << "'" << LogIO::POST;
     204           0 :         _fileVersion = CURRENT_VERSION;
     205             :     }
     206             : }
     207             : 
     208          68 : void RegionTextParser::_parse(const String& contents, const String& fileDesc, bool requireImageRegion) {
     209          68 :     _log->origin(LogOrigin("AsciiRegionFileParser", __func__));
     210          68 :     static const Regex startAnn("^ann[[:space:]]+");
     211          68 :     static const Regex startDiff("^-[[:space:]]*");
     212          68 :     static const Regex startGlobal("^global[[:space:]]+");
     213          68 :     AnnotationBase::unitInit();
     214         136 :     auto lines = stringToVector(contents, '\n');
     215          68 :     uInt lineCount = 0;
     216             :     auto qFreqs = _overridingFreqRange
     217             :         ? std::pair<Quantity, Quantity>(
     218          70 :             Quantity(_overridingFreqRange->first.getValue().getValue(), "Hz"),
     219          72 :             Quantity(_overridingFreqRange->second.getValue().getValue(), "Hz")
     220             :         )
     221         274 :         : std::pair<Quantity, Quantity>(Quantity(0), Quantity(0));
     222         266 :     for(auto iter=lines.cbegin(); iter!=lines.cend(); ++iter) {
     223         198 :         ++lineCount;
     224         198 :         Bool annOnly = false;
     225         198 :         ostringstream preambleoss;
     226         198 :         preambleoss << fileDesc + " line# " << lineCount << ": ";
     227         198 :         const auto preamble = preambleoss.str();
     228         198 :         Bool difference = false;
     229         198 :         iter->trim();
     230         257 :         if (
     231         198 :             iter->empty() || iter->startsWith("#")
     232             :         ) {
     233             :             // ignore comments and blank lines
     234          59 :             _addLine(AsciiAnnotationFileLine(*iter));
     235          59 :             continue;
     236             :         }
     237         139 :         auto consumeMe = *iter;
     238             :         // consumeMe.downcase();
     239             :         Bool spectralParmsUpdated;
     240         139 :         ParamSet newParams;
     241         139 :         if (consumeMe.contains(startDiff)) {
     242           1 :             difference = true;
     243             :             // consume the difference character to allow further processing of string
     244           1 :             consumeMe.del(0, 1);
     245           1 :             consumeMe.trim();
     246           1 :             *_log << LogIO::NORMAL << preamble << "difference found" << LogIO::POST;
     247             :         }
     248         138 :         else if(consumeMe.contains(startAnn)) {
     249           0 :             annOnly = true;
     250             :             // consume the annotation chars
     251           0 :             consumeMe.del(0, 3);
     252           0 :             consumeMe.trim();
     253           0 :             *_log << LogIO::NORMAL << preamble << "annotation only found" << LogIO::POST;
     254             :         }
     255         138 :         else if(consumeMe.contains(startGlobal)) {
     256           0 :             consumeMe.del(0, 6);
     257           0 :             _currentGlobals = _getCurrentParamSet(
     258             :                 spectralParmsUpdated, newParams,
     259             :                 consumeMe, preamble
     260           0 :             );
     261           0 :             std::map<AnnotationBase::Keyword, String> gParms;
     262           0 :             for (const auto& p: newParams) {
     263           0 :                 gParms[p.first] = p.second.stringVal;
     264             :             }
     265           0 :             _addLine(AsciiAnnotationFileLine(gParms));
     266           0 :             if (
     267           0 :                 _csys.hasSpectralAxis() && spectralParmsUpdated
     268           0 :                 && newParams.find(AnnotationBase::RANGE) != newParams.end()
     269             :             ) {
     270           0 :                 qFreqs = _quantitiesFromFrequencyString(
     271           0 :                     newParams[AnnotationBase::RANGE].stringVal, preamble
     272           0 :                 );
     273             :             }
     274           0 :             *_log << LogIO::NORMAL << preamble << "global found" << LogIO::POST;
     275           0 :             continue;
     276             :         }
     277             :         // now look for per-line shapes and annotations
     278         278 :         Vector<Quantity> qDirs;
     279         278 :         vector<Quantity> quantities;
     280         278 :         String textString;
     281         139 :         const auto annType = _getAnnotationType(
     282             :             qDirs, quantities, textString, consumeMe, preamble
     283             :         );
     284             :         ParamSet currentParamSet = _getCurrentParamSet(
     285             :             spectralParmsUpdated, newParams, consumeMe, preamble
     286         278 :         );
     287         139 :         if (
     288         139 :             newParams.find(AnnotationBase::LABEL) == newParams.end()
     289         139 :             || newParams[AnnotationBase::LABEL].stringVal.empty()
     290             :         ) {
     291         137 :             if (newParams.find(AnnotationBase::LABELCOLOR) != newParams.end()) {
     292           0 :                 *_log << LogIO::WARN << preamble
     293             :                     << "Ignoring labelcolor because there is no associated label specified"
     294           0 :                     << LogIO::POST;
     295             :             }
     296         137 :             if (newParams.find(AnnotationBase::LABELPOS) != newParams.end()) {
     297           0 :                 *_log << LogIO::WARN << preamble
     298             :                     << "Ignoring labelpos because there is no associated label specified"
     299           0 :                     << LogIO::POST;
     300             :             }
     301         137 :             if (newParams.find(AnnotationBase::LABELOFF) != newParams.end()) {
     302           0 :                 *_log << LogIO::WARN << preamble
     303             :                     << "Ignoring labeloff because there is no associated label specified"
     304           0 :                     << LogIO::POST;
     305             :             }
     306             :         }
     307           2 :         else if (newParams.find(AnnotationBase::LABELCOLOR) == newParams.end()) {
     308             :             // if a label is specified but no label color is specified, the labelcolor
     309             :             // is to be the same as the annotation color
     310           2 :             newParams[AnnotationBase::LABELCOLOR] = newParams[AnnotationBase::COLOR];
     311             :         }
     312         139 :         if (_csys.hasSpectralAxis()) {
     313         139 :             if(spectralParmsUpdated) {
     314          24 :                 qFreqs = _quantitiesFromFrequencyString(
     315          16 :                     currentParamSet[AnnotationBase::RANGE].stringVal, preamble
     316           8 :                 );
     317             :             }
     318         131 :             else if(
     319         131 :                 _currentGlobals.find(AnnotationBase::RANGE)
     320         262 :                 == _currentGlobals.end()
     321         131 :                 || ! _currentGlobals.at(AnnotationBase::RANGE).freqRange
     322             :             ) {
     323             :                 // no global frequency range, so use entire freq span
     324         128 :                 qFreqs = std::pair<Quantity, Quantity>(Quantity(0), Quantity(0));
     325             :             }
     326             :         }
     327         139 :         auto globalsLessLocal = _currentGlobals;
     328         124 :         for (
     329         139 :             auto iter=newParams.cbegin();
     330         387 :             iter != newParams.cend(); ++iter
     331             :         ) {
     332         124 :             AnnotationBase::Keyword key = iter->first;
     333         124 :             if (globalsLessLocal.find(key) != globalsLessLocal.end()) {
     334         122 :                 globalsLessLocal.erase(key);
     335             :             }
     336             :         }
     337         139 :         _globalKeysToApply.resize(globalsLessLocal.size(), false);
     338         139 :         uInt i = 0;
     339        2380 :         for (
     340         139 :             ParamSet::const_iterator iter=globalsLessLocal.begin();
     341        2519 :             iter != globalsLessLocal.end(); ++iter
     342             :         ) {
     343        2380 :             _globalKeysToApply[i] = iter->first;
     344        2380 :             ++i;
     345             :         }
     346         139 :         _createAnnotation(
     347             :             annType, qDirs, qFreqs, quantities, textString,
     348             :             currentParamSet, annOnly, difference, preamble,
     349             :             requireImageRegion
     350             :         );
     351             :     }
     352          68 :     if (_verbose) {
     353          68 :         *_log << LogIO::NORMAL << "Combined " << _regions
     354          68 :             << " image regions (which excludes any annotation regions)" << LogIO::POST;
     355          68 :         if (_regions > 0) {
     356          68 :             *_log << LogIO::NORMAL << "The specified region will select all pixels that are "
     357             :                 << "included in the region. Full pixels will be included even when they are "
     358          68 :                 << "only partially covered by the region(s)." << LogIO::POST;
     359             :         }
     360             :     }
     361          68 : }
     362             : 
     363         198 : void RegionTextParser::_addLine(const AsciiAnnotationFileLine& line) {
     364         198 :     _lines.push_back(line);
     365         198 : }
     366             : 
     367         139 : AnnotationBase::Type RegionTextParser::_getAnnotationType(
     368             :     Vector<Quantity>& qDirs,
     369             :     vector<Quantity>& quantities,
     370             :     String& textString,
     371             :     String& consumeMe, const String& preamble
     372             : ) const {
     373             :     const static String sOnePairOneSingle =
     374         139 :         "\\[" + sOnePair + ",[^\\[,]+\\]";
     375             :     const static String sOnePairAndText =
     376         139 :         "\\[" + sOnePair + ",[[:space:]]*[\"\'].*[\"\'][[:space:]]*\\]";
     377         139 :     const static String sTwoPair = bTwoPair + "\\]";
     378         139 :     const static Regex startTwoPair("^" + sTwoPair);
     379         139 :     const static Regex startOnePairAndText("^" + sOnePairAndText);
     380             :     const static String sTwoPairOneSingle = bTwoPair
     381         139 :         + ",[[:space:]]*[^\\[,]+[[:space:]]*\\]";
     382         139 :     const static Regex startTwoPairOneSingle("^" + sTwoPairOneSingle);
     383         139 :     const static Regex startOnePairOneSingle("^" + sOnePairOneSingle);
     384         139 :     consumeMe.trim();
     385         278 :     String tmp = consumeMe.through(Regex("[[:alpha:]]+"));
     386         139 :     consumeMe.del(0, (Int)tmp.length());
     387         139 :     consumeMe.trim();
     388         139 :     auto annotationType = AnnotationBase::typeFromString(tmp);
     389         139 :     std::pair<Quantity, Quantity> myPair;
     390         139 :     switch(annotationType) {
     391          21 :     case AnnotationBase::RECT_BOX:
     392          21 :         ThrowIf(
     393             :             ! consumeMe.contains(startTwoPair),
     394             :             preamble + "Illegal box specification "
     395             :         );
     396          21 :         qDirs = _extractNQuantityPairs(consumeMe, preamble);
     397             : 
     398          21 :         if (qDirs.size() != 4) {
     399             :             throw AipsError(preamble
     400           0 :                 + "rectangle box spec must contain exactly 2 direction pairs but it has "
     401           0 :                 + String::toString(qDirs.size())
     402           0 :             );
     403             :         }
     404          21 :         break;
     405           1 :     case AnnotationBase::CENTER_BOX:
     406           1 :         ThrowIf(
     407             :             ! consumeMe.contains(startTwoPair),
     408             :             preamble + "Illegal center box specification " + consumeMe
     409             :         );
     410           1 :         qDirs.resize(2);
     411           1 :         quantities.resize(2);
     412             :         {
     413           2 :             Vector<Quantity> qs = _extractNQuantityPairs(consumeMe, preamble);
     414           1 :             qDirs[0] = qs[0];
     415           1 :             qDirs[1] = qs[1];
     416           1 :             quantities[0] = qs[2];
     417           1 :             quantities[1] = qs[3];
     418             :         }
     419           1 :         break;
     420           0 :     case AnnotationBase::ROTATED_BOX:
     421           0 :         ThrowIf(
     422             :             ! consumeMe.contains(startTwoPairOneSingle),
     423             :             preamble + "Illegal rotated box specification " + consumeMe
     424             :         );
     425           0 :         qDirs.resize(2);
     426           0 :         quantities.resize(3);
     427             :         {
     428           0 :             Vector<Quantity> qs = _extractTwoQuantityPairsAndSingleQuantity(consumeMe, preamble);
     429           0 :             qDirs[0] = qs[0];
     430           0 :             qDirs[1] = qs[1];
     431           0 :             quantities[0] = qs[2];
     432           0 :             quantities[1] = qs[3];
     433           0 :             quantities[2] = qs[4];
     434             :         }
     435           0 :         break;
     436           1 :     case AnnotationBase::POLYGON:
     437             :         // Polygon definitions can be very long with many points.
     438             :         // Testing entire polygon string syntax causes regex seg fault.
     439           1 :         ThrowIf(
     440             :             ! (
     441             :                consumeMe.contains(Regex("^ *\\[ *\\["))
     442             :                && consumeMe.contains(Regex("\\] *\\]"))
     443             :             ), preamble + "Illegal polygon specification " + consumeMe
     444             :         );
     445             :         {
     446           2 :             Vector<Quantity> qs = _extractNQuantityPairs(consumeMe, preamble);
     447           1 :             qDirs.resize(qs.size());
     448           1 :             qDirs = qs;
     449             :         }
     450           1 :         break;
     451          26 :     case AnnotationBase::CIRCLE:
     452          26 :         ThrowIf(
     453             :             ! consumeMe.contains(startOnePairOneSingle),
     454             :             preamble + "Illegal circle specification " + consumeMe
     455             :         );
     456          26 :         qDirs.resize(2);
     457          26 :         quantities.resize(1);
     458             :         {
     459             :             Vector<Quantity> qs = _extractQuantityPairAndSingleQuantity(
     460             :                 consumeMe, preamble
     461          52 :             );
     462          26 :             qDirs[0] = qs[0];
     463          26 :             qDirs[1] = qs[1];
     464          26 :             quantities[0] = qs[2];
     465             :         }
     466          26 :         break;
     467           0 :     case AnnotationBase::ANNULUS:
     468           0 :         ThrowIf(
     469             :             ! consumeMe.contains(startTwoPair),
     470             :             preamble + "Illegal annulus specification " + consumeMe
     471             :         );
     472           0 :         qDirs.resize(2);
     473           0 :         quantities.resize(2);
     474             :         {
     475             :             Vector<Quantity> qs = _extractNQuantityPairs(
     476             :                 consumeMe, preamble
     477           0 :             );
     478           0 :             qDirs[0] = qs[0];
     479           0 :             qDirs[1] = qs[1];
     480           0 :             quantities[0] = qs[2];
     481           0 :             quantities[1] = qs[3];
     482             :         }
     483           0 :         break;
     484          90 :     case AnnotationBase::ELLIPSE:
     485          90 :         if (! consumeMe.contains(startTwoPairOneSingle)) {
     486           0 :             *_log << preamble << "Illegal ellipse specification "
     487           0 :                 << consumeMe << LogIO::EXCEPTION;
     488             :         }
     489          90 :         qDirs.resize(2);
     490          90 :         quantities.resize(3);
     491             :         {
     492             :             Vector<Quantity> qs = _extractTwoQuantityPairsAndSingleQuantity(
     493             :                 consumeMe, preamble
     494         180 :             );
     495          90 :             qDirs[0] = qs[0];
     496          90 :             qDirs[1] = qs[1];
     497          90 :             quantities[0] = qs[2];
     498          90 :             quantities[1] = qs[3];
     499          90 :             quantities[2] = qs[4];
     500             :         }
     501          90 :         break;
     502           0 :     case AnnotationBase::LINE:
     503           0 :         if (! consumeMe.contains(startTwoPair)) {
     504           0 :             *_log << preamble << "Illegal line specification "
     505           0 :                 << consumeMe << LogIO::EXCEPTION;
     506             :         }
     507           0 :         qDirs.resize(4);
     508           0 :         qDirs = _extractNQuantityPairs(consumeMe, preamble);
     509           0 :         if (qDirs.size() != 4) {
     510             :             throw AipsError(preamble
     511           0 :                 + "line spec must contain exactly 2 direction pairs but it has "
     512           0 :                 + String::toString(qDirs.size())
     513           0 :             );
     514             :         }
     515           0 :         break;
     516           0 :     case AnnotationBase::VECTOR:
     517           0 :         if (! consumeMe.contains(startTwoPair)) {
     518           0 :             *_log << preamble << "Illegal vector specification "
     519           0 :                 << consumeMe << LogIO::EXCEPTION;
     520             :         }
     521           0 :         qDirs.resize(4);
     522           0 :         qDirs = _extractNQuantityPairs(consumeMe, preamble);
     523           0 :         if (qDirs.size() != 4) {
     524             :             throw AipsError(preamble
     525           0 :                 + "line spec must contain exactly 2 direction pairs but it has "
     526           0 :                 + String::toString(qDirs.size())
     527           0 :             );
     528             :         }
     529           0 :         break;
     530           0 :     case AnnotationBase::TEXT:
     531           0 :         if (! consumeMe.contains(startOnePairAndText)) {
     532           0 :             *_log << preamble << "Illegal text specification "
     533           0 :                 << consumeMe << LogIO::EXCEPTION;
     534             :         }
     535           0 :         qDirs.resize(2);
     536           0 :         _extractQuantityPairAndString(
     537             :             myPair, textString, consumeMe, preamble, true
     538             :         );
     539           0 :         qDirs[0] = myPair.first;
     540           0 :         qDirs[1] = myPair.second;
     541           0 :         break;
     542           0 :     case AnnotationBase::SYMBOL:
     543           0 :         if (! consumeMe.contains(startOnePairOneSingle)) {
     544           0 :             *_log << preamble << "Illegal symbol specification "
     545           0 :                 << consumeMe << LogIO::EXCEPTION;
     546             :         }
     547           0 :         qDirs.resize(2);
     548           0 :         _extractQuantityPairAndString(
     549             :             myPair, textString, consumeMe, preamble, false
     550             :         );
     551           0 :         qDirs[0] = myPair.first;
     552           0 :         qDirs[1] = myPair.second;
     553           0 :         textString.trim();
     554           0 :         if (textString.length() > 1) {
     555             :             throw AipsError(
     556             :                 preamble
     557           0 :                     + ": A symbol is defined by a single character. The provided string ("
     558           0 :                     + textString
     559           0 :                     + ") has more than one"
     560           0 :             );
     561             :         }
     562           0 :         break;
     563           0 :     default:
     564           0 :         ThrowCc(
     565             :             preamble + "Unable to determine annotation type"
     566             :         );
     567             :     }
     568         278 :     return annotationType;
     569             : }
     570             : 
     571         140 : RegionTextParser::ParamSet RegionTextParser::getParamSet(
     572             :     Bool& spectralParmsUpdated, LogIO& log,
     573             :     const String& text, const String& preamble,
     574             :     const CoordinateSystem& csys,
     575             :     std::shared_ptr<std::pair<MFrequency, MFrequency> > overridingFreqRange,
     576             :     std::shared_ptr<Vector<Stokes::StokesTypes> > overridingCorrRange
     577             : ) {
     578         140 :     ParamSet parms;
     579         140 :     spectralParmsUpdated = false;
     580         280 :     auto consumeMe = text;
     581             :     // get key-value pairs on the line
     582         263 :     while (consumeMe.size() > 0) {
     583         246 :         ParamValue paramValue;
     584         123 :         auto key = AnnotationBase::UNKNOWN_KEYWORD;
     585         123 :         consumeMe.trim();
     586         123 :         consumeMe.ltrim(',');
     587         123 :         consumeMe.trim();
     588         123 :         ThrowIf(
     589             :             ! consumeMe.contains('='),
     590             :             preamble + "Illegal extra characters on line ("
     591             :                 + consumeMe + "). Did you forget a '='?"
     592             :         );
     593         123 :         const auto equalPos = consumeMe.find('=');
     594         246 :         auto keyword = consumeMe.substr(0, equalPos);
     595         123 :         keyword.trim();
     596         123 :         keyword.downcase();
     597         123 :         consumeMe.del(0, (Int)equalPos + 1);
     598         123 :         consumeMe.trim();
     599         123 :         if (keyword == "label") {
     600           2 :             key = AnnotationBase::LABEL;
     601           2 :             paramValue.stringVal = _doLabel(consumeMe, preamble);
     602             :         }
     603             :         else {
     604         121 :             paramValue.stringVal = _getKeyValue(consumeMe, preamble);
     605         121 :             if (keyword == "coord") {
     606          90 :                 key = AnnotationBase::COORD;
     607             :             }
     608          31 :             else if (keyword == "corr" && ! overridingCorrRange) {
     609           2 :                 if (csys.hasPolarizationCoordinate()) {
     610           2 :                     key = AnnotationBase::CORR;
     611           4 :                     paramValue.stokes = _stokesFromString(
     612             :                         paramValue.stringVal, preamble
     613           2 :                     );
     614             :                 }
     615             :                 else {
     616             :                     log << LogIO::WARN << preamble
     617             :                         << "Keyword " << keyword << " specified but will be ignored "
     618             :                         << "because the coordinate system has no polarization axis."
     619           0 :                         << LogIO::POST;
     620             :                 }
     621             :             }
     622          29 :             else if (
     623          29 :                 ! overridingFreqRange
     624          87 :                 && (
     625          58 :                     keyword == "frame" || keyword == "range"
     626          20 :                     || keyword == "veltype" || keyword == "restfreq"
     627             :                 )
     628             :             ) {
     629           9 :                 spectralParmsUpdated = true;
     630           9 :                 if (! csys.hasSpectralAxis()) {
     631           0 :                     spectralParmsUpdated = false;
     632             :                     log << LogIO::WARN << preamble
     633             :                         << "Keyword " << keyword << " specified but will be ignored "
     634             :                         << "because the coordinate system has no spectral axis."
     635           0 :                         << LogIO::POST;
     636             :                 }
     637           9 :                 else if (keyword == "frame") {
     638           0 :                     key = AnnotationBase::FRAME;
     639             :                 }
     640           9 :                 else if (keyword == "range") {
     641           9 :                     key = AnnotationBase::RANGE;
     642             :                 }
     643           0 :                 else if (keyword == "veltype") {
     644           0 :                     key = AnnotationBase::VELTYPE;
     645             :                 }
     646           0 :                 else if (keyword == "restfreq") {
     647           0 :                     key = AnnotationBase::RESTFREQ;
     648           0 :                     Quantity qRestfreq;
     649           0 :                     ThrowIf(
     650             :                         ! readQuantity(qRestfreq, paramValue.stringVal),
     651             :                         "Could not convert rest frequency "
     652             :                         + paramValue.stringVal + " to quantity"
     653             :                     );
     654             :                 }
     655             :             }
     656          20 :             else if (keyword == "linewidth") {
     657           2 :                 key = AnnotationBase::LINEWIDTH;
     658           2 :                 if (! paramValue.stringVal.matches(Regex("^[1-9]+$"))) {
     659             :                     log << preamble << "linewidth (" << paramValue.stringVal
     660           0 :                         << ") must be a positive integer but is not." << LogIO::EXCEPTION;
     661             :                 }
     662           2 :                 paramValue.intVal = String::toInt(paramValue.stringVal);
     663             :             }
     664          18 :             else if (keyword == "linestyle") {
     665           2 :                 key = AnnotationBase::LINESTYLE;
     666           2 :                 paramValue.lineStyleVal = AnnotationBase::lineStyleFromString(
     667             :                     paramValue.stringVal
     668             :                 );
     669             :             }
     670          16 :             else if (keyword == "symsize") {
     671           2 :                 key = AnnotationBase::SYMSIZE;
     672           2 :                 if (! paramValue.stringVal.matches(Regex("^[1-9]+$"))) {
     673             :                     log << preamble << "symsize (" << paramValue.stringVal
     674           0 :                         << ") must be a positive integer but is not." << LogIO::EXCEPTION;
     675             :                 }
     676           2 :                 paramValue.intVal = String::toInt(paramValue.stringVal);
     677             :             }
     678          14 :             else if (keyword == "symthick") {
     679           2 :                 key = AnnotationBase::SYMTHICK;
     680           2 :                 if (! paramValue.stringVal.matches(Regex("^[1-9]+$"))) {
     681             :                     log << preamble << "symthick (" << paramValue.stringVal
     682           0 :                         << ") must be a positive integer but is not." << LogIO::EXCEPTION;
     683             :                 }
     684           2 :                 paramValue.intVal = String::toInt(paramValue.stringVal);
     685             :             }
     686          12 :             else if (keyword == "color") {
     687           4 :                 key = AnnotationBase::COLOR;
     688             :             }
     689           8 :             else if (keyword == "font") {
     690           2 :                 key = AnnotationBase::FONT;
     691             :             }
     692           6 :             else if (keyword == "fontsize") {
     693           2 :                 key = AnnotationBase::FONTSIZE;
     694           2 :                 paramValue.intVal = String::toInt(paramValue.stringVal);
     695             :             }
     696           4 :             else if (keyword == "fontstyle") {
     697           2 :                 key = AnnotationBase::FONTSTYLE;
     698           2 :                 paramValue.fontStyleVal = AnnotationBase::fontStyleFromString(
     699             :                     paramValue.stringVal
     700             :                 );
     701             :             }
     702           2 :             else if (keyword == "usetex") {
     703           2 :                 String v = paramValue.stringVal;
     704           2 :                 v.downcase();
     705           2 :                 key = AnnotationBase::USETEX;
     706           2 :                 if (
     707           4 :                     v != "true"  && v != "t"
     708           4 :                     && v != "false" && v != "f"
     709             :                 ) {
     710             :                     log << preamble << "Cannot determine boolean value of usetex"
     711           0 :                         << paramValue.stringVal << LogIO::EXCEPTION;
     712             :                 }
     713           2 :                 paramValue.boolVal = (v == "true" || v == "t");
     714             :             }
     715           0 :             else if (keyword == "labelcolor") {
     716           0 :                 key = AnnotationBase::LABELCOLOR;
     717             :             }
     718           0 :             else if (keyword == "labelpos") {
     719           0 :                 key = AnnotationBase::LABELPOS;
     720             :             }
     721           0 :             else if (keyword == "labeloff") {
     722           0 :                 auto v = paramValue.stringVal;
     723           0 :                 static const String sInt("[-+]?[0-9]+");
     724           0 :                 static const Regex rInt(sInt);
     725           0 :                 if (
     726           0 :                     ! v.contains(
     727           0 :                         Regex(
     728           0 :                             sInt + "[[:space:]]*,[[:space:]]*" + sInt
     729             :                         )
     730             :                     )
     731             :                 ) {
     732             :                     log << preamble << "Illegal label offset specification \""
     733           0 :                         << v << "\"" << LogIO::EXCEPTION;
     734             :                 }
     735             :                 // the brackets have been stripped, add them back to make it easier
     736             :                 // to parse with a method already in existence
     737           0 :                 auto pair = _extractSinglePair("[" + v + "]");
     738           0 :                 paramValue.intVec = vector<Int>();
     739             : 
     740           0 :                 for (
     741           0 :                     auto iter=pair.begin();
     742           0 :                     iter != pair.end(); ++iter
     743             :                 ) {
     744           0 :                     if (! iter->matches(rInt)) {
     745             :                         log << preamble << "Illegal label offset specification, "
     746           0 :                             << *iter << " is not an integer" << LogIO::EXCEPTION;
     747             :                     }
     748           0 :                     paramValue.intVec.push_back(String::toInt(*iter));
     749             :                 }
     750           0 :                 key = AnnotationBase::LABELOFF;
     751             :             }
     752             :             else {
     753           0 :                 ThrowCc(preamble + "Unrecognized key " + keyword);
     754             :             }
     755             :         }
     756         123 :         consumeMe.trim();
     757         123 :         if (key != AnnotationBase::UNKNOWN_KEYWORD) {
     758         123 :             parms[key] = paramValue;
     759             :         }
     760             :     }
     761         280 :     return parms;
     762             : }
     763             : 
     764             : RegionTextParser::ParamSet
     765         139 : RegionTextParser::_getCurrentParamSet(
     766             :     Bool& spectralParmsUpdated, ParamSet& newParams,
     767             :     String& consumeMe, const String& preamble
     768             : ) const {
     769         139 :     auto currentParams = _currentGlobals;
     770         278 :     newParams = getParamSet(
     771             :         spectralParmsUpdated,
     772         139 :         *_log, consumeMe, preamble, _csys, _overridingFreqRange,
     773         139 :         _overridingCorrRange
     774         139 :     );
     775         261 :     for (const auto& p: newParams) {
     776         122 :         currentParams[p.first] = p.second;
     777             :     }
     778         139 :     ThrowIf(
     779             :         currentParams.find(AnnotationBase::RANGE) == currentParams.end()
     780             :         && currentParams.find(AnnotationBase::FRAME) != currentParams.end(),
     781             :         preamble + "Frame specified but frequency range not specified"
     782             :     );
     783         139 :     ThrowIf(
     784             :         currentParams.find(AnnotationBase::RANGE) == currentParams.end()
     785             :         && currentParams.find(AnnotationBase::RESTFREQ) != currentParams.end(),
     786             :         preamble + "Rest frequency specified but velocity range not specified"
     787             :     );
     788         139 :     return currentParams;
     789             : }
     790             : 
     791           8 : std::pair<Quantity, Quantity> RegionTextParser::_quantitiesFromFrequencyString(
     792             :     const String& freqString, const String& preamble
     793             : ) const {
     794             :     // the brackets have been stripped, add them back to make it easier
     795             :     // to parse with a method already in existence
     796          16 :     auto cString = "[" + freqString + "]";
     797           8 :     ThrowIf(! cString.contains(startOnePair),
     798             :         preamble + "Incorrect spectral range specification ("
     799             :         + freqString + ")"
     800             :     );
     801             :     return _extractSingleQuantityPair(
     802             :         cString, preamble
     803          16 :     );
     804             : }
     805             : 
     806         139 : void RegionTextParser::_createAnnotation(
     807             :     const AnnotationBase::Type annType,
     808             :     const Vector<Quantity>& qDirs,
     809             :     const std::pair<Quantity, Quantity>& qFreqs,
     810             :     const vector<Quantity>& quantities,
     811             :     const String& textString,
     812             :     const ParamSet& currentParamSet,
     813             :     const Bool annOnly, const Bool isDifference,
     814             :     const String& preamble, Bool requireImageRegion
     815             : ) {
     816         139 :     CountedPtr<AnnotationBase> annotation;
     817         139 :     Vector<Stokes::StokesTypes> stokes(0);
     818         139 :     if (
     819         139 :         currentParamSet.find(AnnotationBase::CORR) != currentParamSet.end()
     820         139 :         && _csys.hasPolarizationCoordinate()
     821             :     ) {
     822         134 :         stokes.resize(currentParamSet.at(AnnotationBase::CORR).stokes.size());
     823         134 :         stokes = currentParamSet.at(AnnotationBase::CORR).stokes;
     824             :     }
     825         139 :     auto dirRefFrame = currentParamSet.at(AnnotationBase::COORD).stringVal;
     826         139 :     auto freqRefFrame = currentParamSet.find(AnnotationBase::FRAME) == currentParamSet.end()
     827         139 :         ? "" : currentParamSet.at(AnnotationBase::FRAME).stringVal;
     828         139 :     auto doppler = currentParamSet.find(AnnotationBase::VELTYPE) == currentParamSet.end()
     829         139 :         ? "" :    currentParamSet.at(AnnotationBase::VELTYPE).stringVal;
     830         139 :     Quantity restfreq;
     831         139 :     if (
     832         139 :         currentParamSet.find(AnnotationBase::RESTFREQ) != currentParamSet.end()
     833         278 :         && ! readQuantity(
     834         278 :             restfreq, currentParamSet.at(AnnotationBase::RESTFREQ).stringVal
     835             :         )
     836             :     ) {
     837           0 :         *_log << preamble << "restfreq value "
     838           0 :             << currentParamSet.at(AnnotationBase::RESTFREQ).stringVal << " is not "
     839           0 :             << "a valid quantity." << LogIO::EXCEPTION;
     840             :     }
     841             :     try {
     842         139 :     switch (annType) {
     843          21 :         case AnnotationBase::RECT_BOX:
     844             :             annotation = new AnnRectBox(
     845          21 :                 qDirs[0], qDirs[1], qDirs[2], qDirs[3],
     846          21 :                 dirRefFrame, _csys, _imShape, qFreqs.first, qFreqs.second,
     847             :                 freqRefFrame, doppler, restfreq, stokes,
     848             :                 annOnly, requireImageRegion
     849          21 :             );
     850          21 :             break;
     851           1 :         case AnnotationBase::CENTER_BOX:
     852             :             annotation = new AnnCenterBox(
     853           1 :                 qDirs[0], qDirs[1], quantities[0], quantities[1],
     854           1 :                 dirRefFrame, _csys, _imShape, qFreqs.first, qFreqs.second,
     855             :                 freqRefFrame, doppler, restfreq, stokes,
     856             :                 annOnly, requireImageRegion
     857           1 :             );
     858           1 :             break;
     859           0 :         case AnnotationBase::ROTATED_BOX:
     860             :             annotation = new AnnRotBox(
     861           0 :                 qDirs[0], qDirs[1], quantities[0], quantities[1],
     862           0 :                 quantities[2], dirRefFrame, _csys, _imShape, qFreqs.first, qFreqs.second,
     863             :                 freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
     864           0 :             );
     865           0 :             break;
     866           1 :         case AnnotationBase::POLYGON:
     867             :             {
     868           2 :                 Vector<Quantity> x(qDirs.size()/2);
     869           1 :                 Vector<Quantity> y(qDirs.size()/2);
     870          14 :                 for (uInt i=0; i<x.size(); ++i) {
     871          13 :                     x[i] = qDirs[2*i];
     872          13 :                     y[i] = qDirs[2*i + 1];
     873             :                 }
     874             :                 annotation = new AnnPolygon(
     875           1 :                     x, y, dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
     876             :                     freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
     877           1 :                 );
     878             :             }
     879           1 :             break;
     880          26 :         case AnnotationBase::CIRCLE:
     881          26 :             if (
     882          26 :                 quantities[0].getUnit() == "pix"
     883          26 :                 && ! _csys.directionCoordinate().hasSquarePixels()
     884             :             ) {
     885             :                 // radius specified in pixels and pixels are not square, use
     886             :                 // an AnnEllipse
     887             :                 annotation = new AnnEllipse(
     888           0 :                     qDirs[0], qDirs[1], quantities[0], quantities[0], Quantity(0, "deg"),
     889           0 :                     dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
     890             :                     freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
     891           0 :                 );
     892             :             }
     893             :             else {
     894             :                 annotation = new AnnCircle(
     895          26 :                     qDirs[0], qDirs[1], quantities[0],
     896          26 :                     dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
     897             :                     freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
     898          26 :                 );
     899             :             }
     900          26 :             break;
     901           0 :         case AnnotationBase::ANNULUS:
     902             :             annotation = new AnnAnnulus(
     903           0 :                 qDirs[0], qDirs[1], quantities[0], quantities[1],
     904           0 :                 dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
     905             :                 freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
     906           0 :             );
     907           0 :             break;
     908          90 :         case AnnotationBase::ELLIPSE:
     909             :             annotation = new AnnEllipse(
     910          90 :                 qDirs[0], qDirs[1], quantities[0], quantities[1], quantities[2],
     911          90 :                 dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
     912             :                 freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
     913          90 :             );
     914          90 :             break;
     915           0 :         case AnnotationBase::LINE:
     916             :             annotation = new AnnLine(
     917           0 :                 qDirs[0], qDirs[1], qDirs[2],
     918           0 :                 qDirs[3], dirRefFrame,  _csys,
     919           0 :                 qFreqs.first, qFreqs.second,
     920             :                 freqRefFrame, doppler, restfreq,  stokes
     921           0 :             );
     922           0 :             break;
     923           0 :         case AnnotationBase::VECTOR:
     924             :             annotation = new AnnVector(
     925           0 :                 qDirs[0], qDirs[1], qDirs[2],
     926           0 :                 qDirs[3], dirRefFrame,  _csys,
     927           0 :                 qFreqs.first, qFreqs.second,
     928             :                 freqRefFrame, doppler, restfreq,  stokes
     929           0 :             );
     930           0 :             break;
     931           0 :         case AnnotationBase::TEXT:
     932             :             annotation = new AnnText(
     933           0 :                 qDirs[0], qDirs[1], dirRefFrame,
     934           0 :                 _csys, textString, qFreqs.first, qFreqs.second,
     935             :                 freqRefFrame, doppler, restfreq,  stokes
     936           0 :             );
     937           0 :             break;
     938           0 :         case AnnotationBase::SYMBOL:
     939             :             annotation = new AnnSymbol(
     940           0 :                 qDirs[0], qDirs[1], dirRefFrame,
     941           0 :                 _csys, textString.firstchar(),
     942           0 :                 qFreqs.first, qFreqs.second, freqRefFrame,
     943             :                 doppler, restfreq,  stokes
     944           0 :             );
     945           0 :             break;
     946           0 :         default:
     947           0 :             ThrowCc(
     948             :                 preamble + "Logic error. Unhandled type "
     949             :                     +  String::toString(annType) + " in switch statement"
     950             :             );
     951             :     }
     952             :     }
     953           0 :     catch (const WorldToPixelConversionError& x) {
     954           0 :         *_log << LogIO::WARN << preamble
     955             :             << "Error converting one or more world coordinates to pixel coordinates. "
     956             :             << "This could mean, among other things, that (part of) the region or "
     957             :             << "annotation lies far outside the image. This region/annotation will "
     958           0 :             << "be ignored. The related message is: " << x.getMesg() << LogIO::POST;
     959           0 :         return;
     960             :     }
     961           0 :     catch (const ToLCRegionConversionError& x) {
     962           0 :         *_log << LogIO::WARN << preamble
     963             :             << "Error converting world region to lattice region which probably indicates "
     964             :             << "the region lies outside of the image. This region will be ignored."
     965           0 :             << "The related message is: " << x.getMesg() << LogIO::POST;
     966           0 :         return;
     967             :     }
     968           0 :     catch (const AipsError& x) {
     969           0 :         ThrowCc(preamble + x.getMesg());
     970             :     }
     971         139 :     if (annotation->isRegion()) {
     972         139 :         dynamic_cast<AnnRegion *>(annotation.get())->setDifference(isDifference);
     973         139 :         if (! annOnly) {
     974         139 :             ++_regions;
     975             :         }
     976             :     }
     977         139 :     annotation->setLineWidth(currentParamSet.at(AnnotationBase::LINEWIDTH).intVal);
     978         278 :     annotation->setLineStyle(
     979             :         AnnotationBase::lineStyleFromString(
     980         139 :             currentParamSet.at(AnnotationBase::LINESTYLE).stringVal
     981             :         )
     982             :     );
     983         139 :     annotation->setSymbolSize(currentParamSet.at(AnnotationBase::SYMSIZE).intVal);
     984         139 :     annotation->setSymbolThickness(currentParamSet.at(AnnotationBase::SYMTHICK).intVal);
     985         139 :     annotation->setColor(currentParamSet.at(AnnotationBase::COLOR).stringVal);
     986         139 :     annotation->setFont(currentParamSet.at(AnnotationBase::FONT).stringVal);
     987         139 :     annotation->setFontSize(currentParamSet.at(AnnotationBase::FONTSIZE).intVal);
     988         139 :     annotation->setFontStyle(
     989         139 :         AnnotationBase::fontStyleFromString(
     990         139 :             currentParamSet.at(AnnotationBase::FONTSTYLE).stringVal
     991             :         )
     992             :     );
     993         139 :     annotation->setUseTex(currentParamSet.at(AnnotationBase::USETEX).boolVal);
     994         139 :     if (
     995         139 :         currentParamSet.find(AnnotationBase::LABEL) != currentParamSet.end()
     996         139 :         && ! currentParamSet.find(AnnotationBase::LABEL)->second.stringVal.empty()
     997             :     ) {
     998           2 :         annotation->setLabel(currentParamSet.at(AnnotationBase::LABEL).stringVal);
     999           2 :         annotation->setLabelColor(currentParamSet.at(AnnotationBase::LABELCOLOR).stringVal);
    1000           2 :         annotation->setLabelColor(currentParamSet.at(AnnotationBase::LABELCOLOR).stringVal);
    1001           2 :         annotation->setLabelPosition(currentParamSet.at(AnnotationBase::LABELPOS).stringVal);
    1002           2 :         annotation->setLabelOffset(currentParamSet.at(AnnotationBase::LABELOFF).intVec);
    1003             :     }
    1004         139 :     annotation->setGlobals(_globalKeysToApply);
    1005         278 :     AsciiAnnotationFileLine line(annotation);
    1006         139 :     _addLine(line);
    1007             : }
    1008             : 
    1009          90 : Array<String> RegionTextParser::_extractTwoPairs(uInt& end, const String& string) const {
    1010          90 :     end = 0;
    1011          90 :     Int firstBegin = string.find('[', 1);
    1012          90 :     Int firstEnd = string.find(']', firstBegin);
    1013         180 :     auto firstPair = string.substr(firstBegin, firstEnd - firstBegin + 1);
    1014          90 :     Int secondBegin = string.find('[', firstEnd);
    1015          90 :     Int secondEnd = string.find(']', secondBegin);
    1016         180 :     auto secondPair = string.substr(secondBegin, secondEnd - secondBegin + 1);
    1017         180 :     auto first = _extractSinglePair(firstPair);
    1018         180 :     auto second = _extractSinglePair(secondPair);
    1019             : 
    1020          90 :     end = secondEnd;
    1021          90 :     Array<String> ret(IPosition(2, 2, 2));
    1022          90 :     ret(IPosition(2, 0, 0)) = first[0];
    1023          90 :     ret(IPosition(2, 0, 1)) = first[1];
    1024          90 :     ret(IPosition(2, 1, 0)) = second[0];
    1025          90 :     ret(IPosition(2, 1, 1)) = second[1];
    1026         180 :     return ret;
    1027             : }
    1028             : 
    1029         271 : Vector<String> RegionTextParser::_extractSinglePair(const String& string) {
    1030             :     Char quotes[2];
    1031         271 :     quotes[0] = '\'';
    1032         271 :     quotes[1] = '"';
    1033         271 :     Int firstBegin = string.find('[', 0) + 1;
    1034         271 :     Int firstEnd = string.find(',', firstBegin);
    1035         542 :     auto first = string.substr(firstBegin, firstEnd - firstBegin);
    1036         271 :     first.trim();
    1037         271 :     first.trim(quotes, 2);
    1038         271 :     Int secondBegin = firstEnd + 1;
    1039         271 :     Int secondEnd = string.find(']', secondBegin);
    1040         542 :     auto second = string.substr(secondBegin, secondEnd - secondBegin);
    1041         271 :     second.trim();
    1042         271 :     second.trim(quotes, 2);
    1043         271 :     Vector<String> ret(2);
    1044         271 :     ret[0] = first;
    1045         271 :     ret[1] = second;
    1046         542 :     return ret;
    1047             : }
    1048             : 
    1049           2 : String RegionTextParser::_doLabel(
    1050             :     String& consumeMe, const String& preamble
    1051             : ) {
    1052           2 :     const auto firstChar = consumeMe.firstchar();
    1053           2 :     if (firstChar != '\'' && firstChar != '"') {
    1054           0 :         ostringstream oss;
    1055           0 :         oss << preamble << "keyword 'label' found but first non-whitespace "
    1056           0 :             << "character after the '=' is not a quote. It must be.";
    1057           0 :         throw AipsError(oss.str());
    1058             :     }
    1059           2 :     const auto posCloseQuote = consumeMe.find(firstChar, 1);
    1060           2 :     if (posCloseQuote == String::npos) {
    1061           0 :         ostringstream err;
    1062           0 :         err << preamble << "Could not find closing quote ("
    1063           0 :             << String(firstChar) << ") for label";
    1064           0 :         throw AipsError(err.str());
    1065             :     }
    1066           2 :     const auto label = consumeMe.substr(1, posCloseQuote - 1);
    1067           2 :     consumeMe.del(0, (Int)posCloseQuote + 1);
    1068           2 :     return label;
    1069             : }
    1070             : 
    1071         121 : String RegionTextParser::_getKeyValue(
    1072             :     String& consumeMe, const String& preamble
    1073             : ) {
    1074         121 :     String value;
    1075         121 :     if (consumeMe.startsWith("[")) {
    1076          11 :         if (! consumeMe.contains("]")) {
    1077           0 :             ostringstream err;
    1078           0 :             err << preamble << "Unmatched open bracket: "
    1079           0 :                 << consumeMe;
    1080           0 :             ThrowCc(err.str());
    1081             :         }
    1082          11 :         Int closeBracketPos = consumeMe.find("]");
    1083             :         // don't save the open and close brackets
    1084          11 :         value = consumeMe.substr(1, closeBracketPos - 1);
    1085          11 :         consumeMe.del(0, closeBracketPos + 1);
    1086             :     }
    1087         121 :     if (consumeMe.contains(",")) {
    1088          24 :         Int commaPos = consumeMe.find(",");
    1089          24 :         if (value.empty()) {
    1090          22 :             value = consumeMe.substr(0, commaPos);
    1091             :         }
    1092          24 :         consumeMe.del(0, commaPos);
    1093             :     }
    1094          97 :     else if (value.empty()) {
    1095             :         // last key-value pair on the line
    1096          88 :         value = consumeMe;
    1097          88 :         consumeMe = "";
    1098             :     }
    1099         121 :     consumeMe.trim();
    1100             :     Char quotes[2];
    1101         121 :     quotes[0] = '\'';
    1102         121 :     quotes[1] = '"';
    1103         121 :     value.trim();
    1104         121 :     value.trim(quotes, 2);
    1105         121 :     value.trim();
    1106         242 :     return value;
    1107             : }
    1108             : 
    1109          90 : Vector<Quantity> RegionTextParser::_extractTwoQuantityPairsAndSingleQuantity(
    1110             :     String& consumeMe, const String& preamble
    1111             : ) const {
    1112             :     Vector<Quantity> quantities = _extractTwoQuantityPairs(
    1113             :         consumeMe, preamble
    1114          90 :     );
    1115          90 :     consumeMe.trim();
    1116          90 :     consumeMe.ltrim(',');
    1117          90 :     consumeMe.trim();
    1118             :     Char quotes[2];
    1119          90 :     quotes[0] = '\'';
    1120          90 :     quotes[1] = '"';
    1121             : 
    1122          90 :     Int end = consumeMe.find(']', 0);
    1123         180 :     String qString = consumeMe.substr(0, end);
    1124          90 :     qString.trim();
    1125             : 
    1126          90 :     qString.trim(quotes, 2);
    1127          90 :     quantities.resize(5, true);
    1128          90 :     if (! readQuantity(quantities[4], qString)) {
    1129           0 :         *_log << preamble + "Could not convert "
    1130           0 :             << qString << " to quantity." << LogIO::EXCEPTION;
    1131             :     }
    1132          90 :     consumeMe.del(0, end + 1);
    1133         180 :     return quantities;
    1134             : }
    1135             : 
    1136          26 : void RegionTextParser::_extractQuantityPairAndString(
    1137             :     std::pair<Quantity, Quantity>& quantities, String& string,
    1138             :     String& consumeMe, const String& preamble,
    1139             :     const Bool requireQuotesAroundString
    1140             : ) const {
    1141             :     // erase the left '['
    1142          26 :     consumeMe.del(0, 1);
    1143          26 :     SubString pairString = consumeMe.through(startOnePair);
    1144          26 :     quantities = _extractSingleQuantityPair(pairString, preamble);
    1145          26 :     consumeMe.del(0, (Int)pairString.length() + 1);
    1146          26 :     consumeMe.trim();
    1147          26 :     consumeMe.ltrim(',');
    1148          26 :     consumeMe.trim();
    1149          26 :     Int end = 0;
    1150          26 :     String::size_type startSearchPos = 0;
    1151          26 :     if (requireQuotesAroundString) {
    1152           0 :         Char quoteChar = consumeMe.firstchar();
    1153           0 :         if (quoteChar != '\'' && quoteChar != '"') {
    1154           0 :             *_log << preamble
    1155           0 :                 << "Quotes around string required but no quotes were found";
    1156             :         }
    1157           0 :         startSearchPos = consumeMe.find(quoteChar, 1);
    1158           0 :         if (startSearchPos == String::npos) {
    1159           0 :             *_log << preamble
    1160             :                 << "Quotes required around string but no matching close quote found"
    1161           0 :                 << LogIO::EXCEPTION;
    1162             :         }
    1163             :     }
    1164          26 :     end = consumeMe.find(']', startSearchPos);
    1165          26 :     string = consumeMe.substr(0, end);
    1166          26 :     consumeMe.del(0, end + 1);
    1167          26 :     string.trim();
    1168             :     Char quotes[2];
    1169          26 :     quotes[0] = '\'';
    1170          26 :     quotes[1] = '"';
    1171          26 :     string.trim(quotes, 2);
    1172          26 :     string.trim();
    1173          26 : }
    1174             : 
    1175          26 : Vector<Quantity> RegionTextParser::_extractQuantityPairAndSingleQuantity(
    1176             :     String& consumeMe, const String& preamble
    1177             : ) const {
    1178          52 :     String qString;
    1179             : 
    1180          52 :     std::pair<Quantity, Quantity> myPair;
    1181          26 :     _extractQuantityPairAndString(
    1182             :         myPair, qString, consumeMe, preamble, false
    1183             :     );
    1184          26 :     Vector<Quantity> quantities(3);
    1185          26 :     quantities[0] = myPair.first;
    1186          26 :     quantities[1] = myPair.second;
    1187          26 :     ThrowIf(
    1188             :         ! readQuantity(quantities[2], qString),
    1189             :         preamble + "Could not convert "
    1190             :         + qString + " to quantity"
    1191             :     );
    1192          52 :     return quantities;
    1193             : }
    1194             : 
    1195          90 : Vector<Quantity> RegionTextParser::_extractTwoQuantityPairs(
    1196             :     String& consumeMe, const String& preamble
    1197             : ) const {
    1198         180 :     const Regex startbTwoPair("^" + bTwoPair);
    1199         180 :     String mySubstring = String(consumeMe).through(startbTwoPair);
    1200          90 :     uInt end = 0;
    1201         180 :     Array<String> pairs = _extractTwoPairs(end, mySubstring);
    1202          90 :     Vector<Quantity> quantities(4);
    1203             : 
    1204         450 :     for (uInt i=0; i<4; ++i) {
    1205         720 :         String desc("string " + String::toString(i));
    1206         720 :         String value = pairs(IPosition(2, i/2, i%2));
    1207         360 :         if (! readQuantity(quantities[i], value)) {
    1208           0 :             *_log << preamble << "Could not convert " << desc
    1209           0 :                 << " (" << value << ") to quantity." << LogIO::EXCEPTION;
    1210             :         }
    1211             :     }
    1212          90 :     consumeMe.del(0, (Int)end + 1);
    1213         180 :     return quantities;
    1214             : }
    1215             : 
    1216          23 : Vector<Quantity> RegionTextParser::_extractNQuantityPairs (
    1217             :         String& consumeMe, const String& preamble
    1218             : ) const {
    1219          46 :     String pairs = consumeMe.through(Regex("\\] *\\]"));
    1220          46 :     String nPairs(pairs);
    1221          23 :     consumeMe.del(0, (Int)pairs.length() + 1);
    1222          23 :     pairs.trim();
    1223             :     // remove the left most [
    1224          23 :     pairs.del(0, 1);
    1225          23 :     pairs.trim();
    1226          23 :     Vector<Quantity> qs(0);
    1227             : 
    1228             :     try {
    1229          80 :         while (pairs.length() > 1) {
    1230         114 :             std::pair<Quantity, Quantity> myqs = _extractSingleQuantityPair(pairs, preamble);
    1231          57 :             qs.resize(qs.size() + 2, true);
    1232          57 :             qs[qs.size() - 2] = myqs.first;
    1233          57 :             qs[qs.size() - 1] = myqs.second;
    1234          57 :             pairs.del(0, (Int)pairs.find(']', 0) + 1);
    1235          57 :             pairs.trim();
    1236          57 :             pairs.ltrim(',');
    1237          57 :             pairs.trim();
    1238             :         }
    1239             :     }
    1240           0 :     catch (const AipsError&) {
    1241           0 :         ThrowCc(
    1242             :             preamble + "Illegal polygon specification " + nPairs
    1243             :         );
    1244             :     }
    1245          46 :     return qs;
    1246             : }
    1247             : 
    1248          91 : std::pair<Quantity, Quantity> RegionTextParser::_extractSingleQuantityPair(
    1249             :     const String& pairString, const String& preamble
    1250             : ) const {
    1251         182 :     String mySubstring = String(pairString).through(sOnePair, 0);
    1252         182 :     Vector<String> pair = _extractSinglePair(mySubstring);
    1253          91 :     std::pair<Quantity, Quantity> quantities;
    1254         273 :     for (uInt i=0; i<2; ++i) {
    1255         364 :         String value = pair[i];
    1256         182 :         ThrowIf(
    1257             :             ! readQuantity(
    1258             :                 i == 0
    1259             :                     ? quantities.first
    1260             :                     : quantities.second,
    1261             :                 value
    1262             :             ),
    1263             :             preamble + "Could not convert ("
    1264             :             + value + ") to quantity."
    1265             :         );
    1266             :     }
    1267         182 :     return quantities;
    1268             : }
    1269             : 
    1270          73 : void RegionTextParser::_setOverridingCorrelations(const String& globalOverrideStokes) {
    1271          73 :     if (globalOverrideStokes.empty() || ! _csys.hasPolarizationAxis()) {
    1272             :         // no global override specified
    1273          72 :         return;
    1274             :     }
    1275           2 :     String mycopy = globalOverrideStokes;
    1276           2 :     vector<String> myStokes = ParameterParser::stokesFromString(mycopy);
    1277           2 :     String myCommaSeperatedString;
    1278           1 :     vector<String>::const_iterator iter = myStokes.begin();
    1279           1 :     vector<String>::const_iterator end = myStokes.end();
    1280           1 :     uInt count = 0;
    1281           3 :     while(iter != end) {
    1282           2 :         myCommaSeperatedString += *iter;
    1283           2 :         if (count < myStokes.size() - 1) {
    1284           1 :             myCommaSeperatedString += ",";
    1285             :         }
    1286           2 :         ++count;
    1287           2 :         ++iter;
    1288             :     }
    1289           2 :     ParamValue corr;
    1290           1 :     corr.stokes = _stokesFromString(myCommaSeperatedString, String(__func__));
    1291           1 :     _currentGlobals[AnnotationBase::CORR] = corr;
    1292           1 :     _overridingCorrRange.reset((new Vector<Stokes::StokesTypes>(corr.stokes)));
    1293             : }
    1294             : 
    1295          74 : void RegionTextParser::_setOverridingChannelRange(
    1296             :     const String& globalOverrideChans
    1297             : ) {
    1298          74 :     if (globalOverrideChans.empty() || ! _csys.hasSpectralAxis()) {
    1299             :         // no global override specified
    1300          71 :         return;
    1301             :     }
    1302           3 :     uInt nSelectedChannels = 0;
    1303           3 :     uInt nChannels = _imShape[_csys.spectralAxisNumber(false)];
    1304             :     std::vector<uInt> myChanRange =  ParameterParser::spectralRangesFromChans(
    1305             :         nSelectedChannels, globalOverrideChans,
    1306             :         nChannels
    1307           4 :     );
    1308           3 :     uInt nRanges = myChanRange.size();
    1309           3 :     if (nRanges == 0) {
    1310             :         // no channel range specified
    1311           0 :         return;
    1312             :     }
    1313           4 :     ThrowIf(
    1314             :         nRanges > 2,
    1315             :         "Overriding spectral specification must be "
    1316             :         "limited to a sngle channel range"
    1317             :     );
    1318           4 :     MFrequency first, second;
    1319           4 :     const SpectralCoordinate specCoord = _csys.spectralCoordinate();
    1320           2 :     specCoord.toWorld(first, myChanRange[0]);
    1321           2 :     specCoord.toWorld(second, myChanRange[1]);
    1322           2 :     _overridingFreqRange.reset(new std::pair<MFrequency, MFrequency>(first, second));
    1323           4 :     ParamValue range;
    1324           2 :     range.freqRange = _overridingFreqRange;
    1325           2 :     _currentGlobals[AnnotationBase::RANGE] = range;
    1326             : 
    1327           4 :     ParamValue frame;
    1328           2 :     frame.intVal = specCoord.frequencySystem(false);
    1329           2 :     _currentGlobals[AnnotationBase::FRAME] = frame;
    1330             : 
    1331           4 :     ParamValue veltype;
    1332           2 :     veltype.intVal = specCoord.velocityDoppler();
    1333           2 :     _currentGlobals[AnnotationBase::VELTYPE] = veltype;
    1334             : 
    1335           2 :     ParamValue restfreq;
    1336           0 :     restfreq.stringVal = String::toString(
    1337           2 :         specCoord.restFrequency()
    1338           2 :     ) + "Hz";
    1339           2 :     _currentGlobals[AnnotationBase::RESTFREQ] = restfreq;
    1340             : }
    1341             : 
    1342             : Vector<Stokes::StokesTypes>
    1343           3 : RegionTextParser::_stokesFromString(
    1344             :     const String& stokes, const String& preamble
    1345             : ) {
    1346           3 :     const auto maxn = Stokes::NumberOfTypes;
    1347         105 :     std::unique_ptr<string[]> res(new string[maxn]);
    1348           3 :     Int nStokes = split(stokes, res.get( ), maxn, ",");
    1349           3 :     Vector<Stokes::StokesTypes> myTypes(nStokes);
    1350           7 :     for (Int i=0; i<nStokes; ++i) {
    1351           8 :         String x(res.get( )[i]);
    1352           4 :         x.trim();
    1353           4 :         myTypes[i] = Stokes::type(x);
    1354           4 :         if (myTypes[i] == Stokes::Undefined) {
    1355           0 :             throw AipsError(preamble + "Unknown correlation type " + x);
    1356             :         }
    1357             :     }
    1358           6 :     return myTypes;
    1359             : }
    1360             : 
    1361          74 : void RegionTextParser::_setInitialGlobals() {
    1362         148 :     ParamValue coord;
    1363          74 :     coord.intVal = _csys.directionCoordinate(
    1364         148 :         _csys.findCoordinate(Coordinate::DIRECTION)
    1365          74 :     ).directionType(false);
    1366          74 :     coord.stringVal = MDirection::showType(coord.intVal);
    1367          74 :     _currentGlobals[AnnotationBase::COORD] = coord;
    1368             : 
    1369         148 :     ParamValue range;
    1370          74 :     range.freqRange.reset();
    1371          74 :     _currentGlobals[AnnotationBase::RANGE] = range;
    1372             : 
    1373         148 :     ParamValue corr;
    1374          74 :     corr.stokes = Vector<Stokes::StokesTypes>(0);
    1375          74 :     _currentGlobals[AnnotationBase::CORR] = corr;
    1376             : 
    1377          74 :     if (_csys.hasSpectralAxis()) {
    1378             :         SpectralCoordinate spectral = _csys.spectralCoordinate(
    1379         148 :             _csys.findCoordinate(Coordinate::SPECTRAL)
    1380         148 :         );
    1381             : 
    1382         148 :         ParamValue frame;
    1383          74 :         frame.intVal = spectral.frequencySystem(false);
    1384          74 :         _currentGlobals[AnnotationBase::FRAME] = frame;
    1385             : 
    1386         148 :         ParamValue veltype;
    1387          74 :         veltype.intVal = spectral.velocityDoppler();
    1388          74 :         _currentGlobals[AnnotationBase::VELTYPE] = veltype;
    1389             : 
    1390         148 :         ParamValue restfreq;
    1391             :         // truncates value, not enough precision
    1392             :         // restfreq.stringVal = String::toString(spectral.restFrequency()) + "Hz";
    1393          74 :         ostringstream oss;
    1394          74 :         oss << std::setprecision(20) << spectral.restFrequency() << "Hz";
    1395          74 :         restfreq.stringVal = oss.str();
    1396          74 :         _currentGlobals[AnnotationBase::RESTFREQ] = restfreq;
    1397             :     }
    1398         148 :     ParamValue linewidth;
    1399          74 :     linewidth.intVal = AnnotationBase::DEFAULT_LINEWIDTH;
    1400          74 :     _currentGlobals[AnnotationBase::LINEWIDTH] = linewidth;
    1401             : 
    1402         148 :     ParamValue linestyle;
    1403          74 :     linestyle.lineStyleVal = AnnotationBase::DEFAULT_LINESTYLE;
    1404          74 :     _currentGlobals[AnnotationBase::LINESTYLE] = linestyle;
    1405             : 
    1406         148 :     ParamValue symsize;
    1407          74 :     symsize.intVal = AnnotationBase::DEFAULT_SYMBOLSIZE;
    1408          74 :     _currentGlobals[AnnotationBase::SYMSIZE] = symsize;
    1409             : 
    1410         148 :     ParamValue symthick;
    1411          74 :     symthick.intVal = AnnotationBase::DEFAULT_SYMBOLTHICKNESS;
    1412          74 :     _currentGlobals[AnnotationBase::SYMTHICK] = symthick;
    1413             : 
    1414         148 :     ParamValue color;
    1415          74 :     color.color = AnnotationBase::DEFAULT_COLOR;
    1416          74 :     color.stringVal = AnnotationBase::colorToString(color.color);
    1417          74 :     _currentGlobals[AnnotationBase::COLOR] = color;
    1418             : 
    1419         148 :     ParamValue font;
    1420          74 :     font.stringVal = AnnotationBase::DEFAULT_FONT;
    1421          74 :     _currentGlobals[AnnotationBase::FONT] = font;
    1422             : 
    1423         148 :     ParamValue fontsize;
    1424          74 :     fontsize.intVal = AnnotationBase::DEFAULT_FONTSIZE;
    1425          74 :     _currentGlobals[AnnotationBase::FONTSIZE] = fontsize;
    1426             : 
    1427         148 :     ParamValue fontstyle;
    1428          74 :     fontstyle.fontStyleVal = AnnotationBase::DEFAULT_FONTSTYLE;
    1429          74 :     _currentGlobals[AnnotationBase::FONTSTYLE] = fontstyle;
    1430             : 
    1431         148 :     ParamValue usetex;
    1432          74 :     usetex.boolVal = AnnotationBase::DEFAULT_USETEX;
    1433          74 :     _currentGlobals[AnnotationBase::USETEX] = usetex;
    1434             : 
    1435         148 :     ParamValue labelcolor;
    1436          74 :     labelcolor.color = AnnotationBase::DEFAULT_LABELCOLOR;
    1437          74 :     labelcolor.stringVal = AnnotationBase::colorToString(labelcolor.color);
    1438          74 :     _currentGlobals[AnnotationBase::LABELCOLOR] = labelcolor;
    1439             : 
    1440         148 :     ParamValue labelpos;
    1441          74 :     labelpos.stringVal = AnnotationBase::DEFAULT_LABELPOS;
    1442          74 :     _currentGlobals[AnnotationBase::LABELPOS] = labelpos;
    1443             : 
    1444          74 :     ParamValue labeloff;
    1445          74 :     labeloff.intVec = AnnotationBase::DEFAULT_LABELOFF;
    1446          74 :     _currentGlobals[AnnotationBase::LABELOFF] = labeloff;
    1447          74 : }
    1448             : 
    1449             : }
    1450             : 

Generated by: LCOV version 1.16