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 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 : _lines(), _globalKeysToApply(), _fileVersion(-1), _imShape(imShape),
68 0 : _regions(0), _verbose(verbose) {
69 0 : RegularFile file(filename);
70 0 : if (! file.exists()) {
71 : throw AipsError(
72 0 : _ORIGIN + "File " + filename + " does not exist."
73 0 : );
74 : }
75 0 : if (! file.isReadable()) {
76 : throw AipsError(
77 0 : _ORIGIN + "File " + filename + " is not readable."
78 0 : );
79 : }
80 0 : if (! _csys.hasDirectionCoordinate()) {
81 : 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 : }
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 : _globalKeysToApply(), _fileVersion(-1), _imShape(imShape), _regions(0),
120 0 : _verbose(verbose) {
121 0 : if (! _csys.hasDirectionCoordinate()) {
122 : 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 : }
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 : }
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 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 : }
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 : }
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 : 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 : }
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 : }
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 : }
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 : }
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 : }
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 : }
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 : 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 0 : return annotationType;
569 : }
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 : }
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 : }
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 0 : consumeMe.trim();
757 0 : if (key != AnnotationBase::UNKNOWN_KEYWORD) {
758 0 : parms[key] = paramValue;
759 : }
760 : }
761 0 : return parms;
762 : }
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 : }
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 : }
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 : }
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 : }
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 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 : }
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 : }
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 : }
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 : }
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 : }
1066 0 : const auto label = consumeMe.substr(1, posCloseQuote - 1);
1067 0 : consumeMe.del(0, (Int)posCloseQuote + 1);
1068 0 : return label;
1069 : }
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 : }
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 : }
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 : }
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 : }
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 : }
1212 0 : consumeMe.del(0, (Int)end + 1);
1213 0 : return quantities;
1214 : }
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 : }
1239 : }
1240 0 : catch (const AipsError&) {
1241 0 : ThrowCc(
1242 : preamble + "Illegal polygon specification " + nPairs
1243 : );
1244 : }
1245 0 : return qs;
1246 : }
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 : }
1267 0 : return quantities;
1268 : }
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 : }
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 : }
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 : }
1358 0 : return myTypes;
1359 : }
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 : }
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 :
|