LCOV - code coverage report
Current view: top level - imageanalysis/ImageAnalysis - PVGenerator.cc (source / functions) Hit Total Coverage
Test: casacpp_coverage.info Lines: 0 353 0.0 %
Date: 2024-10-09 13:55:54 Functions: 0 23 0.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           0 : 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           0 : ) : ImageTask<Float>(
      53             :         image, "", regionRec, "", chanInp, stokes,
      54             :         maskInp, outname, overwrite
      55           0 :     ), _start(), _end(), _width(1), _unit("arcsec") {
      56           0 :     _construct();
      57           0 : }
      58             : 
      59           0 : PVGenerator::~PVGenerator() {}
      60             : 
      61           0 : void PVGenerator::setEndpoints(
      62             :     const std::pair<Double, Double>& start,
      63             :     const std::pair<Double, Double>& end
      64             : ) {
      65           0 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
      66           0 :     Double startx = start.first;
      67           0 :     Double starty = start.second;
      68           0 :     Double endx = end.first;
      69           0 :     Double endy = end.second;
      70           0 :     ThrowIf(
      71             :         startx == endx && starty == endy,
      72             :         "Start and end pixels must be different."
      73             :     );
      74           0 :     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           0 :     Vector<Int> dirAxes = _getImage()->coordinates().directionAxesNumbers();
      82           0 :     Int xShape = _getImage()->shape()[dirAxes[0]];
      83           0 :     Int yShape = _getImage()->shape()[dirAxes[1]];
      84           0 :     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           0 :     _start.reset(new vector<Double>(2));
      93           0 :     _end.reset(new vector<Double>(2));
      94           0 :     (*_start)[0] = startx;
      95           0 :     (*_start)[1] = starty;
      96           0 :     (*_end)[0] = endx;
      97           0 :     (*_end)[1] = endy;
      98           0 : }
      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           0 : void PVGenerator::setEndpoints(
     107             :     const std::pair<Double, Double>& center, Double length,
     108             :     const Quantity& pa
     109             : ) {
     110           0 :     ThrowIf(
     111             :         length <= 0,
     112             :         "Length must be positive"
     113             :     );
     114           0 :     setEndpoints(center, length*_increment(), pa);
     115           0 : }
     116             : 
     117           0 : void PVGenerator::setEndpoints(
     118             :     const std::pair<Double, Double>& center, const Quantity& length,
     119             :     const Quantity& pa
     120             : ) {
     121           0 :     Vector<Double> centerV(2);
     122           0 :     const CoordinateSystem csys = _getImage()->coordinates();
     123           0 :     if (csys.isDirectionAbscissaLongitude()) {
     124           0 :         centerV[0] = center.first;
     125           0 :         centerV[1] = center.second;
     126             :     }
     127             :     else {
     128           0 :         centerV[0] = center.second;
     129           0 :         centerV[1] = center.first;
     130             :     }
     131           0 :     setEndpoints(
     132           0 :         csys.directionCoordinate().toWorld(centerV),
     133             :         length, pa
     134             :     );
     135           0 : }
     136             : 
     137           0 : void PVGenerator::setEndpoints(
     138             :     const MDirection& center, const Quantity& length,
     139             :     const Quantity& pa
     140             : ) {
     141           0 :     ThrowIf(
     142             :         ! pa.isConform("rad"),
     143             :         "Position angle must have angular units"
     144             :     );
     145           0 :     Quantity inc = _increment();
     146           0 :     ThrowIf(
     147             :         ! length.isConform(inc),
     148             :         "Units of length are not conformant with direction axes units"
     149             :     );
     150           0 :     MDirection start = center;
     151           0 :     start.shiftAngle(length/2, pa);
     152           0 :     MDirection end = center;
     153           0 :     end.shiftAngle(length/2, pa - Quantity(180, "deg"));
     154           0 :     setEndpoints(start, end);
     155           0 : }
     156             : 
     157           0 : void PVGenerator::setEndpoints(
     158             :     const MDirection& center, Double length,
     159             :     const Quantity& pa
     160             : ) {
     161           0 :     setEndpoints(center, length*_increment(), pa);
     162           0 : }
     163             : 
     164           0 : Quantity PVGenerator::_increment() const {
     165           0 :     const DirectionCoordinate dc = _getImage()->coordinates().directionCoordinate();
     166           0 :     Vector<String> units = dc.worldAxisUnits();
     167           0 :     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           0 :     return Quantity(fabs(dc.increment()[0]), units[0]);
     173           0 : }
     174             : 
     175           0 : void PVGenerator::setEndpoints(
     176             :     const MDirection& start, const MDirection& end
     177             : ) {
     178           0 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     179           0 :     const DirectionCoordinate dc = _getImage()->coordinates().directionCoordinate();
     180           0 :     Vector<Double> sPixel = dc.toPixel(start);
     181           0 :     Vector<Double> ePixel = dc.toPixel(end);
     182           0 :     *_getLog() << LogIO::NORMAL << "Setting pixel end points "
     183           0 :         << sPixel << ", " << ePixel << LogIO::POST;
     184           0 :     setEndpoints(
     185           0 :         std::make_pair(sPixel[0], sPixel[1]),
     186           0 :         std::make_pair(ePixel[0], ePixel[1])
     187             :     );
     188           0 : }
     189             : 
     190           0 : void PVGenerator::setWidth(uInt width) {
     191           0 :     ThrowIf(
     192             :         width % 2 == 0,
     193             :         "Width must be odd."
     194             :     );
     195           0 :     _width = width;
     196           0 : }
     197             : 
     198           0 : void PVGenerator::setWidth(const Quantity& q) {
     199           0 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     200           0 :     const DirectionCoordinate dc = _getImage()->coordinates().directionCoordinate();
     201           0 :     Quantity inc(fabs(dc.increment()[0]), dc.worldAxisUnits()[0]);
     202           0 :     ThrowIf(
     203             :         ! q.isConform(inc),
     204             :         "Nonconformant units specified for quantity"
     205             :     );
     206           0 :     Double nPixels = (q/inc).getValue("");
     207           0 :     if (nPixels < 1) {
     208           0 :         nPixels = 1;
     209           0 :         *_getLog() << LogIO::NORMAL << "Using a width of 1 pixel or "
     210           0 :             << inc.getValue(q.getUnit()) << q.getUnit() << LogIO::POST;
     211             :     }
     212           0 :     else if (near(fmod(nPixels, 2), 1.0)) {
     213           0 :         nPixels = floor(nPixels + 0.5);
     214             :     }
     215             :     else {
     216           0 :         nPixels = ceil(nPixels);
     217           0 :         if (near(fmod(nPixels, 2), 0.0)) {
     218           0 :             nPixels += 1;
     219             :         }
     220           0 :         Quantity qq = nPixels*inc;
     221           0 :         *_getLog() << LogIO::NORMAL << "Rounding width up to next odd number of pixels ("
     222           0 :             << nPixels << "), or " << qq.getValue(q.getUnit()) << q.getUnit() << LogIO::POST;
     223           0 :     }
     224           0 :     setWidth((uInt)nPixels);
     225           0 : }
     226             : 
     227           0 : SPIIF PVGenerator::generate() const {
     228           0 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     229           0 :     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           0 :             *_getImage(), "", *_getRegion(),
     236           0 :             _getMask(), false, false, false, _getStretch()
     237             :          )
     238           0 :     );
     239           0 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     240           0 :     auto subCoords = subImage->coordinates();
     241           0 :     auto dirAxes = subCoords.directionAxesNumbers();
     242           0 :     Int xAxis = dirAxes[0];
     243           0 :     Int yAxis = dirAxes[1];
     244           0 :     auto subShape = subImage->shape();
     245           0 :     auto origShape = _getImage()->shape();
     246           0 :     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           0 :     _checkWidth(subShape[xAxis], subShape[yAxis]);
     253           0 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     254             :     // get the PA of the end points
     255           0 :     auto start = *_start;
     256           0 :     auto end = *_end;
     257           0 :     Double paInRad = start[1] == end[1] ?
     258           0 :         start[0] < end[0]
     259           0 :             ? 0 : C::pi
     260           0 :         : atan2(
     261           0 :             end[0] - start[0], end[1] - start[1]
     262           0 :         ) - C::pi_2;
     263           0 :     Double halfwidth = (_width - 1)/2;
     264           0 :     if (_width > 1) {
     265             :         // check already done when setting the points if _width == 1
     266           0 :         _checkWidthSanity(paInRad, halfwidth, start, end, subImage, xAxis, yAxis);
     267             :     }
     268           0 :     SPCIIF rotated = subImage;
     269           0 :     auto paInDeg = paInRad*180/C::pi;
     270           0 :     auto mustRotate = abs(fmod(paInDeg, 360)) > 0.001;
     271           0 :     if (mustRotate) {
     272           0 :         _moveRefPixel(subImage, subCoords, start, end, paInDeg, xAxis, yAxis);
     273           0 :         rotated = _doRotate(
     274             :             subImage, start, end, 
     275             :             xAxis, yAxis, halfwidth, paInRad
     276           0 :         );
     277             :     }
     278             :     else {
     279           0 :         *_getLog() << LogIO::NORMAL
     280             :             << "Rotation angle (very nearly) 0 degrees, no rotation required"
     281           0 :             << LogIO::POST;
     282             :     }
     283             :     // done with this pointer
     284           0 :     subImage.reset();
     285           0 :     Vector<Double> origStartPixel(subShape.size(), 0);
     286           0 :     origStartPixel[xAxis] = start[0];
     287           0 :     origStartPixel[yAxis] = start[1];
     288           0 :     Vector<Double> origEndPixel(subShape.size(), 0);
     289           0 :     origEndPixel[xAxis] = end[0];
     290           0 :     origEndPixel[yAxis] = end[1];
     291           0 :     auto startWorld = subCoords.toWorld(origStartPixel);
     292           0 :     auto endWorld = subCoords.toWorld(origEndPixel);
     293           0 :     const auto& rotCoords = rotated->coordinates();
     294           0 :     auto rotPixStart = rotCoords.toPixel(startWorld);
     295           0 :     auto rotPixEnd = rotCoords.toPixel(endWorld);
     296           0 :     if (mustRotate) {
     297           0 :         Double xdiff = fabs(end[0] - start[0]);
     298           0 :         Double ydiff = fabs(end[1] - start[1]);
     299           0 :         _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           0 :     );
     308           0 :     return _dropDegen(collapsed, collapsedAxis);
     309           0 : }
     310             : 
     311           0 : 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           0 :     IPosition blc(rotated->ndim(), 0);
     316           0 :     auto trc = rotated->shape() - 1;
     317           0 :     blc[xAxis] = (Int)(rotPixStart[xAxis] + 0.5);
     318           0 :     blc[yAxis] = (Int)(rotPixStart[yAxis] + 0.5 - halfwidth);
     319           0 :     trc[xAxis] = (Int)(rotPixEnd[xAxis] + 0.5);
     320           0 :     trc[yAxis] = (Int)(rotPixEnd[yAxis] + 0.5 + halfwidth);
     321           0 :     auto lcbox = (Record)LCBox(blc, trc, rotated->shape()).toRecord("");
     322           0 :     IPosition axes(1, yAxis);
     323             :     ImageCollapser<Float> collapser(
     324             :         "mean", rotated, &lcbox, "", axes, false, "", false
     325           0 :     );
     326           0 :     SPIIF collapsed = collapser.collapse();
     327           0 :     auto newRefPix = rotated->coordinates().referencePixel();
     328           0 :     newRefPix[xAxis] = rotPixStart[xAxis] - blc[xAxis];
     329           0 :     newRefPix[yAxis] = rotPixStart[yAxis] - blc[yAxis];
     330           0 :     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           0 :     ImageMetaData<Float> md(collapsed);
     335           0 :     Vector<Int> dirShape = md.directionShape();
     336           0 :     AlwaysAssert(dirShape[1] == 1, AipsError);
     337           0 :     const auto& dc = collCoords.directionCoordinate();
     338           0 :     collapsedAxis = collCoords.directionAxesNumbers()[1];
     339           0 :     Vector<Double> pixStart(2, 0);
     340           0 :     auto collapsedStart = dc.toWorld(pixStart);
     341           0 :     Vector<Double> pixEnd(2, 0);
     342           0 :     pixEnd[0] = dirShape[0];
     343           0 :     auto collapsedEnd = dc.toWorld(pixEnd);
     344             :     auto separation = collapsedEnd.separation(
     345           0 :         collapsedStart, dc.worldAxisUnits()[0]
     346           0 :     );
     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           0 :     Vector<String> axisName(2, "Offset");
     351           0 :     Vector<String> axisUnit(2, _unit);
     352           0 :     Vector<Double> crval(2, 0);
     353           0 :     Vector<Double> cdelt(2, separation.getValue(axisUnit[0])/dirShape[0]);
     354           0 :     Matrix<Double> xform(2, 2, 1);
     355           0 :     xform(0, 1) = 0;
     356           0 :     xform(1, 0) = 0;
     357           0 :     Vector<Double> crpix(2, (dirShape[0] - 1)/2);
     358             :     LinearCoordinate lc(
     359             :         axisName, axisUnit, crval,
     360             :         cdelt, xform, crpix
     361           0 :     );
     362           0 :     collCoords.replaceCoordinate(
     363           0 :         lc, collCoords.directionCoordinateNumber()
     364             :     );
     365           0 :     TableRecord misc = collapsed->miscInfo();
     366           0 :     collapsed->coordinates().save(misc, "secondary_coordinates");
     367           0 :     collapsed->setMiscInfo(misc);
     368           0 :     collapsed->setCoordinateInfo(collCoords);
     369           0 :     return collapsed;
     370           0 : }
     371             : 
     372           0 : SPIIF PVGenerator::_dropDegen(SPIIF collapsed, Int collapsedAxis) const {
     373           0 :     IPosition keep, axisPath;
     374           0 :     uInt j = 0;
     375           0 :     for (uInt i=0; i<collapsed->ndim(); ++i) {
     376           0 :         if ((Int)i != collapsedAxis) {
     377           0 :             axisPath.append(IPosition(1, j));
     378           0 :             j++;
     379           0 :             if (collapsed->shape()[i] == 1) {
     380           0 :                 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           0 :         *collapsed, Record(), "", 0, AxesSpecifier(keep, axisPath), false, true
     387           0 :     );
     388           0 :     std::unique_ptr<ArrayLattice<Bool> > newMask;
     389           0 :     if (dynamic_cast<TempImage<Float> *>(collapsed.get())->hasPixelMask()) {
     390             :         // because the mask doesn't lose its degenerate axis when subimaging.
     391           0 :         Array<Bool> newArray = collapsed->pixelMask().get().reform(cDropped->shape());
     392           0 :         newMask.reset(new ArrayLattice<Bool>(cDropped->shape()));
     393           0 :         newMask->put(newArray);
     394           0 :     }
     395           0 :     return _prepareOutputImage(*cDropped, 0, newMask.get());
     396           0 : }
     397             : 
     398           0 : 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           0 :     Vector<Double> worldStart, worldEnd;
     403           0 :     const auto& dc1 = subImage->coordinates().directionCoordinate();
     404           0 :     ThrowIf(
     405             :         ! dc1.toWorld(worldStart, Vector<Double>(start)),
     406             :         "dc1.toWorld() of start pixel coordinate failed"
     407             :     );
     408           0 :     ThrowIf(
     409             :         ! dc1.toWorld(worldEnd, Vector<Double>(end)),
     410             :         "dc1.toWorld() of end coordinate failed"
     411             :     );
     412             :     std::unique_ptr<DirectionCoordinate> rotCoord(
     413           0 :         dynamic_cast<DirectionCoordinate *>(
     414           0 :             dc1.rotate(Quantity(paInRad, "rad"))
     415             :         )
     416           0 :     );
     417           0 :     Vector<Double> startPixRot, endPixRot;
     418           0 :     ThrowIf(
     419             :         ! rotCoord->toPixel(startPixRot, worldStart),
     420             :         "Error converting start world coordinate to pixel coordinate"
     421             :     );
     422           0 :     ThrowIf(
     423             :         ! rotCoord->toPixel(endPixRot, worldEnd),
     424             :         "Error converting end world coordinate to pixel coordinate"
     425             :     );
     426           0 :     AlwaysAssert(abs(startPixRot[1] - endPixRot[1]) < 1e-6, AipsError);
     427           0 :     Double xdiff = fabs(end[0] - start[0]);
     428           0 :     Double ydiff = fabs(end[1] - start[1]);
     429           0 :     AlwaysAssert(
     430             :         abs(
     431             :             (endPixRot[0] - startPixRot[0])
     432             :             - sqrt(xdiff*xdiff + ydiff*ydiff)
     433             :         ) < 1e-6, AipsError
     434             :     );
     435           0 :     Double padNumber = max(0.0, 1 - startPixRot[0]);
     436           0 :     padNumber = max(padNumber, -(startPixRot[1] - halfwidth - 1));
     437           0 :     auto imageToRotate = subImage;
     438           0 :     Int nPixels = 0;
     439           0 :     if (padNumber > 0) {
     440           0 :         nPixels = (Int)padNumber + 1;
     441           0 :         *_getLog() << LogIO::NORMAL
     442             :             << "Some pixels will fall outside the rotated image, so "
     443             :             << "padding before rotating with " << nPixels << " pixels."
     444           0 :             << LogIO::POST;
     445           0 :         ImagePadder padder(subImage);
     446           0 :         padder.setPaddingPixels(nPixels);
     447           0 :         auto padded = padder.pad(true);
     448           0 :         imageToRotate = padded;
     449           0 :     }
     450           0 :     IPosition blc(subImage->ndim(), 0);
     451           0 :     auto subShape = subImage->shape();
     452           0 :     auto trc = subShape - 1;
     453             :     // ensure we have enough real estate after the rotation
     454           0 :     blc[xAxis] = (Int)min(min(start[0], end[0]) - 1 - halfwidth, 0.0);
     455           0 :     blc[yAxis] = (Int)min(min(start[1], end[1]) - 1 - halfwidth, 0.0);
     456           0 :     trc[xAxis] = (Int)max(
     457           0 :         max(start[0], end[0]) + 1 + halfwidth,
     458           0 :         blc[xAxis] + (Double)subShape[xAxis] - 1
     459           0 :     ) + nPixels;
     460           0 :     trc[yAxis] = (Int)max(
     461           0 :         max(start[1], end[1]) + 1 + halfwidth,
     462           0 :         (Double)subShape[yAxis] - 1
     463           0 :     ) + nPixels;
     464           0 :     Record lcbox = LCBox(blc, trc, imageToRotate->shape()).toRecord("");
     465           0 :     SPIIF rotated;
     466           0 :     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           0 :         auto outShape = subShape;
     474           0 :         outShape[xAxis] = (Int)(endPixRot[0] + nPixels + 6);
     475           0 :         outShape[yAxis] = (Int)(startPixRot[1] + halfwidth) + nPixels + 6;
     476           0 :         ImageRotator<Float> rotator(imageToRotate, &lcbox, "", "", false);
     477           0 :         rotator.setAngle(Quantity(paInRad, "rad"));
     478           0 :         rotator.setShape(outShape);
     479           0 :         return rotator.rotate();
     480           0 :     }
     481           0 : }
     482             : 
     483           0 : 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           0 :     auto rotShape = rotated->shape();
     490           0 :     for (uInt i=0; i<2 ;i++) {
     491           0 :         Int64 shape = i == 0 ? rotShape[xAxis] : rotShape[yAxis];
     492           0 :         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           0 :     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           0 :     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           0 :     AlwaysAssert(rotPixStart[xAxis] < rotPixEnd[xAxis], AipsError);
     516           0 : }
     517             : 
     518           0 : 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           0 :     std::vector<Double> midpoint(end.size());
     525             :     // THESE CAN EASILLY BE CHANGED TO ONE PASS WITH C++11 AND LAMBDA FUNCTIONS
     526           0 :     std::transform( end.begin( ), end.end( ), start.begin( ), midpoint.begin( ), std::plus<double>( ) );
     527           0 :     std::transform( midpoint.begin( ), midpoint.end( ), midpoint.begin( ), std::bind2nd(std::divides<double>(),2.0) );
     528           0 :     Vector<Double> newRefPix = subCoords.referencePixel();
     529           0 :     auto useExactMidPoint = True;
     530           0 :     if (abs(end[1] - end[0]) > 1) {
     531           0 :         Double targety = int(midpoint[1]);
     532           0 :         Double targetx = (near(targety, midpoint[1], 1e-8))
     533           0 :             ? midpoint[0]
     534             :             : (
     535           0 :                 start[0]*(end[1] - targety) + end[0]*(targety - start[1])
     536           0 :             )/(end[1] - start[1]);
     537           0 :         newRefPix[xAxis] = targetx;
     538           0 :         newRefPix[yAxis] = targety;
     539           0 :         useExactMidPoint = targetx < min(start[0], start[1]) || targetx > max(start[0], start[1]);
     540             :     }
     541           0 :     if (useExactMidPoint) {
     542             :         // no or small rotation required, rotate around exact midpoint of segment
     543           0 :         newRefPix[xAxis] = midpoint[0];
     544           0 :         newRefPix[yAxis] = midpoint[1];
     545             :     }
     546           0 :     Vector<Double> newRefVal;
     547           0 :     ThrowIf(
     548             :         ! subCoords.toWorld(newRefVal, newRefPix),
     549             :         "Failed to find world coordinate value at midpoint of segment"
     550             :     );
     551           0 :     ThrowIf(
     552             :         ! subCoords.setReferencePixel(newRefPix),
     553             :         "Failed to set reference pixel"
     554             :     );
     555           0 :     ThrowIf(
     556             :         ! subCoords.setReferenceValue(newRefVal),
     557             :         "Failed to set reference value"
     558             :     );
     559           0 :     ThrowIf(
     560             :         ! subImage->setCoordinateInfo(subCoords),
     561             :         "Failed to set coordinate system"
     562             :     );
     563             :     // rotate the image through this angle, in the opposite direction.
     564           0 :     *_getLog() << LogIO::NORMAL << "Rotating image by " << paInDeg
     565           0 :         << " degrees about direction coordinate pixel (" << newRefPix[xAxis] << ", "
     566           0 :         << newRefPix[yAxis] << ") to align specified slice with the x axis" << LogIO::POST;
     567           0 : }
     568             : 
     569           0 : 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           0 :     Double angle1 = paInRad + C::pi/2;
     574           0 :     Double halfx = halfwidth*cos(angle1);
     575           0 :     Double halfy = halfwidth*sin(angle1);
     576           0 :     Vector<Double> xs(4);
     577           0 :     xs[0] = start[0] - halfx;
     578           0 :     xs[1] = start[0] + halfx;
     579           0 :     xs[2] = end[0] - halfx;
     580           0 :     xs[3] = end[0] + halfx;
     581           0 :     Vector<Double> ys(4);
     582           0 :     ys[0] = start[1] - halfy;
     583           0 :     ys[1] = start[1] + halfy;
     584           0 :     ys[2] = end[1] - halfy;
     585           0 :     ys[3] = end[1] + halfy;
     586           0 :     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           0 : }
     594             : 
     595           0 : void PVGenerator::setOffsetUnit(const String& s) {
     596           0 :     Quantity q(1, s);
     597           0 :     ThrowIf(
     598             :         ! q.isConform("rad"),
     599             :         s + " is not a unit of angular measure"
     600             :     );
     601           0 :     _unit = s;
     602           0 : }
     603             : 
     604           0 : String PVGenerator::getClass() const {
     605           0 :     return _class;
     606             : }
     607             : 
     608           0 : void PVGenerator::_checkWidth(const Int64 xShape, const Int64 yShape) const {
     609           0 :     *_getLog() << LogOrigin(_class, __func__, WHERE);
     610           0 :     if (_width == 1) {
     611           0 :         return;
     612             :     }
     613           0 :     vector<Double> start = *_start;
     614           0 :     vector<Double> end = *_end;
     615             : 
     616           0 :     Double angle = (start[0] == end[0])
     617           0 :         ? 0 : atan2((end[1] - start[1]),(end[0] - start[0])) + C::pi_2;
     618           0 :     Double halfwidth = (_width - 1)/2;
     619             : 
     620           0 :     Double delX = halfwidth * cos(angle);
     621           0 :     Double delY = halfwidth * sin(angle);
     622           0 :     if (
     623           0 :         start[0] - delX < 0 || start[0] + delX > xShape
     624           0 :         || start[1] - delY < 0 || start[1] + delY > yShape
     625           0 :         || end[0] - delX < 0 || end[0] + delX > xShape
     626           0 :         || 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           0 : }
     634             : 
     635             : }
     636             : 
     637             : 

Generated by: LCOV version 1.16