Line data Source code
1 : //# Copyright (C) 1993,1994,1995,1996,1999,2001
2 : //# Associated Universities, Inc. Washington DC, USA.
3 : //#
4 : //# This library is free software; you can redistribute it and/or modify it
5 : //# under the terms of the GNU Library General Public License as published by
6 : //# the Free Software Foundation; either version 2 of the License, or (at your
7 : //# option) any later version.
8 : //#
9 : //# This library is distributed in the hope that it will be useful, but WITHOUT
10 : //# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 : //# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 : //# License for more details.
13 : //#
14 : //# You should have received a copy of the GNU Library General Public License
15 : //# along with this library; if not, write to the Free Software Foundation,
16 : //# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
17 : //#
18 : //# Correspondence concerning AIPS++ should be addressed as follows:
19 : //# Internet email: casa-feedback@nrao.edu.
20 : //# Postal address: AIPS++ Project Office
21 : //# National Radio Astronomy Observatory
22 : //# 520 Edgemont Road
23 : //# Charlottesville, VA 22903-2475 USA
24 : //#
25 :
26 : #include <casacore/coordinates/Coordinates/DirectionCoordinate.h>
27 : #include <components/ComponentModels/SkyComponentFactory.h>
28 : #include <casacore/casa/Quanta/MVAngle.h>
29 : #include <components/ComponentModels/GaussianDeconvolver.h>
30 : #include <components/ComponentModels/GaussianShape.h>
31 : #include <components/ComponentModels/ComponentType.h>
32 : #include <casacore/images/Images/ImageUtilities.h>
33 :
34 : using namespace casacore;
35 : namespace casa {
36 :
37 0 : SkyComponent SkyComponentFactory::encodeSkyComponent(
38 : LogIO& logIO, Double& facToJy,
39 : const CoordinateSystem& cSys, const Unit& brightnessUnit,
40 : ComponentType::Shape type, const Vector<Double>& parameters,
41 : Stokes::StokesTypes stokes, Bool xIsLong, const GaussianBeam& beam
42 : ) {
43 : // Input:
44 : // pars(0) = FLux image units (e.g. peak flux in Jy/beam)
45 : // pars(1) = x cen abs pix
46 : // pars(2) = y cen abs pix
47 : // pars(3) = major pix
48 : // pars(4) = minor pix
49 : // pars(5) = pa radians (pos +x -> +y)
50 :
51 0 : SkyComponent sky;
52 :
53 : // Account for the fact that 'x' could be longitude or latitude. Urk.
54 :
55 0 : Vector<Double> pars = parameters.copy();
56 0 : if (!xIsLong) {
57 0 : Double tmp = pars(0);
58 :
59 0 : pars(0) = pars(1);
60 0 : pars(1) = tmp;
61 :
62 0 : Double pa0 = pars(5);
63 0 : MVAngle pa(pa0 + C::pi_2);
64 0 : pa(); // +/- pi
65 0 : pars(5) = pa.radian();
66 0 : }
67 0 : GaussianBeam cbeam = beam;
68 0 : if (brightnessUnit.getName().contains("beam") && beam.isNull()) {
69 0 : cbeam = ImageUtilities::makeFakeBeam(logIO, cSys);
70 : }
71 :
72 0 : sky.fromPixel(facToJy, pars, brightnessUnit, cbeam, cSys, type, stokes);
73 0 : return sky;
74 0 : }
75 :
76 : /*
77 : // moved from ImageAnalysis. See comments in ImageUtilities.h
78 : // TODO the only thing that uses this is ImageFitter. So move it there
79 : SkyComponent SkyComponentFactory::encodeSkyComponent(
80 : LogIO& os, Double& facToJy,
81 : const ImageInterface<Float>& subIm, ComponentType::Shape model,
82 : const Vector<Double>& parameters, Stokes::StokesTypes stokes,
83 : Bool xIsLong, Bool deconvolveIt, const GaussianBeam& beam
84 : ) {
85 : //
86 : // This function takes a vector of doubles and converts them to
87 : // a SkyComponent. These doubles are in the 'x' and 'y' frames
88 : // (e.g. result from Fit2D). It is possible that the
89 : // x and y axes of the pixel array are lat/long rather than
90 : // long/lat if the CoordinateSystem has been reordered. So we have
91 : // to take this into account before making the SkyComponent as it
92 : // needs to know long/lat values. The subImage holds only the sky
93 :
94 : // Input
95 : // pars(0) = Flux image units
96 : // pars(1) = x cen abs pix
97 : // pars(2) = y cen abs pix
98 : // pars(3) = major pix
99 : // pars(4) = minor pix
100 : // pars(5) = pa radians (pos +x -> +y)
101 : // Output
102 : // facToJy = converts brightness units to Jy
103 : //
104 :
105 : const CoordinateSystem& cSys = subIm.coordinates();
106 : const Unit& bU = subIm.units();
107 : SkyComponent sky = SkyComponentFactory::encodeSkyComponent(
108 : os, facToJy, cSys, bU, model,
109 : parameters, stokes, xIsLong, beam
110 : );
111 : if (!deconvolveIt) {
112 : return sky;
113 : }
114 :
115 : if (beam.isNull()) {
116 : os << LogIO::WARN
117 : << "This image does not have a restoring beam so no deconvolution possible"
118 : << LogIO::POST;
119 : return sky;
120 : }
121 : Int dirCoordinate = cSys.findCoordinate(Coordinate::DIRECTION);
122 : if (dirCoordinate == -1) {
123 : os << LogIO::WARN
124 : << "This image does not have a DirectionCoordinate so no deconvolution possible"
125 : << LogIO::POST;
126 : return sky;
127 : }
128 : return SkyComponentFactory::deconvolveSkyComponent(os, sky, beam);
129 : }
130 : */
131 :
132 : // moved from ImageAnalysis. See comments in ImageUtilities.h
133 0 : SkyComponent SkyComponentFactory::deconvolveSkyComponent(
134 : LogIO& os,
135 : const SkyComponent& skyIn, const GaussianBeam& beam
136 : ) {
137 0 : const ComponentShape& shapeIn = skyIn.shape();
138 0 : ComponentType::Shape type = shapeIn.type();
139 0 : if (type == ComponentType::POINT) {
140 0 : return skyIn;
141 : }
142 0 : SkyComponent skyOut = skyIn.copy();
143 0 : if (type == ComponentType::GAUSSIAN) {
144 : // Recover shape
145 0 : const TwoSidedShape& ts = dynamic_cast<const TwoSidedShape&> (shapeIn);
146 0 : Quantity major = ts.majorAxis();
147 0 : Quantity minor = ts.minorAxis();
148 0 : Quantity pa = ts.positionAngle();
149 0 : Angular2DGaussian source(major, minor, pa);
150 0 : Angular2DGaussian deconvolvedSize;
151 0 : GaussianDeconvolver::deconvolve(deconvolvedSize, source, beam);
152 0 : const MDirection dirRefIn = shapeIn.refDirection();
153 : GaussianShape shapeOut(
154 0 : dirRefIn, deconvolvedSize.getMajor(),
155 0 : deconvolvedSize.getMinor(),
156 0 : deconvolvedSize.getPA(true)
157 0 : );
158 0 : skyOut.setShape(shapeOut);
159 0 : }
160 : else {
161 0 : os << "Cannot deconvolve components of type " << shapeIn.ident()
162 0 : << LogIO::EXCEPTION;
163 : }
164 0 : return skyOut;
165 0 : }
166 :
167 0 : Vector<Double> SkyComponentFactory::decodeSkyComponent (
168 : const SkyComponent& sky,
169 : const ImageInfo& ii,
170 : const CoordinateSystem& cSys,
171 : const Unit& brightnessUnit,
172 : Stokes::StokesTypes stokes,
173 : Bool xIsLong
174 : ) {
175 : //
176 : // The decomposition of the SkyComponent gives things as longitide
177 : // and latitude. But it is possible that the x and y axes of the
178 : // pixel array are lat/long rather than long/lat if the CoordinateSystem
179 : // has been reordered. So we have to take this into account.
180 : //
181 : // Output:
182 : // pars(0) = FLux image units (e.g. peak flux in Jy/beam)
183 : // pars(1) = x cen abs pix
184 : // pars(2) = y cen abs pix
185 : // pars(3) = major pix
186 : // pars(4) = minor pix
187 : // pars(5) = pa radians (pos +x -> +y)
188 : //
189 0 : GaussianBeam beam = ii.restoringBeam();
190 :
191 : // pars(1,2) = longitude, latitude centre
192 :
193 0 : Vector<Double> pars = sky.toPixel (brightnessUnit, beam, cSys, stokes).copy();
194 :
195 : // Now account for the fact that 'x' (horizontally displayed axis) could be
196 : // longitude or latitude. Urk.
197 :
198 0 : Double pa0 = pars(5);
199 0 : if (!xIsLong) {
200 0 : Double tmp = pars(0);
201 0 : pars(0) = pars(1);
202 0 : pars(1) = tmp;
203 : //
204 0 : MVAngle pa(pa0 - C::pi_2);
205 0 : pa(); // +/- pi
206 0 : pa0 = pa.radian();
207 0 : }
208 0 : pars(5) = pa0;
209 : //
210 0 : return pars;
211 0 : }
212 :
213 0 : void SkyComponentFactory::worldWidthsToPixel(
214 : Vector<Double>& dParameters,
215 : const Vector<Quantum<Double> >& wParameters,
216 : const CoordinateSystem& cSys,
217 : const IPosition& pixelAxes,
218 : Bool doRef
219 : )
220 : //
221 : // world parameters: x, y, major, minor, pa
222 : // pixel parameters: major, minor, pa (rad)
223 : //
224 : {
225 0 : ThrowIf(
226 : pixelAxes.nelements()!=2,
227 : "You must give two pixel axes"
228 : );
229 0 : ThrowIf(
230 : wParameters.nelements() != 5,
231 : "The world parameters vector must be of length 5."
232 : );
233 :
234 0 : dParameters.resize(3);
235 : Int c0, c1, axisInCoordinate0, axisInCoordinate1;
236 0 : cSys.findPixelAxis(c0, axisInCoordinate0, pixelAxes(0));
237 0 : cSys.findPixelAxis(c1, axisInCoordinate1, pixelAxes(1));
238 :
239 : // Find units
240 :
241 0 : String majorUnit = wParameters(2).getFullUnit().getName();
242 0 : String minorUnit = wParameters(3).getFullUnit().getName();
243 :
244 : // This saves me trying to handle mixed pixel/world units which is a pain for coupled coordinates
245 :
246 0 : ThrowIf(
247 : (majorUnit==String("pix") && minorUnit!=String("pix"))
248 : || (majorUnit!=String("pix") && minorUnit==String("pix")),
249 : "If pixel units are used, both major and minor axes must have pixel units"
250 : );
251 :
252 : // Some checks
253 :
254 0 : Coordinate::Type type0 = cSys.type(c0);
255 0 : Coordinate::Type type1 = cSys.type(c1);
256 0 : ThrowIf(
257 : type0 != type1
258 : && (majorUnit!=String("pix") || minorUnit!=String("pix")),
259 : "The coordinate types for the convolution axes are different. "
260 : "Therefore the units of the major and minor axes of "
261 : "the convolution kernel widths must both be pixels."
262 : );
263 0 : ThrowIf(
264 : type0 == Coordinate::DIRECTION && type1 == Coordinate::DIRECTION && c0 != c1,
265 : "The given axes do not come from the same Direction coordinate. "
266 : "This situation requires further code development."
267 : );
268 0 : ThrowIf(
269 : type0 == Coordinate::STOKES || type1 == Coordinate::STOKES,
270 : "Cannot convolve Stokes axes."
271 : );
272 :
273 : // Deal with pixel units separately. Both are in pixels if either is in pixels.
274 :
275 0 : if (majorUnit==String("pix")) {
276 0 : dParameters(0) = max(wParameters(2).getValue(), wParameters(3).getValue());
277 0 : dParameters(1) = min(wParameters(2).getValue(), wParameters(3).getValue());
278 :
279 0 : if (type0==Coordinate::DIRECTION && type1==Coordinate::DIRECTION) {
280 0 : const DirectionCoordinate& dCoord = cSys.directionCoordinate (c0);
281 :
282 : // Use GaussianShape to get the position angle right. Use the specified
283 : // direction or the reference direction
284 :
285 0 : MDirection world;
286 0 : if (doRef) {
287 0 : dCoord.toWorld(world, dCoord.referencePixel());
288 : }
289 : else {
290 0 : world = MDirection(wParameters(0), wParameters(1), dCoord.directionType());
291 : }
292 :
293 0 : Quantity tmpMaj(1.0, Unit("arcsec"));
294 0 : GaussianShape gaussShape(world, tmpMaj, dParameters(1)/dParameters(0),
295 0 : wParameters(4)); // pa is N->E
296 0 : Vector<Double> pars = gaussShape.toPixel (dCoord);
297 0 : dParameters(2) = pars(4); // pa: +x -> +y
298 0 : }
299 : else {
300 :
301 : // Some 'mixed' plane; the pa is already +x -> +y
302 :
303 0 : dParameters(2) = wParameters(4).getValue(Unit("rad")); // pa
304 : }
305 0 : return;
306 : }
307 :
308 : // Continue on if non-pixel units
309 :
310 0 : if (type0==Coordinate::DIRECTION && type1==Coordinate::DIRECTION) {
311 :
312 : // Check units are angular
313 :
314 0 : Unit rad(String("rad"));
315 0 : ThrowIf(
316 : ! wParameters(2).check(rad.getValue()),
317 : "The units of the major axis must be angular"
318 : );
319 0 : ThrowIf(
320 : ! wParameters(3).check(rad.getValue()),
321 : "The units of the minor axis must be angular"
322 : );
323 :
324 : // Make a Gaussian shape to convert to pixels at specified location
325 :
326 0 : const DirectionCoordinate& dCoord = cSys.directionCoordinate (c0);
327 :
328 0 : MDirection world;
329 0 : if (doRef) {
330 0 : dCoord.toWorld(world, dCoord.referencePixel());
331 : }
332 : else {
333 0 : world = MDirection(wParameters(0), wParameters(1), dCoord.directionType());
334 : }
335 0 : GaussianShape gaussShape(world, wParameters(2), wParameters(3), wParameters(4));
336 0 : Vector<Double> pars = gaussShape.toPixel (dCoord);
337 0 : dParameters(0) = pars(2);
338 0 : dParameters(1) = pars(3);
339 0 : dParameters(2) = pars(4); // radians; +x -> +y
340 0 : }
341 : else {
342 :
343 : // The only other coordinates currently available are non-coupled
344 : // ones and linear except for Tabular, which can be non-regular.
345 : // Urk.
346 :
347 : // Find major and minor axes in pixels
348 :
349 0 : dParameters(0) = _worldWidthToPixel (dParameters(2), wParameters(2),
350 : cSys, pixelAxes);
351 0 : dParameters(1) = _worldWidthToPixel (dParameters(2), wParameters(3),
352 : cSys, pixelAxes);
353 0 : dParameters(2) = wParameters(4).getValue(Unit("rad")); // radians; +x -> +y
354 : }
355 :
356 : // Make sure major > minor
357 :
358 0 : Double tmp = dParameters(0);
359 0 : dParameters(0) = max(tmp, dParameters(1));
360 0 : dParameters(1) = min(tmp, dParameters(1));
361 0 : }
362 :
363 0 : Bool SkyComponentFactory::pixelWidthsToWorld(
364 : GaussianBeam& wParameters,
365 : const Vector<Double>& pParameters, const CoordinateSystem& cSys,
366 : const IPosition& pixelAxes, Bool doRef
367 : ) {
368 : // pixel parameters: x, y, major, minor, pa (rad)
369 : // world parameters: major, minor, pa
370 0 : ThrowIf(
371 : pixelAxes.nelements() != 2,
372 : "You must give two pixel axes"
373 : );
374 0 : ThrowIf(
375 : pParameters.nelements() != 5,
376 : "The parameters vector must be of length 5"
377 : );
378 : Int c0, axis0, c1, axis1;
379 0 : cSys.findPixelAxis(c0, axis0, pixelAxes(0));
380 0 : cSys.findPixelAxis(c1, axis1, pixelAxes(1));
381 0 : Bool flipped = false;
382 0 : if (
383 0 : cSys.type(c1) == Coordinate::DIRECTION
384 0 : && cSys.type(c0) == Coordinate::DIRECTION
385 : ) {
386 0 : ThrowIf(
387 : c0 != c1,
388 : "Cannot handle axes from different DirectionCoordinates"
389 : );
390 0 : flipped = _skyPixelWidthsToWorld(wParameters, cSys, pParameters, pixelAxes, doRef);
391 : }
392 : else {
393 0 : wParameters = GaussianBeam();
394 : // Major/minor
395 : Quantity q0 = _pixelWidthToWorld(
396 0 : pParameters(4), pParameters(2),
397 : cSys, pixelAxes
398 0 : );
399 : Quantity q1 = _pixelWidthToWorld(
400 0 : pParameters(4), pParameters(3),
401 : cSys, pixelAxes
402 0 : );
403 : // Position angle; radians; +x -> +y
404 0 : if (q0.getValue() < q1.getValue(q0.getFullUnit())) {
405 0 : flipped = true;
406 0 : wParameters = GaussianBeam(q1, q0, Quantity(pParameters(4), "rad"));
407 :
408 : }
409 : else {
410 0 : wParameters = GaussianBeam(q0, q1, Quantity(pParameters(4), "rad"));
411 : }
412 0 : }
413 0 : return flipped;
414 : }
415 :
416 :
417 0 : Bool SkyComponentFactory::_skyPixelWidthsToWorld (
418 : Angular2DGaussian& gauss2d,
419 : const CoordinateSystem& cSys,
420 : const Vector<Double>& pParameters,
421 : const IPosition& pixelAxes, Bool doRef
422 : )
423 : //
424 : // pixel parameters: x, y, major, minor, pa (rad)
425 : // world parameters: major, minor, pa
426 : //
427 : {
428 : // What coordinates are these axes ?
429 :
430 : Int c0, c1, axisInCoordinate0, axisInCoordinate1;
431 0 : cSys.findPixelAxis(c0, axisInCoordinate0, pixelAxes(0));
432 0 : cSys.findPixelAxis(c1, axisInCoordinate1, pixelAxes(1));
433 : // See what sort of coordinates we have. Make sure it is called
434 : // only for the Sky. More development needed otherwise.
435 :
436 0 : Coordinate::Type type0 = cSys.type(c0);
437 0 : Coordinate::Type type1 = cSys.type(c1);
438 0 : ThrowIf(
439 : type0!=Coordinate::DIRECTION || type1!=Coordinate::DIRECTION,
440 : "Can only be called for axes holding the sky"
441 : );
442 0 : ThrowIf(
443 : c0 != c1,
444 : "The given axes do not come from the same Direction coordinate. "
445 : "This situation requires further code development"
446 : );
447 : // Is the 'x' (first axis) the Longitude or Latitude ?
448 :
449 0 : Vector<Int> dirPixelAxes = cSys.pixelAxes(c0);
450 0 : Bool xIsLong = dirPixelAxes(0)==pixelAxes(0) && dirPixelAxes(1)==pixelAxes(1);
451 0 : uInt whereIsX = 0;
452 0 : uInt whereIsY = 1;
453 0 : if (!xIsLong) {
454 0 : whereIsX = 1;
455 0 : whereIsY = 0;
456 : }
457 : // Encode a pretend GaussianShape from these values as a means
458 : // of converting to world.
459 :
460 0 : const DirectionCoordinate& dCoord = cSys.directionCoordinate(c0);
461 0 : GaussianShape gaussShape;
462 0 : Vector<Double> cParameters(pParameters.copy());
463 0 : if (doRef) {
464 0 : cParameters(0) = dCoord.referencePixel()(whereIsX); // x centre
465 0 : cParameters(1) = dCoord.referencePixel()(whereIsY); // y centre
466 : }
467 : else {
468 0 : if (xIsLong) {
469 0 : cParameters(0) = pParameters(0);
470 0 : cParameters(1) = pParameters(1);
471 : } else {
472 0 : cParameters(0) = pParameters(1);
473 0 : cParameters(1) = pParameters(0);
474 : }
475 : }
476 0 : Bool flipped = gaussShape.fromPixel (cParameters, dCoord);
477 0 : gauss2d = Angular2DGaussian(
478 0 : gaussShape.majorAxis(), gaussShape.minorAxis(),
479 0 : gaussShape.positionAngle()
480 0 : );
481 0 : return flipped;
482 0 : }
483 :
484 0 : Double SkyComponentFactory::_worldWidthToPixel (
485 : Double positionAngle,
486 : const Quantum<Double>& length,
487 : const CoordinateSystem& cSys,
488 : const IPosition& pixelAxes
489 : ) {
490 0 : Int worldAxis0 = cSys.pixelAxisToWorldAxis(pixelAxes(0));
491 0 : Int worldAxis1 = cSys.pixelAxisToWorldAxis(pixelAxes(1));
492 :
493 : // Units of the axes must be consistent for now.
494 : // I will be able to relax this criterion when I get the time
495 :
496 0 : Vector<String> units = cSys.worldAxisUnits();
497 0 : Unit unit0(units(worldAxis0));
498 0 : Unit unit1(units(worldAxis1));
499 0 : ThrowIf(
500 : unit0 != unit1,
501 : "Units of the two axes must be conformant"
502 : );
503 0 : Unit unit(unit0);
504 :
505 : // Check units are ok
506 :
507 0 : if (!length.check(unit.getValue())) {
508 0 : ostringstream oss;
509 0 : oss << "The units of the world length (" << length.getFullUnit().getName()
510 0 : << ") are not consistent with those of Coordinate System ("
511 0 : << unit.getName() << ")";
512 0 : ThrowCc(oss.str());
513 0 : }
514 :
515 0 : Double w0 = cos(positionAngle) * length.getValue(unit);
516 0 : Double w1 = sin(positionAngle) * length.getValue(unit);
517 :
518 : // Find pixel coordinate of tip of axis relative to reference pixel
519 :
520 0 : Vector<Double> world = cSys.referenceValue().copy();
521 0 : world(worldAxis0) += w0;
522 0 : world(worldAxis1) += w1;
523 :
524 0 : Vector<Double> pixel;
525 0 : ThrowIf(
526 : ! cSys.toPixel (pixel, world),
527 : cSys.errorMessage()
528 : );
529 :
530 0 : return hypot(pixel(pixelAxes(0)), pixel(pixelAxes(1)));
531 0 : }
532 :
533 0 : Quantum<Double> SkyComponentFactory::_pixelWidthToWorld (
534 : Double positionAngle, Double length,
535 : const CoordinateSystem& cSys2,
536 : const IPosition& pixelAxes
537 : ) {
538 0 : CoordinateSystem cSys(cSys2);
539 0 : Int worldAxis0 = cSys.pixelAxisToWorldAxis(pixelAxes(0));
540 0 : Int worldAxis1 = cSys.pixelAxisToWorldAxis(pixelAxes(1));
541 :
542 : // Units of the axes must be consistent for now.
543 : // I will be able to relax this criterion when I get the time
544 :
545 0 : Vector<String> units = cSys.worldAxisUnits().copy();
546 0 : Unit unit0(units(worldAxis0));
547 0 : Unit unit1(units(worldAxis1));
548 0 : ThrowIf(
549 : unit0 != unit1,
550 : "Units of the axes must be conformant"
551 : );
552 :
553 : // Set units to be the same for both axes
554 :
555 0 : units(worldAxis1) = units(worldAxis0);
556 0 : ThrowIf(
557 : !cSys.setWorldAxisUnits(units),
558 : cSys.errorMessage()
559 : );
560 :
561 0 : Double p0 = cos(positionAngle) * length;
562 0 : Double p1 = sin(positionAngle) * length;
563 :
564 : // Find world coordinate of tip of length relative to reference pixel
565 :
566 0 : Vector<Double> pixel= cSys.referencePixel().copy();
567 0 : pixel(pixelAxes(0)) += p0;
568 0 : pixel(pixelAxes(1)) += p1;
569 :
570 0 : Vector<Double> world;
571 0 : ThrowIf(
572 : ! cSys.toWorld(world, pixel),
573 : cSys.errorMessage()
574 : );
575 0 : Double lengthInWorld = hypot(world(worldAxis0), world(worldAxis1));
576 0 : return Quantum<Double>(lengthInWorld, Unit(units(worldAxis0)));
577 0 : }
578 :
579 :
580 :
581 : using namespace casacore;
582 : } // end namespace casa
|