LCOV - code coverage report
Current view: top level - imageanalysis/ImageAnalysis - PVGenerator.cc (source / functions) Hit Total Coverage
Test: casacpp_coverage.info Lines: 338 353 95.8 %
Date: 2024-12-11 20:54:31 Functions: 20 23 87.0 %

          Line data    Source code
       1             : //# Copyright (C) 1998,1999,2000,2001,2003
       2             : //# Associated Universities, Inc. Washington DC, USA.
       3             : //#
       4             : //# This program is free software; you can redistribute it and/or modify it
       5             : //# under the terms of the GNU General Public License as published by the Free
       6             : //# Software Foundation; either version 2 of the License, or (at your option)
       7             : //# any later version.
       8             : //#
       9             : //# This program 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 General Public License for
      12             : //# more details.
      13             : //#
      14             : //# You should have received a copy of the GNU General Public License along
      15             : //# with this program; if not, write to the Free Software Foundation, Inc.,
      16             : //# 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             : //# $Id: $
      26             : 
      27             : #include <imageanalysis/ImageAnalysis/PVGenerator.h>
      28             : 
      29             : #include <casacore/casa/Quanta/Quantum.h>
      30             : #include <casacore/measures/Measures/MDirection.h>
      31             : #include <casacore/tables/Tables/PlainTable.h>
      32             : 
      33             : #include <imageanalysis/ImageAnalysis/ImageCollapser.h>
      34             : #include <imageanalysis/ImageAnalysis/ImageMetaData.h>
      35             : #include <imageanalysis/ImageAnalysis/ImagePadder.h>
      36             : #include <imageanalysis/ImageAnalysis/ImageRotator.h>
      37             : #include <imageanalysis/ImageAnalysis/SubImageFactory.h>
      38             : 
      39             : #include <iomanip>
      40             : 
      41             : using namespace casacore;
      42             : namespace casa {
      43             : 
      44             : const String PVGenerator::_class = "PVGenerator";
      45             : 
      46          47 : PVGenerator::PVGenerator(
      47             :     const SPCIIF image,
      48             :     const Record *const &regionRec,
      49             :     const String& chanInp, const String& stokes,
      50             :     const String& maskInp, const String& outname,
      51             :     const Bool overwrite
      52          47 : ) : ImageTask<Float>(
      53             :         image, "", regionRec, "", chanInp, stokes,
      54             :         maskInp, outname, overwrite
      55          47 :     ), _start(), _end(), _width(1), _unit("arcsec") {
      56          47 :     _construct();
      57          47 : }
      58             : 
      59          47 : PVGenerator::~PVGenerator() {}
      60             : 
      61          47 : void PVGenerator::setEndpoints(
      62             :     const std::pair<Double, Double>& start,
      63             :     const std::pair<Double, Double>& end
      64             : ) {
      65          47 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
      66          47 :     Double startx = start.first;
      67          47 :     Double starty = start.second;
      68          47 :     Double endx = end.first;
      69          47 :     Double endy = end.second;
      70          47 :     ThrowIf(
      71             :         startx == endx && starty == endy,
      72             :         "Start and end pixels must be different."
      73             :     );
      74          47 :     ThrowIf(
      75             :         startx < 2 || endx < 2 || starty < 2 || endy < 2,
      76             :         "Line segment end point positions must be contained in the image and be "
      77             :         "farther than two pixels from image edges. The pixel positions for "
      78             :         "the specified line segment are at " + _pairToString(start) + " and "
      79             :         + _pairToString(end)
      80             :     );
      81          47 :     Vector<Int> dirAxes = _getImage()->coordinates().directionAxesNumbers();
      82          47 :     Int xShape = _getImage()->shape()[dirAxes[0]];
      83          47 :     Int yShape = _getImage()->shape()[dirAxes[1]];
      84          47 :     ThrowIf(
      85             :         startx > xShape-3 || endx > xShape-3
      86             :         || starty > yShape-3 || endy > yShape-3,
      87             :         "Line segment end point positions must be contained in the image and must fall "
      88             :         "farther than two pixels from the image edges. The pixel positions for "
      89             :         "the specified line segment are at " + _pairToString(start) + " and "
      90             :         + _pairToString(end)
      91             :     );
      92          47 :     _start.reset(new vector<Double>(2));
      93          47 :     _end.reset(new vector<Double>(2));
      94          47 :     (*_start)[0] = startx;
      95          47 :     (*_start)[1] = starty;
      96          47 :     (*_end)[0] = endx;
      97          47 :     (*_end)[1] = endy;
      98          47 : }
      99             : 
     100           0 : String PVGenerator::_pairToString(const std::pair<Double, Double>& p) {
     101           0 :     ostringstream os;
     102           0 :     os << "[" << p.first << ", " << p.second << "]";
     103           0 :     return os.str();
     104           0 : }
     105             : 
     106           8 : void PVGenerator::setEndpoints(
     107             :     const std::pair<Double, Double>& center, Double length,
     108             :     const Quantity& pa
     109             : ) {
     110           8 :     ThrowIf(
     111             :         length <= 0,
     112             :         "Length must be positive"
     113             :     );
     114           8 :     setEndpoints(center, length*_increment(), pa);
     115           8 : }
     116             : 
     117          14 : void PVGenerator::setEndpoints(
     118             :     const std::pair<Double, Double>& center, const Quantity& length,
     119             :     const Quantity& pa
     120             : ) {
     121          14 :     Vector<Double> centerV(2);
     122          14 :     const CoordinateSystem csys = _getImage()->coordinates();
     123          14 :     if (csys.isDirectionAbscissaLongitude()) {
     124          14 :         centerV[0] = center.first;
     125          14 :         centerV[1] = center.second;
     126             :     }
     127             :     else {
     128           0 :         centerV[0] = center.second;
     129           0 :         centerV[1] = center.first;
     130             :     }
     131          14 :     setEndpoints(
     132          28 :         csys.directionCoordinate().toWorld(centerV),
     133             :         length, pa
     134             :     );
     135          14 : }
     136             : 
     137          23 : void PVGenerator::setEndpoints(
     138             :     const MDirection& center, const Quantity& length,
     139             :     const Quantity& pa
     140             : ) {
     141          23 :     ThrowIf(
     142             :         ! pa.isConform("rad"),
     143             :         "Position angle must have angular units"
     144             :     );
     145          23 :     Quantity inc = _increment();
     146          23 :     ThrowIf(
     147             :         ! length.isConform(inc),
     148             :         "Units of length are not conformant with direction axes units"
     149             :     );
     150          23 :     MDirection start = center;
     151          23 :     start.shiftAngle(length/2, pa);
     152          23 :     MDirection end = center;
     153          23 :     end.shiftAngle(length/2, pa - Quantity(180, "deg"));
     154          23 :     setEndpoints(start, end);
     155          23 : }
     156             : 
     157           5 : void PVGenerator::setEndpoints(
     158             :     const MDirection& center, Double length,
     159             :     const Quantity& pa
     160             : ) {
     161           5 :     setEndpoints(center, length*_increment(), pa);
     162           5 : }
     163             : 
     164          36 : Quantity PVGenerator::_increment() const {
     165          36 :     const DirectionCoordinate dc = _getImage()->coordinates().directionCoordinate();
     166          36 :     Vector<String> units = dc.worldAxisUnits();
     167          36 :     ThrowIf(
     168             :         units[0] != units[1],
     169             :         "Cannot calculate the direction pixel increment because the"
     170             :         "axes have different units of " + units[0] + " and " + units[1]
     171             :     );
     172         108 :     return Quantity(fabs(dc.increment()[0]), units[0]);
     173          36 : }
     174             : 
     175          27 : void PVGenerator::setEndpoints(
     176             :     const MDirection& start, const MDirection& end
     177             : ) {
     178          27 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     179          27 :     const DirectionCoordinate dc = _getImage()->coordinates().directionCoordinate();
     180          27 :     Vector<Double> sPixel = dc.toPixel(start);
     181          27 :     Vector<Double> ePixel = dc.toPixel(end);
     182          54 :     *_getLog() << LogIO::NORMAL << "Setting pixel end points "
     183          27 :         << sPixel << ", " << ePixel << LogIO::POST;
     184          27 :     setEndpoints(
     185          27 :         std::make_pair(sPixel[0], sPixel[1]),
     186          27 :         std::make_pair(ePixel[0], ePixel[1])
     187             :     );
     188          27 : }
     189             : 
     190          47 : void PVGenerator::setWidth(uInt width) {
     191          47 :     ThrowIf(
     192             :         width % 2 == 0,
     193             :         "Width must be odd."
     194             :     );
     195          47 :     _width = width;
     196          47 : }
     197             : 
     198           6 : void PVGenerator::setWidth(const Quantity& q) {
     199           6 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     200           6 :     const DirectionCoordinate dc = _getImage()->coordinates().directionCoordinate();
     201           6 :     Quantity inc(fabs(dc.increment()[0]), dc.worldAxisUnits()[0]);
     202           6 :     ThrowIf(
     203             :         ! q.isConform(inc),
     204             :         "Nonconformant units specified for quantity"
     205             :     );
     206           6 :     Double nPixels = (q/inc).getValue("");
     207           6 :     if (nPixels < 1) {
     208           2 :         nPixels = 1;
     209           4 :         *_getLog() << LogIO::NORMAL << "Using a width of 1 pixel or "
     210           2 :             << inc.getValue(q.getUnit()) << q.getUnit() << LogIO::POST;
     211             :     }
     212           4 :     else if (near(fmod(nPixels, 2), 1.0)) {
     213           1 :         nPixels = floor(nPixels + 0.5);
     214             :     }
     215             :     else {
     216           3 :         nPixels = ceil(nPixels);
     217           3 :         if (near(fmod(nPixels, 2), 0.0)) {
     218           3 :             nPixels += 1;
     219             :         }
     220           3 :         Quantity qq = nPixels*inc;
     221           3 :         *_getLog() << LogIO::NORMAL << "Rounding width up to next odd number of pixels ("
     222           3 :             << nPixels << "), or " << qq.getValue(q.getUnit()) << q.getUnit() << LogIO::POST;
     223           3 :     }
     224           6 :     setWidth((uInt)nPixels);
     225           6 : }
     226             : 
     227          47 : SPIIF PVGenerator::generate() const {
     228          47 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     229          47 :     ThrowIf(
     230             :         _start.get() == 0 || _end.get() == 0,
     231             :         "Start and/or end points have not been set"
     232             :     );
     233             :     SPIIF subImage(
     234             :         SubImageFactory<Float>::createImage(
     235         141 :             *_getImage(), "", *_getRegion(),
     236          47 :             _getMask(), false, false, false, _getStretch()
     237             :          )
     238         142 :     );
     239          46 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     240          46 :     auto subCoords = subImage->coordinates();
     241          46 :     auto dirAxes = subCoords.directionAxesNumbers();
     242          46 :     Int xAxis = dirAxes[0];
     243          46 :     Int yAxis = dirAxes[1];
     244          46 :     auto subShape = subImage->shape();
     245          46 :     auto origShape = _getImage()->shape();
     246          46 :     ThrowIf(
     247             :         subShape[xAxis] != origShape[xAxis]
     248             :         || subShape[yAxis] != origShape[yAxis],
     249             :         "You are not permitted to make a region selection "
     250             :         "in the direction coordinate"
     251             :     );
     252          46 :     _checkWidth(subShape[xAxis], subShape[yAxis]);
     253          46 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     254             :     // get the PA of the end points
     255          46 :     auto start = *_start;
     256          46 :     auto end = *_end;
     257          66 :     Double paInRad = start[1] == end[1] ?
     258          20 :         start[0] < end[0]
     259          20 :             ? 0 : C::pi
     260         104 :         : atan2(
     261          26 :             end[0] - start[0], end[1] - start[1]
     262          26 :         ) - C::pi_2;
     263          46 :     Double halfwidth = (_width - 1)/2;
     264          46 :     if (_width > 1) {
     265             :         // check already done when setting the points if _width == 1
     266           6 :         _checkWidthSanity(paInRad, halfwidth, start, end, subImage, xAxis, yAxis);
     267             :     }
     268          46 :     SPCIIF rotated = subImage;
     269          46 :     auto paInDeg = paInRad*180/C::pi;
     270          46 :     auto mustRotate = abs(fmod(paInDeg, 360)) > 0.001;
     271          46 :     if (mustRotate) {
     272          27 :         _moveRefPixel(subImage, subCoords, start, end, paInDeg, xAxis, yAxis);
     273          54 :         rotated = _doRotate(
     274             :             subImage, start, end, 
     275             :             xAxis, yAxis, halfwidth, paInRad
     276          27 :         );
     277             :     }
     278             :     else {
     279          38 :         *_getLog() << LogIO::NORMAL
     280             :             << "Rotation angle (very nearly) 0 degrees, no rotation required"
     281          19 :             << LogIO::POST;
     282             :     }
     283             :     // done with this pointer
     284          46 :     subImage.reset();
     285          46 :     Vector<Double> origStartPixel(subShape.size(), 0);
     286          46 :     origStartPixel[xAxis] = start[0];
     287          46 :     origStartPixel[yAxis] = start[1];
     288          46 :     Vector<Double> origEndPixel(subShape.size(), 0);
     289          46 :     origEndPixel[xAxis] = end[0];
     290          46 :     origEndPixel[yAxis] = end[1];
     291          46 :     auto startWorld = subCoords.toWorld(origStartPixel);
     292          46 :     auto endWorld = subCoords.toWorld(origEndPixel);
     293          46 :     const auto& rotCoords = rotated->coordinates();
     294          46 :     auto rotPixStart = rotCoords.toPixel(startWorld);
     295          46 :     auto rotPixEnd = rotCoords.toPixel(endWorld);
     296          46 :     if (mustRotate) {
     297          27 :         Double xdiff = fabs(end[0] - start[0]);
     298          27 :         Double ydiff = fabs(end[1] - start[1]);
     299          27 :         _checkRotatedImageSanity(
     300             :             rotated, rotPixStart, rotPixEnd,
     301             :             xAxis, yAxis, xdiff, ydiff
     302             :         );
     303             :     }
     304             :     Int collapsedAxis;
     305             :     auto collapsed = _doCollapse(
     306             :         collapsedAxis, rotated, xAxis, yAxis, rotPixStart, rotPixEnd, halfwidth
     307          46 :     );
     308          92 :     return _dropDegen(collapsed, collapsedAxis);
     309          46 : }
     310             : 
     311          46 : SPIIF PVGenerator::_doCollapse(
     312             :     Int& collapsedAxis, SPCIIF rotated, Int xAxis, Int yAxis, const Vector<Double>& rotPixStart,
     313             :     const Vector<Double>& rotPixEnd, Double halfwidth
     314             : ) const {
     315          46 :     IPosition blc(rotated->ndim(), 0);
     316          46 :     auto trc = rotated->shape() - 1;
     317          46 :     blc[xAxis] = (Int)(rotPixStart[xAxis] + 0.5);
     318          46 :     blc[yAxis] = (Int)(rotPixStart[yAxis] + 0.5 - halfwidth);
     319          46 :     trc[xAxis] = (Int)(rotPixEnd[xAxis] + 0.5);
     320          46 :     trc[yAxis] = (Int)(rotPixEnd[yAxis] + 0.5 + halfwidth);
     321          92 :     auto lcbox = (Record)LCBox(blc, trc, rotated->shape()).toRecord("");
     322          46 :     IPosition axes(1, yAxis);
     323             :     ImageCollapser<Float> collapser(
     324             :         "mean", rotated, &lcbox, "", axes, false, "", false
     325          92 :     );
     326          46 :     SPIIF collapsed = collapser.collapse();
     327          46 :     auto newRefPix = rotated->coordinates().referencePixel();
     328          46 :     newRefPix[xAxis] = rotPixStart[xAxis] - blc[xAxis];
     329          46 :     newRefPix[yAxis] = rotPixStart[yAxis] - blc[yAxis];
     330          46 :     auto collCoords = collapsed->coordinates();
     331             : 
     332             :     // to determine the pixel increment of the angular offset axis, get the
     333             :     // distance between the end points
     334          46 :     ImageMetaData<Float> md(collapsed);
     335          46 :     Vector<Int> dirShape = md.directionShape();
     336          46 :     AlwaysAssert(dirShape[1] == 1, AipsError);
     337          46 :     const auto& dc = collCoords.directionCoordinate();
     338          46 :     collapsedAxis = collCoords.directionAxesNumbers()[1];
     339          46 :     Vector<Double> pixStart(2, 0);
     340          46 :     auto collapsedStart = dc.toWorld(pixStart);
     341          46 :     Vector<Double> pixEnd(2, 0);
     342          46 :     pixEnd[0] = dirShape[0];
     343          46 :     auto collapsedEnd = dc.toWorld(pixEnd);
     344             :     auto separation = collapsedEnd.separation(
     345          46 :         collapsedStart, dc.worldAxisUnits()[0]
     346          92 :     );
     347             :     // The new coordinate must have the same number of axes as the coordinate
     348             :     // it replaces, so 2 for the linear coordinate, we will remove the degenerate
     349             :     // axis later
     350          46 :     Vector<String> axisName(2, "Offset");
     351          46 :     Vector<String> axisUnit(2, _unit);
     352          46 :     Vector<Double> crval(2, 0);
     353          46 :     Vector<Double> cdelt(2, separation.getValue(axisUnit[0])/dirShape[0]);
     354          46 :     Matrix<Double> xform(2, 2, 1);
     355          46 :     xform(0, 1) = 0;
     356          46 :     xform(1, 0) = 0;
     357          46 :     Vector<Double> crpix(2, (dirShape[0] - 1)/2);
     358             :     LinearCoordinate lc(
     359             :         axisName, axisUnit, crval,
     360             :         cdelt, xform, crpix
     361          46 :     );
     362          46 :     collCoords.replaceCoordinate(
     363          46 :         lc, collCoords.directionCoordinateNumber()
     364             :     );
     365          46 :     TableRecord misc = collapsed->miscInfo();
     366          46 :     collapsed->coordinates().save(misc, "secondary_coordinates");
     367          46 :     collapsed->setMiscInfo(misc);
     368          46 :     collapsed->setCoordinateInfo(collCoords);
     369          92 :     return collapsed;
     370          46 : }
     371             : 
     372          46 : SPIIF PVGenerator::_dropDegen(SPIIF collapsed, Int collapsedAxis) const {
     373          46 :     IPosition keep, axisPath;
     374          46 :     uInt j = 0;
     375         201 :     for (uInt i=0; i<collapsed->ndim(); ++i) {
     376         155 :         if ((Int)i != collapsedAxis) {
     377         109 :             axisPath.append(IPosition(1, j));
     378         109 :             j++;
     379         109 :             if (collapsed->shape()[i] == 1) {
     380          17 :                 keep.append(IPosition(1, i));
     381             :             }
     382             :         }
     383             :     }
     384             :     // now remove the degenerate linear axis
     385             :     std::shared_ptr<const SubImage<Float> > cDropped = SubImageFactory<Float>::createSubImageRO(
     386         138 :         *collapsed, Record(), "", 0, AxesSpecifier(keep, axisPath), false, true
     387         138 :     );
     388          46 :     std::unique_ptr<ArrayLattice<Bool> > newMask;
     389          46 :     if (dynamic_cast<TempImage<Float> *>(collapsed.get())->hasPixelMask()) {
     390             :         // because the mask doesn't lose its degenerate axis when subimaging.
     391           4 :         Array<Bool> newArray = collapsed->pixelMask().get().reform(cDropped->shape());
     392           2 :         newMask.reset(new ArrayLattice<Bool>(cDropped->shape()));
     393           2 :         newMask->put(newArray);
     394           2 :     }
     395          92 :     return _prepareOutputImage(*cDropped, 0, newMask.get());
     396          46 : }
     397             : 
     398          27 : SPCIIF PVGenerator::_doRotate(
     399             :     SPIIF subImage, const vector<Double>& start, const vector<Double>& end,
     400             :     Int xAxis, Int yAxis, Double halfwidth, Double paInRad
     401             : ) const {
     402          27 :     Vector<Double> worldStart, worldEnd;
     403          27 :     const auto& dc1 = subImage->coordinates().directionCoordinate();
     404          27 :     ThrowIf(
     405             :         ! dc1.toWorld(worldStart, Vector<Double>(start)),
     406             :         "dc1.toWorld() of start pixel coordinate failed"
     407             :     );
     408          27 :     ThrowIf(
     409             :         ! dc1.toWorld(worldEnd, Vector<Double>(end)),
     410             :         "dc1.toWorld() of end coordinate failed"
     411             :     );
     412             :     std::unique_ptr<DirectionCoordinate> rotCoord(
     413          27 :         dynamic_cast<DirectionCoordinate *>(
     414          54 :             dc1.rotate(Quantity(paInRad, "rad"))
     415             :         )
     416          54 :     );
     417          27 :     Vector<Double> startPixRot, endPixRot;
     418          27 :     ThrowIf(
     419             :         ! rotCoord->toPixel(startPixRot, worldStart),
     420             :         "Error converting start world coordinate to pixel coordinate"
     421             :     );
     422          27 :     ThrowIf(
     423             :         ! rotCoord->toPixel(endPixRot, worldEnd),
     424             :         "Error converting end world coordinate to pixel coordinate"
     425             :     );
     426          27 :     AlwaysAssert(abs(startPixRot[1] - endPixRot[1]) < 1e-6, AipsError);
     427          27 :     Double xdiff = fabs(end[0] - start[0]);
     428          27 :     Double ydiff = fabs(end[1] - start[1]);
     429          27 :     AlwaysAssert(
     430             :         abs(
     431             :             (endPixRot[0] - startPixRot[0])
     432             :             - sqrt(xdiff*xdiff + ydiff*ydiff)
     433             :         ) < 1e-6, AipsError
     434             :     );
     435          27 :     Double padNumber = max(0.0, 1 - startPixRot[0]);
     436          27 :     padNumber = max(padNumber, -(startPixRot[1] - halfwidth - 1));
     437          27 :     auto imageToRotate = subImage;
     438          27 :     Int nPixels = 0;
     439          27 :     if (padNumber > 0) {
     440          16 :         nPixels = (Int)padNumber + 1;
     441          32 :         *_getLog() << LogIO::NORMAL
     442             :             << "Some pixels will fall outside the rotated image, so "
     443             :             << "padding before rotating with " << nPixels << " pixels."
     444          16 :             << LogIO::POST;
     445          32 :         ImagePadder padder(subImage);
     446          16 :         padder.setPaddingPixels(nPixels);
     447          16 :         auto padded = padder.pad(true);
     448          16 :         imageToRotate = padded;
     449          16 :     }
     450          27 :     IPosition blc(subImage->ndim(), 0);
     451          27 :     auto subShape = subImage->shape();
     452          27 :     auto trc = subShape - 1;
     453             :     // ensure we have enough real estate after the rotation
     454          27 :     blc[xAxis] = (Int)min(min(start[0], end[0]) - 1 - halfwidth, 0.0);
     455          27 :     blc[yAxis] = (Int)min(min(start[1], end[1]) - 1 - halfwidth, 0.0);
     456         108 :     trc[xAxis] = (Int)max(
     457          27 :         max(start[0], end[0]) + 1 + halfwidth,
     458          27 :         blc[xAxis] + (Double)subShape[xAxis] - 1
     459          27 :     ) + nPixels;
     460         108 :     trc[yAxis] = (Int)max(
     461          27 :         max(start[1], end[1]) + 1 + halfwidth,
     462          27 :         (Double)subShape[yAxis] - 1
     463          27 :     ) + nPixels;
     464          54 :     Record lcbox = LCBox(blc, trc, imageToRotate->shape()).toRecord("");
     465          27 :     SPIIF rotated;
     466          27 :     if (paInRad == 0) {
     467           0 :         *_getLog() << LogIO::NORMAL << "Slice is along x-axis, no rotation necessary.";
     468           0 :         return SubImageFactory<Float>::createSubImageRW(
     469           0 :             *imageToRotate, lcbox, "", 0, AxesSpecifier(), true
     470           0 :         );
     471             :     }
     472             :     else {
     473          27 :         auto outShape = subShape;
     474          27 :         outShape[xAxis] = (Int)(endPixRot[0] + nPixels + 6);
     475          27 :         outShape[yAxis] = (Int)(startPixRot[1] + halfwidth) + nPixels + 6;
     476          54 :         ImageRotator<Float> rotator(imageToRotate, &lcbox, "", "", false);
     477          27 :         rotator.setAngle(Quantity(paInRad, "rad"));
     478          27 :         rotator.setShape(outShape);
     479          27 :         return rotator.rotate();
     480          27 :     }
     481          27 : }
     482             : 
     483          27 : void PVGenerator::_checkRotatedImageSanity(
     484             :     SPCIIF rotated, const Vector<Double>& rotPixStart, const Vector<Double>& rotPixEnd,
     485             :     Int xAxis, Int yAxis, Double xdiff, Double ydiff
     486             : ) const {
     487             :     // sanity checks, can be removed when this is well tested and used without issue
     488             :     // The rotated start and end pixels should lie in the image
     489          27 :     auto rotShape = rotated->shape();
     490          81 :     for (uInt i=0; i<2 ;i++) {
     491          54 :         Int64 shape = i == 0 ? rotShape[xAxis] : rotShape[yAxis];
     492          54 :         AlwaysAssert(
     493             :             rotPixStart[i] > 0 && rotPixEnd[i] > 0
     494             :             && rotPixStart[i] < shape - 1 && rotPixEnd[i] < shape - 1,
     495             :             AipsError
     496             :         );
     497             :     }
     498             : 
     499             :     // We've rotated to make the slice coincident with the x axis, therefore, the y axis
     500             :     // values of the endpoints should be equal
     501          27 :     AlwaysAssert(abs(rotPixStart[yAxis] - rotPixEnd[yAxis]) < 1e-6, AipsError);
     502             :     // the difference in the x axis coordinate of rotated endpoints should simply be
     503             :     // the distance between those points before rotation
     504          27 :     AlwaysAssert(
     505             :         abs(
     506             :             (rotPixEnd[xAxis] - rotPixStart[xAxis])
     507             :             - sqrt(xdiff*xdiff + ydiff*ydiff)
     508             :         ) < 1e-6, AipsError
     509             :     );
     510             :     // CAS-6043, because it's possible for the above conditions to be true but the y values to still be
     511             :     // just a little different and on either side of the 0.5 pixel mark
     512             :     //rotPixEnd[yAxis] = rotPixStart[yAxis];
     513             :     // We have rotated so the position of the starting pixel x is smaller than
     514             :     // the ending pixel x.
     515          27 :     AlwaysAssert(rotPixStart[xAxis] < rotPixEnd[xAxis], AipsError);
     516          27 : }
     517             : 
     518          27 : void PVGenerator::_moveRefPixel(
     519             :     SPIIF subImage, CoordinateSystem& subCoords, const std::vector<Double>& start,
     520             :     const std::vector<Double>& end, Double paInDeg, Int xAxis, Int yAxis
     521             : ) const {
     522             :     // rotation occurs about the reference pixel, so move the reference pixel to be
     523             :     // on the segment, near the midpoint so that the y value is an integer.
     524          27 :     std::vector<Double> midpoint(end.size());
     525             :     // THESE CAN EASILLY BE CHANGED TO ONE PASS WITH C++11 AND LAMBDA FUNCTIONS
     526          27 :     std::transform( end.begin( ), end.end( ), start.begin( ), midpoint.begin( ), std::plus<double>( ) );
     527          27 :     std::transform( midpoint.begin( ), midpoint.end( ), midpoint.begin( ), std::bind2nd(std::divides<double>(),2.0) );
     528          27 :     Vector<Double> newRefPix = subCoords.referencePixel();
     529          27 :     auto useExactMidPoint = True;
     530          27 :     if (abs(end[1] - end[0]) > 1) {
     531          23 :         Double targety = int(midpoint[1]);
     532          23 :         Double targetx = (near(targety, midpoint[1], 1e-8))
     533          23 :             ? midpoint[0]
     534             :             : (
     535          18 :                 start[0]*(end[1] - targety) + end[0]*(targety - start[1])
     536          18 :             )/(end[1] - start[1]);
     537          23 :         newRefPix[xAxis] = targetx;
     538          23 :         newRefPix[yAxis] = targety;
     539          23 :         useExactMidPoint = targetx < min(start[0], start[1]) || targetx > max(start[0], start[1]);
     540             :     }
     541          27 :     if (useExactMidPoint) {
     542             :         // no or small rotation required, rotate around exact midpoint of segment
     543           7 :         newRefPix[xAxis] = midpoint[0];
     544           7 :         newRefPix[yAxis] = midpoint[1];
     545             :     }
     546          27 :     Vector<Double> newRefVal;
     547          27 :     ThrowIf(
     548             :         ! subCoords.toWorld(newRefVal, newRefPix),
     549             :         "Failed to find world coordinate value at midpoint of segment"
     550             :     );
     551          27 :     ThrowIf(
     552             :         ! subCoords.setReferencePixel(newRefPix),
     553             :         "Failed to set reference pixel"
     554             :     );
     555          27 :     ThrowIf(
     556             :         ! subCoords.setReferenceValue(newRefVal),
     557             :         "Failed to set reference value"
     558             :     );
     559          27 :     ThrowIf(
     560             :         ! subImage->setCoordinateInfo(subCoords),
     561             :         "Failed to set coordinate system"
     562             :     );
     563             :     // rotate the image through this angle, in the opposite direction.
     564          54 :     *_getLog() << LogIO::NORMAL << "Rotating image by " << paInDeg
     565          54 :         << " degrees about direction coordinate pixel (" << newRefPix[xAxis] << ", "
     566          27 :         << newRefPix[yAxis] << ") to align specified slice with the x axis" << LogIO::POST;
     567          27 : }
     568             : 
     569           6 : void PVGenerator::_checkWidthSanity(
     570             :     Double paInRad, Double halfwidth, const vector<Double>& start,
     571             :     const vector<Double>& end, SPCIIF subImage, Int xAxis, Int yAxis
     572             : ) const {
     573           6 :     Double angle1 = paInRad + C::pi/2;
     574           6 :     Double halfx = halfwidth*cos(angle1);
     575           6 :     Double halfy = halfwidth*sin(angle1);
     576           6 :     Vector<Double> xs(4);
     577           6 :     xs[0] = start[0] - halfx;
     578           6 :     xs[1] = start[0] + halfx;
     579           6 :     xs[2] = end[0] - halfx;
     580           6 :     xs[3] = end[0] + halfx;
     581           6 :     Vector<Double> ys(4);
     582           6 :     ys[0] = start[1] - halfy;
     583           6 :     ys[1] = start[1] + halfy;
     584           6 :     ys[2] = end[1] - halfy;
     585           6 :     ys[3] = end[1] + halfy;
     586           6 :     ThrowIf(
     587             :         min(xs) < 2 || max(xs) > subImage->shape()[xAxis] - 3
     588             :         || min(ys) < 2 || max(ys) > subImage->shape()[yAxis] - 3,
     589             :         "Error: specified end points and half width are such "
     590             :         "that chosen directional region falls outside or within "
     591             :         "two pixels of the edge of the image."
     592             :     );
     593           6 : }
     594             : 
     595          47 : void PVGenerator::setOffsetUnit(const String& s) {
     596          47 :     Quantity q(1, s);
     597          47 :     ThrowIf(
     598             :         ! q.isConform("rad"),
     599             :         s + " is not a unit of angular measure"
     600             :     );
     601          47 :     _unit = s;
     602          47 : }
     603             : 
     604           0 : String PVGenerator::getClass() const {
     605           0 :     return _class;
     606             : }
     607             : 
     608          46 : void PVGenerator::_checkWidth(const Int64 xShape, const Int64 yShape) const {
     609          46 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     610          46 :     if (_width == 1) {
     611          40 :         return;
     612             :     }
     613           6 :     vector<Double> start = *_start;
     614           6 :     vector<Double> end = *_end;
     615             : 
     616           6 :     Double angle = (start[0] == end[0])
     617           6 :         ? 0 : atan2((end[1] - start[1]),(end[0] - start[0])) + C::pi_2;
     618           6 :     Double halfwidth = (_width - 1)/2;
     619             : 
     620           6 :     Double delX = halfwidth * cos(angle);
     621           6 :     Double delY = halfwidth * sin(angle);
     622           6 :     if (
     623          12 :         start[0] - delX < 0 || start[0] + delX > xShape
     624           6 :         || start[1] - delY < 0 || start[1] + delY > yShape
     625           6 :         || end[0] - delX < 0 || end[0] + delX > xShape
     626          12 :         || end[1] - delY < 0 || end[1] + delY > yShape
     627             :     ) {
     628           0 :         *_getLog() << LogIO::WARN << "The half width chosen is too large "
     629             :             << "to include all pixels along the chosen slice. The half "
     630             :             << "width extends beyond the image edge(s) at some location(s)."
     631           0 :             << LogIO::POST;
     632             :     }
     633           6 : }
     634             : 
     635             : }
     636             : 
     637             : 

Generated by: LCOV version 1.16