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