LCOV - code coverage report
Current view: top level - imageanalysis/IO - RegionTextParser.cc (source / functions) Hit Total Coverage
Test: casacpp_coverage.info Lines: 0 929 0.0 %
Date: 2024-10-09 13:55:54 Functions: 0 27 0.0 %

          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: casa-feedback@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           0 : 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           0 : ) : _csys(csys), _log(new LogIO()), _currentGlobals(),
      67           0 :     _lines(), _globalKeysToApply(), _fileVersion(-1), _imShape(imShape),
      68           0 :     _regions(0), _verbose(verbose) {
      69           0 :     RegularFile file(filename);
      70           0 :     if (! file.exists()) {
      71           0 :         throw AipsError(
      72           0 :             _ORIGIN + "File " + filename + " does not exist."
      73           0 :         );
      74             :     }
      75           0 :     if (! file.isReadable()) {
      76           0 :         throw AipsError(
      77           0 :             _ORIGIN + "File " + filename + " is not readable."
      78           0 :         );
      79             :     }
      80           0 :     if (! _csys.hasDirectionCoordinate()) {
      81           0 :         throw AipsError(
      82           0 :             _ORIGIN
      83           0 :             + "Coordinate system does not have a direction coordinate"
      84           0 :         );
      85             :     }
      86           0 :     _setInitialGlobals();
      87           0 :     _setOverridingChannelRange(globalOverrideChans);
      88           0 :     _setOverridingCorrelations(globalOverrrideStokes);
      89           0 :     RegularFileIO fileIO(file);
      90           0 :     Int bufSize = 4096;
      91           0 :     std::unique_ptr<char> buffer(new char[bufSize]);
      92             :     int nRead;
      93           0 :     String contents;
      94           0 :     if (! prependRegion.empty()) {
      95           0 :         contents = prependRegion + "\n";
      96             :     }
      97           0 :     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           0 :     }
     104             :     // get the last chunk
     105           0 :     String chunk(buffer.get( ), nRead);
     106           0 :     if (_fileVersion < 0) {
     107           0 :         _determineVersion(chunk, filename, requireAtLeastThisVersion);
     108             :     }
     109           0 :     contents += chunk;
     110           0 :     _parse(contents, filename, requireImageRegion);
     111           0 : }
     112             : 
     113           0 : 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           0 : ) : _csys(csys), _log(new LogIO()), _currentGlobals(), _lines(),
     119           0 :     _globalKeysToApply(), _fileVersion(-1), _imShape(imShape), _regions(0),
     120           0 :     _verbose(verbose) {
     121           0 :     if (! _csys.hasDirectionCoordinate()) {
     122           0 :         throw AipsError(
     123           0 :             _ORIGIN + "Coordinate system has no direction coordinate"
     124           0 :         );
     125             :     }
     126           0 :     _setInitialGlobals();
     127           0 :     _setOverridingChannelRange(globalOverrideChans);
     128           0 :     _setOverridingCorrelations(globalOverrrideStokes);
     129           0 :     _parse(prependRegion.empty() ? text : prependRegion + "\n" + text, "", requireImageRegion);
     130           0 : }
     131             : 
     132           0 : 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           0 : vector<AsciiAnnotationFileLine> RegionTextParser::getLines() const {
     143           0 :     return _lines;
     144             : }
     145             : 
     146           0 : void RegionTextParser::_determineVersion(
     147             :     const String& chunk, const String& filename,
     148             :     const Int requireAtLeastThisVersion
     149             : ) {
     150           0 :     *_log << LogOrigin("RegionTextParser", __FUNCTION__);
     151           0 :     if (_fileVersion >= 0) {
     152             :         // already determined
     153           0 :         return;
     154             :     }
     155           0 :     ThrowIf(
     156             :         ! chunk.contains(MAGIC),
     157             :         _ORIGIN + "File " + filename
     158             :         + " does not contain CASA region text file magic value"
     159             :     );
     160           0 :     Regex version(MAGIC.regexp() + "v[0-9]+");
     161           0 :     if (chunk.contains(version)) {
     162           0 :         const auto vString = chunk.substr(6);
     163           0 :         auto done = false;
     164           0 :         auto count = 1;
     165           0 :         auto oldVersion = -2000;
     166           0 :         while (! done) {
     167             :             try {
     168           0 :                 _fileVersion = String::toInt(vString.substr(0, count));
     169           0 :                 ++count;
     170           0 :                 if (_fileVersion == oldVersion) {
     171           0 :                     done = true;
     172             :                 }
     173             :                 else {
     174           0 :                     oldVersion = _fileVersion;
     175             :                 }
     176             :             }
     177           0 :             catch (const AipsError&) {
     178           0 :                 done = true;
     179           0 :             }
     180             :         }
     181           0 :         if (_fileVersion < requireAtLeastThisVersion) {
     182           0 :             *_log << _ORIGIN << "File version " << _fileVersion
     183             :                 << " is less than required version "
     184           0 :                 << requireAtLeastThisVersion << LogIO::EXCEPTION;
     185             :         }
     186           0 :         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           0 :         *_log << LogIO::NORMAL << _ORIGIN << "Found spec version "
     195           0 :             << _fileVersion << LogIO::POST;
     196           0 :     }
     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           0 : }
     207             : 
     208           0 : void RegionTextParser::_parse(const String& contents, const String& fileDesc, bool requireImageRegion) {
     209           0 :     _log->origin(LogOrigin("AsciiRegionFileParser", __func__));
     210           0 :     static const Regex startAnn("^ann[[:space:]]+");
     211           0 :     static const Regex startDiff("^-[[:space:]]*");
     212           0 :     static const Regex startGlobal("^global[[:space:]]+");
     213           0 :     AnnotationBase::unitInit();
     214           0 :     auto lines = stringToVector(contents, '\n');
     215           0 :     uInt lineCount = 0;
     216             :     auto qFreqs = _overridingFreqRange
     217             :         ? std::pair<Quantity, Quantity>(
     218           0 :             Quantity(_overridingFreqRange->first.getValue().getValue(), "Hz"),
     219           0 :             Quantity(_overridingFreqRange->second.getValue().getValue(), "Hz")
     220             :         )
     221           0 :         : std::pair<Quantity, Quantity>(Quantity(0), Quantity(0));
     222           0 :     for(auto iter=lines.cbegin(); iter!=lines.cend(); ++iter) {
     223           0 :         ++lineCount;
     224           0 :         Bool annOnly = false;
     225           0 :         ostringstream preambleoss;
     226           0 :         preambleoss << fileDesc + " line# " << lineCount << ": ";
     227           0 :         const auto preamble = preambleoss.str();
     228           0 :         Bool difference = false;
     229           0 :         iter->trim();
     230           0 :         if (
     231           0 :             iter->empty() || iter->startsWith("#")
     232             :         ) {
     233             :             // ignore comments and blank lines
     234           0 :             _addLine(AsciiAnnotationFileLine(*iter));
     235           0 :             continue;
     236             :         }
     237           0 :         auto consumeMe = *iter;
     238             :         // consumeMe.downcase();
     239             :         Bool spectralParmsUpdated;
     240           0 :         ParamSet newParams;
     241           0 :         if (consumeMe.contains(startDiff)) {
     242           0 :             difference = true;
     243             :             // consume the difference character to allow further processing of string
     244           0 :             consumeMe.del(0, 1);
     245           0 :             consumeMe.trim();
     246           0 :             *_log << LogIO::NORMAL << preamble << "difference found" << LogIO::POST;
     247             :         }
     248           0 :         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           0 :         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           0 :         }
     277             :         // now look for per-line shapes and annotations
     278           0 :         Vector<Quantity> qDirs;
     279           0 :         vector<Quantity> quantities;
     280           0 :         String textString;
     281           0 :         const auto annType = _getAnnotationType(
     282             :             qDirs, quantities, textString, consumeMe, preamble
     283             :         );
     284             :         ParamSet currentParamSet = _getCurrentParamSet(
     285             :             spectralParmsUpdated, newParams, consumeMe, preamble
     286           0 :         );
     287           0 :         if (
     288           0 :             newParams.find(AnnotationBase::LABEL) == newParams.end()
     289           0 :             || newParams[AnnotationBase::LABEL].stringVal.empty()
     290             :         ) {
     291           0 :             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           0 :             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           0 :             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           0 :         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           0 :             newParams[AnnotationBase::LABELCOLOR] = newParams[AnnotationBase::COLOR];
     311             :         }
     312           0 :         if (_csys.hasSpectralAxis()) {
     313           0 :             if(spectralParmsUpdated) {
     314           0 :                 qFreqs = _quantitiesFromFrequencyString(
     315           0 :                     currentParamSet[AnnotationBase::RANGE].stringVal, preamble
     316           0 :                 );
     317             :             }
     318           0 :             else if(
     319           0 :                 _currentGlobals.find(AnnotationBase::RANGE)
     320           0 :                 == _currentGlobals.end()
     321           0 :                 || ! _currentGlobals.at(AnnotationBase::RANGE).freqRange
     322             :             ) {
     323             :                 // no global frequency range, so use entire freq span
     324           0 :                 qFreqs = std::pair<Quantity, Quantity>(Quantity(0), Quantity(0));
     325             :             }
     326             :         }
     327           0 :         auto globalsLessLocal = _currentGlobals;
     328           0 :         for (
     329           0 :             auto iter=newParams.cbegin();
     330           0 :             iter != newParams.cend(); ++iter
     331             :         ) {
     332           0 :             AnnotationBase::Keyword key = iter->first;
     333           0 :             if (globalsLessLocal.find(key) != globalsLessLocal.end()) {
     334           0 :                 globalsLessLocal.erase(key);
     335             :             }
     336             :         }
     337           0 :         _globalKeysToApply.resize(globalsLessLocal.size(), false);
     338           0 :         uInt i = 0;
     339           0 :         for (
     340           0 :             ParamSet::const_iterator iter=globalsLessLocal.begin();
     341           0 :             iter != globalsLessLocal.end(); ++iter
     342             :         ) {
     343           0 :             _globalKeysToApply[i] = iter->first;
     344           0 :             ++i;
     345             :         }
     346           0 :         _createAnnotation(
     347             :             annType, qDirs, qFreqs, quantities, textString,
     348             :             currentParamSet, annOnly, difference, preamble,
     349             :             requireImageRegion
     350             :         );
     351           0 :     }
     352           0 :     if (_verbose) {
     353           0 :         *_log << LogIO::NORMAL << "Combined " << _regions
     354           0 :             << " image regions (which excludes any annotation regions)" << LogIO::POST;
     355           0 :         if (_regions > 0) {
     356           0 :             *_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           0 :                 << "only partially covered by the region(s)." << LogIO::POST;
     359             :         }
     360             :     }
     361           0 : }
     362             : 
     363           0 : void RegionTextParser::_addLine(const AsciiAnnotationFileLine& line) {
     364           0 :     _lines.push_back(line);
     365           0 : }
     366             : 
     367           0 : 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           0 :         "\\[" + sOnePair + ",[^\\[,]+\\]";
     375             :     const static String sOnePairAndText =
     376           0 :         "\\[" + sOnePair + ",[[:space:]]*[\"\'].*[\"\'][[:space:]]*\\]";
     377           0 :     const static String sTwoPair = bTwoPair + "\\]";
     378           0 :     const static Regex startTwoPair("^" + sTwoPair);
     379           0 :     const static Regex startOnePairAndText("^" + sOnePairAndText);
     380             :     const static String sTwoPairOneSingle = bTwoPair
     381           0 :         + ",[[:space:]]*[^\\[,]+[[:space:]]*\\]";
     382           0 :     const static Regex startTwoPairOneSingle("^" + sTwoPairOneSingle);
     383           0 :     const static Regex startOnePairOneSingle("^" + sOnePairOneSingle);
     384           0 :     consumeMe.trim();
     385           0 :     String tmp = consumeMe.through(Regex("[[:alpha:]]+"));
     386           0 :     consumeMe.del(0, (Int)tmp.length());
     387           0 :     consumeMe.trim();
     388           0 :     auto annotationType = AnnotationBase::typeFromString(tmp);
     389           0 :     std::pair<Quantity, Quantity> myPair;
     390           0 :     switch(annotationType) {
     391           0 :     case AnnotationBase::RECT_BOX:
     392           0 :         ThrowIf(
     393             :             ! consumeMe.contains(startTwoPair),
     394             :             preamble + "Illegal box specification "
     395             :         );
     396           0 :         qDirs = _extractNQuantityPairs(consumeMe, preamble);
     397             : 
     398           0 :         if (qDirs.size() != 4) {
     399           0 :             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           0 :         break;
     405           0 :     case AnnotationBase::CENTER_BOX:
     406           0 :         ThrowIf(
     407             :             ! consumeMe.contains(startTwoPair),
     408             :             preamble + "Illegal center box specification " + consumeMe
     409             :         );
     410           0 :         qDirs.resize(2);
     411           0 :         quantities.resize(2);
     412             :         {
     413           0 :             Vector<Quantity> qs = _extractNQuantityPairs(consumeMe, preamble);
     414           0 :             qDirs[0] = qs[0];
     415           0 :             qDirs[1] = qs[1];
     416           0 :             quantities[0] = qs[2];
     417           0 :             quantities[1] = qs[3];
     418           0 :         }
     419           0 :         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           0 :         }
     435           0 :         break;
     436           0 :     case AnnotationBase::POLYGON:
     437             :         // Polygon definitions can be very long with many points.
     438             :         // Testing entire polygon string syntax causes regex seg fault.
     439           0 :         ThrowIf(
     440             :             ! (
     441             :                consumeMe.contains(Regex("^ *\\[ *\\["))
     442             :                && consumeMe.contains(Regex("\\] *\\]"))
     443             :             ), preamble + "Illegal polygon specification " + consumeMe
     444             :         );
     445             :         {
     446           0 :             Vector<Quantity> qs = _extractNQuantityPairs(consumeMe, preamble);
     447           0 :             qDirs.resize(qs.size());
     448           0 :             qDirs = qs;
     449           0 :         }
     450           0 :         break;
     451           0 :     case AnnotationBase::CIRCLE:
     452           0 :         ThrowIf(
     453             :             ! consumeMe.contains(startOnePairOneSingle),
     454             :             preamble + "Illegal circle specification " + consumeMe
     455             :         );
     456           0 :         qDirs.resize(2);
     457           0 :         quantities.resize(1);
     458             :         {
     459             :             Vector<Quantity> qs = _extractQuantityPairAndSingleQuantity(
     460             :                 consumeMe, preamble
     461           0 :             );
     462           0 :             qDirs[0] = qs[0];
     463           0 :             qDirs[1] = qs[1];
     464           0 :             quantities[0] = qs[2];
     465           0 :         }
     466           0 :         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           0 :         }
     483           0 :         break;
     484           0 :     case AnnotationBase::ELLIPSE:
     485           0 :         if (! consumeMe.contains(startTwoPairOneSingle)) {
     486           0 :             *_log << preamble << "Illegal ellipse specification "
     487           0 :                 << consumeMe << LogIO::EXCEPTION;
     488             :         }
     489           0 :         qDirs.resize(2);
     490           0 :         quantities.resize(3);
     491             :         {
     492             :             Vector<Quantity> qs = _extractTwoQuantityPairsAndSingleQuantity(
     493             :                 consumeMe, preamble
     494           0 :             );
     495           0 :             qDirs[0] = qs[0];
     496           0 :             qDirs[1] = qs[1];
     497           0 :             quantities[0] = qs[2];
     498           0 :             quantities[1] = qs[3];
     499           0 :             quantities[2] = qs[4];
     500           0 :         }
     501           0 :         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           0 :             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           0 :             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           0 :             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           0 :     return annotationType;
     569           0 : }
     570             : 
     571           0 : 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           0 :     ParamSet parms;
     579           0 :     spectralParmsUpdated = false;
     580           0 :     auto consumeMe = text;
     581             :     // get key-value pairs on the line
     582           0 :     while (consumeMe.size() > 0) {
     583           0 :         ParamValue paramValue;
     584           0 :         auto key = AnnotationBase::UNKNOWN_KEYWORD;
     585           0 :         consumeMe.trim();
     586           0 :         consumeMe.ltrim(',');
     587           0 :         consumeMe.trim();
     588           0 :         ThrowIf(
     589             :             ! consumeMe.contains('='),
     590             :             preamble + "Illegal extra characters on line ("
     591             :                 + consumeMe + "). Did you forget a '='?"
     592             :         );
     593           0 :         const auto equalPos = consumeMe.find('=');
     594           0 :         auto keyword = consumeMe.substr(0, equalPos);
     595           0 :         keyword.trim();
     596           0 :         keyword.downcase();
     597           0 :         consumeMe.del(0, (Int)equalPos + 1);
     598           0 :         consumeMe.trim();
     599           0 :         if (keyword == "label") {
     600           0 :             key = AnnotationBase::LABEL;
     601           0 :             paramValue.stringVal = _doLabel(consumeMe, preamble);
     602             :         }
     603             :         else {
     604           0 :             paramValue.stringVal = _getKeyValue(consumeMe, preamble);
     605           0 :             if (keyword == "coord") {
     606           0 :                 key = AnnotationBase::COORD;
     607             :             }
     608           0 :             else if (keyword == "corr" && ! overridingCorrRange) {
     609           0 :                 if (csys.hasPolarizationCoordinate()) {
     610           0 :                     key = AnnotationBase::CORR;
     611           0 :                     paramValue.stokes = _stokesFromString(
     612             :                         paramValue.stringVal, preamble
     613           0 :                     );
     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           0 :             else if (
     623           0 :                 ! overridingFreqRange
     624           0 :                 && (
     625           0 :                     keyword == "frame" || keyword == "range"
     626           0 :                     || keyword == "veltype" || keyword == "restfreq"
     627             :                 )
     628             :             ) {
     629           0 :                 spectralParmsUpdated = true;
     630           0 :                 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           0 :                 else if (keyword == "frame") {
     638           0 :                     key = AnnotationBase::FRAME;
     639             :                 }
     640           0 :                 else if (keyword == "range") {
     641           0 :                     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           0 :                 }
     655             :             }
     656           0 :             else if (keyword == "linewidth") {
     657           0 :                 key = AnnotationBase::LINEWIDTH;
     658           0 :                 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           0 :                 paramValue.intVal = String::toInt(paramValue.stringVal);
     663             :             }
     664           0 :             else if (keyword == "linestyle") {
     665           0 :                 key = AnnotationBase::LINESTYLE;
     666           0 :                 paramValue.lineStyleVal = AnnotationBase::lineStyleFromString(
     667             :                     paramValue.stringVal
     668             :                 );
     669             :             }
     670           0 :             else if (keyword == "symsize") {
     671           0 :                 key = AnnotationBase::SYMSIZE;
     672           0 :                 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           0 :                 paramValue.intVal = String::toInt(paramValue.stringVal);
     677             :             }
     678           0 :             else if (keyword == "symthick") {
     679           0 :                 key = AnnotationBase::SYMTHICK;
     680           0 :                 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           0 :                 paramValue.intVal = String::toInt(paramValue.stringVal);
     685             :             }
     686           0 :             else if (keyword == "color") {
     687           0 :                 key = AnnotationBase::COLOR;
     688             :             }
     689           0 :             else if (keyword == "font") {
     690           0 :                 key = AnnotationBase::FONT;
     691             :             }
     692           0 :             else if (keyword == "fontsize") {
     693           0 :                 key = AnnotationBase::FONTSIZE;
     694           0 :                 paramValue.intVal = String::toInt(paramValue.stringVal);
     695             :             }
     696           0 :             else if (keyword == "fontstyle") {
     697           0 :                 key = AnnotationBase::FONTSTYLE;
     698           0 :                 paramValue.fontStyleVal = AnnotationBase::fontStyleFromString(
     699             :                     paramValue.stringVal
     700             :                 );
     701             :             }
     702           0 :             else if (keyword == "usetex") {
     703           0 :                 String v = paramValue.stringVal;
     704           0 :                 v.downcase();
     705           0 :                 key = AnnotationBase::USETEX;
     706           0 :                 if (
     707           0 :                     v != "true"  && v != "t"
     708           0 :                     && v != "false" && v != "f"
     709             :                 ) {
     710             :                     log << preamble << "Cannot determine boolean value of usetex"
     711           0 :                         << paramValue.stringVal << LogIO::EXCEPTION;
     712             :                 }
     713           0 :                 paramValue.boolVal = (v == "true" || v == "t");
     714           0 :             }
     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           0 :                 }
     750           0 :                 key = AnnotationBase::LABELOFF;
     751           0 :             }
     752             :             else {
     753           0 :                 ThrowCc(preamble + "Unrecognized key " + keyword);
     754             :             }
     755             :         }
     756           0 :         consumeMe.trim();
     757           0 :         if (key != AnnotationBase::UNKNOWN_KEYWORD) {
     758           0 :             parms[key] = paramValue;
     759             :         }
     760           0 :     }
     761           0 :     return parms;
     762           0 : }
     763             : 
     764             : RegionTextParser::ParamSet
     765           0 : RegionTextParser::_getCurrentParamSet(
     766             :     Bool& spectralParmsUpdated, ParamSet& newParams,
     767             :     String& consumeMe, const String& preamble
     768             : ) const {
     769           0 :     auto currentParams = _currentGlobals;
     770           0 :     newParams = getParamSet(
     771             :         spectralParmsUpdated,
     772           0 :         *_log, consumeMe, preamble, _csys, _overridingFreqRange,
     773           0 :         _overridingCorrRange
     774           0 :     );
     775           0 :     for (const auto& p: newParams) {
     776           0 :         currentParams[p.first] = p.second;
     777             :     }
     778           0 :     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           0 :     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           0 :     return currentParams;
     789           0 : }
     790             : 
     791           0 : 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           0 :     auto cString = "[" + freqString + "]";
     797           0 :     ThrowIf(! cString.contains(startOnePair),
     798             :         preamble + "Incorrect spectral range specification ("
     799             :         + freqString + ")"
     800             :     );
     801             :     return _extractSingleQuantityPair(
     802             :         cString, preamble
     803           0 :     );
     804           0 : }
     805             : 
     806           0 : 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           0 :     CountedPtr<AnnotationBase> annotation;
     817           0 :     Vector<Stokes::StokesTypes> stokes(0);
     818           0 :     if (
     819           0 :         currentParamSet.find(AnnotationBase::CORR) != currentParamSet.end()
     820           0 :         && _csys.hasPolarizationCoordinate()
     821             :     ) {
     822           0 :         stokes.resize(currentParamSet.at(AnnotationBase::CORR).stokes.size());
     823           0 :         stokes = currentParamSet.at(AnnotationBase::CORR).stokes;
     824             :     }
     825           0 :     auto dirRefFrame = currentParamSet.at(AnnotationBase::COORD).stringVal;
     826           0 :     auto freqRefFrame = currentParamSet.find(AnnotationBase::FRAME) == currentParamSet.end()
     827           0 :         ? "" : currentParamSet.at(AnnotationBase::FRAME).stringVal;
     828           0 :     auto doppler = currentParamSet.find(AnnotationBase::VELTYPE) == currentParamSet.end()
     829           0 :         ? "" :    currentParamSet.at(AnnotationBase::VELTYPE).stringVal;
     830           0 :     Quantity restfreq;
     831           0 :     if (
     832           0 :         currentParamSet.find(AnnotationBase::RESTFREQ) != currentParamSet.end()
     833           0 :         && ! readQuantity(
     834           0 :             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           0 :     switch (annType) {
     843           0 :         case AnnotationBase::RECT_BOX:
     844             :             annotation = new AnnRectBox(
     845           0 :                 qDirs[0], qDirs[1], qDirs[2], qDirs[3],
     846           0 :                 dirRefFrame, _csys, _imShape, qFreqs.first, qFreqs.second,
     847             :                 freqRefFrame, doppler, restfreq, stokes,
     848             :                 annOnly, requireImageRegion
     849           0 :             );
     850           0 :             break;
     851           0 :         case AnnotationBase::CENTER_BOX:
     852             :             annotation = new AnnCenterBox(
     853           0 :                 qDirs[0], qDirs[1], quantities[0], quantities[1],
     854           0 :                 dirRefFrame, _csys, _imShape, qFreqs.first, qFreqs.second,
     855             :                 freqRefFrame, doppler, restfreq, stokes,
     856             :                 annOnly, requireImageRegion
     857           0 :             );
     858           0 :             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           0 :         case AnnotationBase::POLYGON:
     867             :             {
     868           0 :                 Vector<Quantity> x(qDirs.size()/2);
     869           0 :                 Vector<Quantity> y(qDirs.size()/2);
     870           0 :                 for (uInt i=0; i<x.size(); ++i) {
     871           0 :                     x[i] = qDirs[2*i];
     872           0 :                     y[i] = qDirs[2*i + 1];
     873             :                 }
     874             :                 annotation = new AnnPolygon(
     875           0 :                     x, y, dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
     876             :                     freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
     877           0 :                 );
     878           0 :             }
     879           0 :             break;
     880           0 :         case AnnotationBase::CIRCLE:
     881           0 :             if (
     882           0 :                 quantities[0].getUnit() == "pix"
     883           0 :                 && ! _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           0 :                     qDirs[0], qDirs[1], quantities[0],
     896           0 :                     dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
     897             :                     freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
     898           0 :                 );
     899             :             }
     900           0 :             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           0 :         case AnnotationBase::ELLIPSE:
     909             :             annotation = new AnnEllipse(
     910           0 :                 qDirs[0], qDirs[1], quantities[0], quantities[1], quantities[2],
     911           0 :                 dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
     912             :                 freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
     913           0 :             );
     914           0 :             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           0 :     }
     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           0 :     }
     968           0 :     catch (const AipsError& x) {
     969           0 :         ThrowCc(preamble + x.getMesg());
     970           0 :     }
     971           0 :     if (annotation->isRegion()) {
     972           0 :         dynamic_cast<AnnRegion *>(annotation.get())->setDifference(isDifference);
     973           0 :         if (! annOnly) {
     974           0 :             ++_regions;
     975             :         }
     976             :     }
     977           0 :     annotation->setLineWidth(currentParamSet.at(AnnotationBase::LINEWIDTH).intVal);
     978           0 :     annotation->setLineStyle(
     979             :         AnnotationBase::lineStyleFromString(
     980           0 :             currentParamSet.at(AnnotationBase::LINESTYLE).stringVal
     981             :         )
     982             :     );
     983           0 :     annotation->setSymbolSize(currentParamSet.at(AnnotationBase::SYMSIZE).intVal);
     984           0 :     annotation->setSymbolThickness(currentParamSet.at(AnnotationBase::SYMTHICK).intVal);
     985           0 :     annotation->setColor(currentParamSet.at(AnnotationBase::COLOR).stringVal);
     986           0 :     annotation->setFont(currentParamSet.at(AnnotationBase::FONT).stringVal);
     987           0 :     annotation->setFontSize(currentParamSet.at(AnnotationBase::FONTSIZE).intVal);
     988           0 :     annotation->setFontStyle(
     989           0 :         AnnotationBase::fontStyleFromString(
     990           0 :             currentParamSet.at(AnnotationBase::FONTSTYLE).stringVal
     991             :         )
     992             :     );
     993           0 :     annotation->setUseTex(currentParamSet.at(AnnotationBase::USETEX).boolVal);
     994           0 :     if (
     995           0 :         currentParamSet.find(AnnotationBase::LABEL) != currentParamSet.end()
     996           0 :         && ! currentParamSet.find(AnnotationBase::LABEL)->second.stringVal.empty()
     997             :     ) {
     998           0 :         annotation->setLabel(currentParamSet.at(AnnotationBase::LABEL).stringVal);
     999           0 :         annotation->setLabelColor(currentParamSet.at(AnnotationBase::LABELCOLOR).stringVal);
    1000           0 :         annotation->setLabelColor(currentParamSet.at(AnnotationBase::LABELCOLOR).stringVal);
    1001           0 :         annotation->setLabelPosition(currentParamSet.at(AnnotationBase::LABELPOS).stringVal);
    1002           0 :         annotation->setLabelOffset(currentParamSet.at(AnnotationBase::LABELOFF).intVec);
    1003             :     }
    1004           0 :     annotation->setGlobals(_globalKeysToApply);
    1005           0 :     AsciiAnnotationFileLine line(annotation);
    1006           0 :     _addLine(line);
    1007           0 : }
    1008             : 
    1009           0 : Array<String> RegionTextParser::_extractTwoPairs(uInt& end, const String& string) const {
    1010           0 :     end = 0;
    1011           0 :     Int firstBegin = string.find('[', 1);
    1012           0 :     Int firstEnd = string.find(']', firstBegin);
    1013           0 :     auto firstPair = string.substr(firstBegin, firstEnd - firstBegin + 1);
    1014           0 :     Int secondBegin = string.find('[', firstEnd);
    1015           0 :     Int secondEnd = string.find(']', secondBegin);
    1016           0 :     auto secondPair = string.substr(secondBegin, secondEnd - secondBegin + 1);
    1017           0 :     auto first = _extractSinglePair(firstPair);
    1018           0 :     auto second = _extractSinglePair(secondPair);
    1019             : 
    1020           0 :     end = secondEnd;
    1021           0 :     Array<String> ret(IPosition(2, 2, 2));
    1022           0 :     ret(IPosition(2, 0, 0)) = first[0];
    1023           0 :     ret(IPosition(2, 0, 1)) = first[1];
    1024           0 :     ret(IPosition(2, 1, 0)) = second[0];
    1025           0 :     ret(IPosition(2, 1, 1)) = second[1];
    1026           0 :     return ret;
    1027           0 : }
    1028             : 
    1029           0 : Vector<String> RegionTextParser::_extractSinglePair(const String& string) {
    1030             :     Char quotes[2];
    1031           0 :     quotes[0] = '\'';
    1032           0 :     quotes[1] = '"';
    1033           0 :     Int firstBegin = string.find('[', 0) + 1;
    1034           0 :     Int firstEnd = string.find(',', firstBegin);
    1035           0 :     auto first = string.substr(firstBegin, firstEnd - firstBegin);
    1036           0 :     first.trim();
    1037           0 :     first.trim(quotes, 2);
    1038           0 :     Int secondBegin = firstEnd + 1;
    1039           0 :     Int secondEnd = string.find(']', secondBegin);
    1040           0 :     auto second = string.substr(secondBegin, secondEnd - secondBegin);
    1041           0 :     second.trim();
    1042           0 :     second.trim(quotes, 2);
    1043           0 :     Vector<String> ret(2);
    1044           0 :     ret[0] = first;
    1045           0 :     ret[1] = second;
    1046           0 :     return ret;
    1047           0 : }
    1048             : 
    1049           0 : String RegionTextParser::_doLabel(
    1050             :     String& consumeMe, const String& preamble
    1051             : ) {
    1052           0 :     const auto firstChar = consumeMe.firstchar();
    1053           0 :     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           0 :     }
    1059           0 :     const auto posCloseQuote = consumeMe.find(firstChar, 1);
    1060           0 :     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           0 :     }
    1066           0 :     const auto label = consumeMe.substr(1, posCloseQuote - 1);
    1067           0 :     consumeMe.del(0, (Int)posCloseQuote + 1);
    1068           0 :     return label;
    1069           0 : }
    1070             : 
    1071           0 : String RegionTextParser::_getKeyValue(
    1072             :     String& consumeMe, const String& preamble
    1073             : ) {
    1074           0 :     String value;
    1075           0 :     if (consumeMe.startsWith("[")) {
    1076           0 :         if (! consumeMe.contains("]")) {
    1077           0 :             ostringstream err;
    1078           0 :             err << preamble << "Unmatched open bracket: "
    1079           0 :                 << consumeMe;
    1080           0 :             ThrowCc(err.str());
    1081           0 :         }
    1082           0 :         Int closeBracketPos = consumeMe.find("]");
    1083             :         // don't save the open and close brackets
    1084           0 :         value = consumeMe.substr(1, closeBracketPos - 1);
    1085           0 :         consumeMe.del(0, closeBracketPos + 1);
    1086             :     }
    1087           0 :     if (consumeMe.contains(",")) {
    1088           0 :         Int commaPos = consumeMe.find(",");
    1089           0 :         if (value.empty()) {
    1090           0 :             value = consumeMe.substr(0, commaPos);
    1091             :         }
    1092           0 :         consumeMe.del(0, commaPos);
    1093             :     }
    1094           0 :     else if (value.empty()) {
    1095             :         // last key-value pair on the line
    1096           0 :         value = consumeMe;
    1097           0 :         consumeMe = "";
    1098             :     }
    1099           0 :     consumeMe.trim();
    1100             :     Char quotes[2];
    1101           0 :     quotes[0] = '\'';
    1102           0 :     quotes[1] = '"';
    1103           0 :     value.trim();
    1104           0 :     value.trim(quotes, 2);
    1105           0 :     value.trim();
    1106           0 :     return value;
    1107           0 : }
    1108             : 
    1109           0 : Vector<Quantity> RegionTextParser::_extractTwoQuantityPairsAndSingleQuantity(
    1110             :     String& consumeMe, const String& preamble
    1111             : ) const {
    1112             :     Vector<Quantity> quantities = _extractTwoQuantityPairs(
    1113             :         consumeMe, preamble
    1114           0 :     );
    1115           0 :     consumeMe.trim();
    1116           0 :     consumeMe.ltrim(',');
    1117           0 :     consumeMe.trim();
    1118             :     Char quotes[2];
    1119           0 :     quotes[0] = '\'';
    1120           0 :     quotes[1] = '"';
    1121             : 
    1122           0 :     Int end = consumeMe.find(']', 0);
    1123           0 :     String qString = consumeMe.substr(0, end);
    1124           0 :     qString.trim();
    1125             : 
    1126           0 :     qString.trim(quotes, 2);
    1127           0 :     quantities.resize(5, true);
    1128           0 :     if (! readQuantity(quantities[4], qString)) {
    1129           0 :         *_log << preamble + "Could not convert "
    1130           0 :             << qString << " to quantity." << LogIO::EXCEPTION;
    1131             :     }
    1132           0 :     consumeMe.del(0, end + 1);
    1133           0 :     return quantities;
    1134           0 : }
    1135             : 
    1136           0 : 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           0 :     consumeMe.del(0, 1);
    1143           0 :     SubString pairString = consumeMe.through(startOnePair);
    1144           0 :     quantities = _extractSingleQuantityPair(pairString, preamble);
    1145           0 :     consumeMe.del(0, (Int)pairString.length() + 1);
    1146           0 :     consumeMe.trim();
    1147           0 :     consumeMe.ltrim(',');
    1148           0 :     consumeMe.trim();
    1149           0 :     Int end = 0;
    1150           0 :     String::size_type startSearchPos = 0;
    1151           0 :     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           0 :     end = consumeMe.find(']', startSearchPos);
    1165           0 :     string = consumeMe.substr(0, end);
    1166           0 :     consumeMe.del(0, end + 1);
    1167           0 :     string.trim();
    1168             :     Char quotes[2];
    1169           0 :     quotes[0] = '\'';
    1170           0 :     quotes[1] = '"';
    1171           0 :     string.trim(quotes, 2);
    1172           0 :     string.trim();
    1173           0 : }
    1174             : 
    1175           0 : Vector<Quantity> RegionTextParser::_extractQuantityPairAndSingleQuantity(
    1176             :     String& consumeMe, const String& preamble
    1177             : ) const {
    1178           0 :     String qString;
    1179             : 
    1180           0 :     std::pair<Quantity, Quantity> myPair;
    1181           0 :     _extractQuantityPairAndString(
    1182             :         myPair, qString, consumeMe, preamble, false
    1183             :     );
    1184           0 :     Vector<Quantity> quantities(3);
    1185           0 :     quantities[0] = myPair.first;
    1186           0 :     quantities[1] = myPair.second;
    1187           0 :     ThrowIf(
    1188             :         ! readQuantity(quantities[2], qString),
    1189             :         preamble + "Could not convert "
    1190             :         + qString + " to quantity"
    1191             :     );
    1192           0 :     return quantities;
    1193           0 : }
    1194             : 
    1195           0 : Vector<Quantity> RegionTextParser::_extractTwoQuantityPairs(
    1196             :     String& consumeMe, const String& preamble
    1197             : ) const {
    1198           0 :     const Regex startbTwoPair("^" + bTwoPair);
    1199           0 :     String mySubstring = String(consumeMe).through(startbTwoPair);
    1200           0 :     uInt end = 0;
    1201           0 :     Array<String> pairs = _extractTwoPairs(end, mySubstring);
    1202           0 :     Vector<Quantity> quantities(4);
    1203             : 
    1204           0 :     for (uInt i=0; i<4; ++i) {
    1205           0 :         String desc("string " + String::toString(i));
    1206           0 :         String value = pairs(IPosition(2, i/2, i%2));
    1207           0 :         if (! readQuantity(quantities[i], value)) {
    1208           0 :             *_log << preamble << "Could not convert " << desc
    1209           0 :                 << " (" << value << ") to quantity." << LogIO::EXCEPTION;
    1210             :         }
    1211           0 :     }
    1212           0 :     consumeMe.del(0, (Int)end + 1);
    1213           0 :     return quantities;
    1214           0 : }
    1215             : 
    1216           0 : Vector<Quantity> RegionTextParser::_extractNQuantityPairs (
    1217             :         String& consumeMe, const String& preamble
    1218             : ) const {
    1219           0 :     String pairs = consumeMe.through(Regex("\\] *\\]"));
    1220           0 :     String nPairs(pairs);
    1221           0 :     consumeMe.del(0, (Int)pairs.length() + 1);
    1222           0 :     pairs.trim();
    1223             :     // remove the left most [
    1224           0 :     pairs.del(0, 1);
    1225           0 :     pairs.trim();
    1226           0 :     Vector<Quantity> qs(0);
    1227             : 
    1228             :     try {
    1229           0 :         while (pairs.length() > 1) {
    1230           0 :             std::pair<Quantity, Quantity> myqs = _extractSingleQuantityPair(pairs, preamble);
    1231           0 :             qs.resize(qs.size() + 2, true);
    1232           0 :             qs[qs.size() - 2] = myqs.first;
    1233           0 :             qs[qs.size() - 1] = myqs.second;
    1234           0 :             pairs.del(0, (Int)pairs.find(']', 0) + 1);
    1235           0 :             pairs.trim();
    1236           0 :             pairs.ltrim(',');
    1237           0 :             pairs.trim();
    1238           0 :         }
    1239             :     }
    1240           0 :     catch (const AipsError&) {
    1241           0 :         ThrowCc(
    1242             :             preamble + "Illegal polygon specification " + nPairs
    1243             :         );
    1244           0 :     }
    1245           0 :     return qs;
    1246           0 : }
    1247             : 
    1248           0 : std::pair<Quantity, Quantity> RegionTextParser::_extractSingleQuantityPair(
    1249             :     const String& pairString, const String& preamble
    1250             : ) const {
    1251           0 :     String mySubstring = String(pairString).through(sOnePair, 0);
    1252           0 :     Vector<String> pair = _extractSinglePair(mySubstring);
    1253           0 :     std::pair<Quantity, Quantity> quantities;
    1254           0 :     for (uInt i=0; i<2; ++i) {
    1255           0 :         String value = pair[i];
    1256           0 :         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           0 :     }
    1267           0 :     return quantities;
    1268           0 : }
    1269             : 
    1270           0 : void RegionTextParser::_setOverridingCorrelations(const String& globalOverrideStokes) {
    1271           0 :     if (globalOverrideStokes.empty() || ! _csys.hasPolarizationAxis()) {
    1272             :         // no global override specified
    1273           0 :         return;
    1274             :     }
    1275           0 :     String mycopy = globalOverrideStokes;
    1276           0 :     vector<String> myStokes = ParameterParser::stokesFromString(mycopy);
    1277           0 :     String myCommaSeperatedString;
    1278           0 :     vector<String>::const_iterator iter = myStokes.begin();
    1279           0 :     vector<String>::const_iterator end = myStokes.end();
    1280           0 :     uInt count = 0;
    1281           0 :     while(iter != end) {
    1282           0 :         myCommaSeperatedString += *iter;
    1283           0 :         if (count < myStokes.size() - 1) {
    1284           0 :             myCommaSeperatedString += ",";
    1285             :         }
    1286           0 :         ++count;
    1287           0 :         ++iter;
    1288             :     }
    1289           0 :     ParamValue corr;
    1290           0 :     corr.stokes = _stokesFromString(myCommaSeperatedString, String(__func__));
    1291           0 :     _currentGlobals[AnnotationBase::CORR] = corr;
    1292           0 :     _overridingCorrRange.reset((new Vector<Stokes::StokesTypes>(corr.stokes)));
    1293           0 : }
    1294             : 
    1295           0 : void RegionTextParser::_setOverridingChannelRange(
    1296             :     const String& globalOverrideChans
    1297             : ) {
    1298           0 :     if (globalOverrideChans.empty() || ! _csys.hasSpectralAxis()) {
    1299             :         // no global override specified
    1300           0 :         return;
    1301             :     }
    1302           0 :     uInt nSelectedChannels = 0;
    1303           0 :     uInt nChannels = _imShape[_csys.spectralAxisNumber(false)];
    1304             :     std::vector<uInt> myChanRange =  ParameterParser::spectralRangesFromChans(
    1305             :         nSelectedChannels, globalOverrideChans,
    1306             :         nChannels
    1307           0 :     );
    1308           0 :     uInt nRanges = myChanRange.size();
    1309           0 :     if (nRanges == 0) {
    1310             :         // no channel range specified
    1311           0 :         return;
    1312             :     }
    1313           0 :     ThrowIf(
    1314             :         nRanges > 2,
    1315             :         "Overriding spectral specification must be "
    1316             :         "limited to a sngle channel range"
    1317             :     );
    1318           0 :     MFrequency first, second;
    1319           0 :     const SpectralCoordinate specCoord = _csys.spectralCoordinate();
    1320           0 :     specCoord.toWorld(first, myChanRange[0]);
    1321           0 :     specCoord.toWorld(second, myChanRange[1]);
    1322           0 :     _overridingFreqRange.reset(new std::pair<MFrequency, MFrequency>(first, second));
    1323           0 :     ParamValue range;
    1324           0 :     range.freqRange = _overridingFreqRange;
    1325           0 :     _currentGlobals[AnnotationBase::RANGE] = range;
    1326             : 
    1327           0 :     ParamValue frame;
    1328           0 :     frame.intVal = specCoord.frequencySystem(false);
    1329           0 :     _currentGlobals[AnnotationBase::FRAME] = frame;
    1330             : 
    1331           0 :     ParamValue veltype;
    1332           0 :     veltype.intVal = specCoord.velocityDoppler();
    1333           0 :     _currentGlobals[AnnotationBase::VELTYPE] = veltype;
    1334             : 
    1335           0 :     ParamValue restfreq;
    1336           0 :     restfreq.stringVal = String::toString(
    1337           0 :         specCoord.restFrequency()
    1338           0 :     ) + "Hz";
    1339           0 :     _currentGlobals[AnnotationBase::RESTFREQ] = restfreq;
    1340           0 : }
    1341             : 
    1342             : Vector<Stokes::StokesTypes>
    1343           0 : RegionTextParser::_stokesFromString(
    1344             :     const String& stokes, const String& preamble
    1345             : ) {
    1346           0 :     const auto maxn = Stokes::NumberOfTypes;
    1347           0 :     std::unique_ptr<string[]> res(new string[maxn]);
    1348           0 :     Int nStokes = split(stokes, res.get( ), maxn, ",");
    1349           0 :     Vector<Stokes::StokesTypes> myTypes(nStokes);
    1350           0 :     for (Int i=0; i<nStokes; ++i) {
    1351           0 :         String x(res.get( )[i]);
    1352           0 :         x.trim();
    1353           0 :         myTypes[i] = Stokes::type(x);
    1354           0 :         if (myTypes[i] == Stokes::Undefined) {
    1355           0 :             throw AipsError(preamble + "Unknown correlation type " + x);
    1356             :         }
    1357           0 :     }
    1358           0 :     return myTypes;
    1359           0 : }
    1360             : 
    1361           0 : void RegionTextParser::_setInitialGlobals() {
    1362           0 :     ParamValue coord;
    1363           0 :     coord.intVal = _csys.directionCoordinate(
    1364           0 :         _csys.findCoordinate(Coordinate::DIRECTION)
    1365           0 :     ).directionType(false);
    1366           0 :     coord.stringVal = MDirection::showType(coord.intVal);
    1367           0 :     _currentGlobals[AnnotationBase::COORD] = coord;
    1368             : 
    1369           0 :     ParamValue range;
    1370           0 :     range.freqRange.reset();
    1371           0 :     _currentGlobals[AnnotationBase::RANGE] = range;
    1372             : 
    1373           0 :     ParamValue corr;
    1374           0 :     corr.stokes = Vector<Stokes::StokesTypes>(0);
    1375           0 :     _currentGlobals[AnnotationBase::CORR] = corr;
    1376             : 
    1377           0 :     if (_csys.hasSpectralAxis()) {
    1378             :         SpectralCoordinate spectral = _csys.spectralCoordinate(
    1379           0 :             _csys.findCoordinate(Coordinate::SPECTRAL)
    1380           0 :         );
    1381             : 
    1382           0 :         ParamValue frame;
    1383           0 :         frame.intVal = spectral.frequencySystem(false);
    1384           0 :         _currentGlobals[AnnotationBase::FRAME] = frame;
    1385             : 
    1386           0 :         ParamValue veltype;
    1387           0 :         veltype.intVal = spectral.velocityDoppler();
    1388           0 :         _currentGlobals[AnnotationBase::VELTYPE] = veltype;
    1389             : 
    1390           0 :         ParamValue restfreq;
    1391             :         // truncates value, not enough precision
    1392             :         // restfreq.stringVal = String::toString(spectral.restFrequency()) + "Hz";
    1393           0 :         ostringstream oss;
    1394           0 :         oss << std::setprecision(20) << spectral.restFrequency() << "Hz";
    1395           0 :         restfreq.stringVal = oss.str();
    1396           0 :         _currentGlobals[AnnotationBase::RESTFREQ] = restfreq;
    1397           0 :     }
    1398           0 :     ParamValue linewidth;
    1399           0 :     linewidth.intVal = AnnotationBase::DEFAULT_LINEWIDTH;
    1400           0 :     _currentGlobals[AnnotationBase::LINEWIDTH] = linewidth;
    1401             : 
    1402           0 :     ParamValue linestyle;
    1403           0 :     linestyle.lineStyleVal = AnnotationBase::DEFAULT_LINESTYLE;
    1404           0 :     _currentGlobals[AnnotationBase::LINESTYLE] = linestyle;
    1405             : 
    1406           0 :     ParamValue symsize;
    1407           0 :     symsize.intVal = AnnotationBase::DEFAULT_SYMBOLSIZE;
    1408           0 :     _currentGlobals[AnnotationBase::SYMSIZE] = symsize;
    1409             : 
    1410           0 :     ParamValue symthick;
    1411           0 :     symthick.intVal = AnnotationBase::DEFAULT_SYMBOLTHICKNESS;
    1412           0 :     _currentGlobals[AnnotationBase::SYMTHICK] = symthick;
    1413             : 
    1414           0 :     ParamValue color;
    1415           0 :     color.color = AnnotationBase::DEFAULT_COLOR;
    1416           0 :     color.stringVal = AnnotationBase::colorToString(color.color);
    1417           0 :     _currentGlobals[AnnotationBase::COLOR] = color;
    1418             : 
    1419           0 :     ParamValue font;
    1420           0 :     font.stringVal = AnnotationBase::DEFAULT_FONT;
    1421           0 :     _currentGlobals[AnnotationBase::FONT] = font;
    1422             : 
    1423           0 :     ParamValue fontsize;
    1424           0 :     fontsize.intVal = AnnotationBase::DEFAULT_FONTSIZE;
    1425           0 :     _currentGlobals[AnnotationBase::FONTSIZE] = fontsize;
    1426             : 
    1427           0 :     ParamValue fontstyle;
    1428           0 :     fontstyle.fontStyleVal = AnnotationBase::DEFAULT_FONTSTYLE;
    1429           0 :     _currentGlobals[AnnotationBase::FONTSTYLE] = fontstyle;
    1430             : 
    1431           0 :     ParamValue usetex;
    1432           0 :     usetex.boolVal = AnnotationBase::DEFAULT_USETEX;
    1433           0 :     _currentGlobals[AnnotationBase::USETEX] = usetex;
    1434             : 
    1435           0 :     ParamValue labelcolor;
    1436           0 :     labelcolor.color = AnnotationBase::DEFAULT_LABELCOLOR;
    1437           0 :     labelcolor.stringVal = AnnotationBase::colorToString(labelcolor.color);
    1438           0 :     _currentGlobals[AnnotationBase::LABELCOLOR] = labelcolor;
    1439             : 
    1440           0 :     ParamValue labelpos;
    1441           0 :     labelpos.stringVal = AnnotationBase::DEFAULT_LABELPOS;
    1442           0 :     _currentGlobals[AnnotationBase::LABELPOS] = labelpos;
    1443             : 
    1444           0 :     ParamValue labeloff;
    1445           0 :     labeloff.intVec = AnnotationBase::DEFAULT_LABELOFF;
    1446           0 :     _currentGlobals[AnnotationBase::LABELOFF] = labeloff;
    1447           0 : }
    1448             : 
    1449             : }
    1450             : 

Generated by: LCOV version 1.16