LCOV - code coverage report
Current view: top level - synthesis/Utilities - SingleDishBeamUtil.cc (source / functions) Hit Total Coverage
Test: casacpp_coverage.info Lines: 153 187 81.8 %
Date: 2025-07-23 00:22:00 Functions: 3 3 100.0 %

          Line data    Source code
       1             : #include <cassert>
       2             : #include <memory>
       3             : #include <iostream>
       4             : 
       5             : #include <casacore/casa/Arrays/ArrayMath.h>
       6             : #include <casacore/casa/Arrays/Vector.h>
       7             : #include <casacore/casa/Utilities/Assert.h>
       8             : #include <casacore/casa/Logging.h>
       9             : #include <casacore/measures/Measures/MDirection.h>
      10             : #include <casacore/measures/TableMeasures/ScalarMeasColumn.h>
      11             : #include <casacore/ms/MSSel/MSSelection.h>
      12             : #include <casacore/tables/TaQL/ExprNode.h>
      13             : 
      14             : #include <synthesis/Utilities/PointingDirectionCalculator.h>
      15             : #include <synthesis/Utilities/SingleDishBeamUtil.h>
      16             : 
      17             : using namespace std;
      18             : using namespace casacore;
      19             : 
      20             : #define _ORIGIN LogOrigin("SingleDishBeamUtil", __func__, WHERE)
      21             : 
      22             : namespace casa { //# NAMESPACE CASA - BEGIN
      23             : 
      24          12 : SingleDishBeamUtil::SingleDishBeamUtil(const MeasurementSet &ms,
      25             :                                        const String &referenceFrame,
      26             :                                        const String &movingSource,
      27             :                                        const String &pointingColumn,
      28          12 :                                        const String &antenna)
      29          12 :        : referenceFrame_(referenceFrame), movingSource_(movingSource),
      30          24 :          pointingColumn_(pointingColumn), antSel_(antenna)
      31             : {
      32          12 :   ms_ = new MeasurementSet(ms);
      33          12 :   directionUnit_ = Unit("rad");
      34          12 : }
      35             : 
      36          12 : Bool SingleDishBeamUtil::getMapPointings(Matrix<Double> &pointingList) {
      37             :     try {
      38          12 :         PointingDirectionCalculator calc(*ms_);
      39             : 
      40          12 :         calc.setDirectionColumn(pointingColumn_);
      41          12 :         calc.selectData(antSel_);
      42          12 :         calc.setFrame(referenceFrame_);
      43          12 :         MDirection::Types refType = MDirection::J2000; // any non planet value
      44          12 :         Bool status = False;
      45          12 :         status = MDirection::getType(refType, movingSource_);
      46          24 :         Bool doMovingSourceCorrection = (status == True &&
      47          12 :                 MDirection::N_Types < refType &&
      48           0 :                 refType < MDirection::N_Planets);
      49          12 :         if (doMovingSourceCorrection) {
      50           0 :             calc.setMovingSource(movingSource_);
      51             :         }
      52          12 :         calc.setDirectionListMatrixShape(PointingDirectionCalculator::COLUMN_MAJOR);
      53             : 
      54          12 :         pointingList = calc.getDirection();
      55          12 :         directionUnit_ = Unit("rad");
      56          12 :         Vector<Double> longitude = pointingList.column(0);
      57          12 :         Vector<Double> latitude = pointingList.column(1);
      58             : 
      59          12 :         if (longitude.size() < 2) return True; // no need for boundary check.
      60             : 
      61             :         // Diagnose if longitude values are divided by periodic boundary surface
      62             :         // (+-pi or 0, 2pi)
      63             :         // In this case, mean of longitude should be around 0 (+-pi) or pi (0,2pi)
      64             :         // and stddev of longitude array be around pi.
      65          12 :         Double longitudeMean = mean(longitude);
      66          12 :         Double longitudeStddev = stddev(longitude);
      67          12 :         if (longitudeStddev > 2.0 / 3.0 * C::pi) {
      68             :             // likely to be the case
      69           0 :             if (abs(longitudeMean) < 0.5 * C::pi) {
      70             :                 // periodic boundary surface would be +-pi
      71           0 :                 for (size_t i = 0; i < longitude.nelements(); ++i) {
      72           0 :                     if (longitude[i] < 0.0) {
      73           0 :                         longitude[i] += C::_2pi;
      74             :                     }
      75             :                 }
      76             :             }
      77           0 :             else if (abs(longitudeMean - C::pi) < 0.5 * C::pi ) {
      78             :                 // periodic boundary surface would be 0,2pi
      79           0 :                 for (size_t i = 0; i < longitude.nelements(); ++i) {
      80           0 :                     if (longitude[i] < C::pi) {
      81           0 :                         longitude[i] += C::_2pi;
      82             :                     }
      83             :                 }
      84             :             }
      85             :         }
      86          12 :     }
      87           0 :     catch (AipsError &e) {
      88           0 :         LogIO os(LogOrigin("Imager", "getMapPointings", WHERE));
      89           0 :         os << LogIO::SEVERE << "Failed due to the rror \"" << e.getMesg() << "\"" << LogIO::POST;
      90           0 :         return False;
      91           0 :     }
      92           0 :     catch (...) {
      93           0 :         LogIO os(LogOrigin("Imager", "getMapPointings", WHERE));
      94           0 :         os << LogIO::SEVERE << "Failed due to unknown error" << LogIO::POST;
      95           0 :         throw;
      96             :         return False;
      97           0 :     }
      98          12 :     return True;
      99             : }
     100             : 
     101          12 :   Bool SingleDishBeamUtil::getPointingSamplingRaster(Quantum<Vector<Double>> &sampling, Quantity &positionAngle) {
     102          24 :     LogIO os(_ORIGIN);
     103          12 :     os << "calculating sampling interval assuming raster scan." << LogIO::POST;
     104             :     try {
     105          12 :       Vector<Double> samplingVal(2, 0.0);
     106             :       // Get time sorted pointing (sort by ANTENNA, TIME)
     107          12 :       Matrix<Double> pointingList;
     108          12 :       ThrowIf (!getMapPointings(pointingList), "Failed to get map pointings");
     109          12 :       os << "got " << pointingList.column(0).size() << " pointings of " << antSel_  << LogIO::POST;
     110             :       // Get timestamps
     111          12 :       Block<String> sortColumns(2);
     112          12 :       sortColumns[0] = "ANTENNA1";
     113          12 :       sortColumns[1] = "TIME";
     114          12 :       MSSelection thisSelection;
     115          12 :       thisSelection.setAntennaExpr(antSel_);
     116          12 :       TableExprNode exprNode = thisSelection.getTEN(&(*ms_));
     117          12 :       if (exprNode.isNull()) {
     118           0 :         throw(AipsError("Invalid antenna selection"));
     119             :       }
     120          12 :       MeasurementSet tmp = (*ms_)(exprNode);
     121          12 :       CountedPtr<MeasurementSet> sortedMS = new MeasurementSet(tmp.sort(sortColumns));
     122          12 :       AlwaysAssert(sortedMS->nrow() == pointingList.column(0).size(), AipsError);
     123          12 :       ScalarMeasColumn<MEpoch> timeColumn_;
     124          12 :       timeColumn_.attach(*sortedMS, "TIME");
     125             :       // Get time and pointings of unique time stamp per antenna
     126          12 :       Vector<uInt> uniqueAntTimeIdx(sortedMS->nrow());
     127          12 :       Vector<Double> uniqueAntTimes(sortedMS->nrow());
     128          12 :       Double currentTime = timeColumn_.convert(0, MEpoch::UTC).get("s").getValue();
     129          12 :       uInt itime = 0;
     130             :       // Initial time stamp
     131          12 :       uniqueAntTimeIdx(itime) = 0;
     132          12 :       uniqueAntTimes(itime) = currentTime;
     133          12 :       ++itime;
     134       11264 :       for (uInt i = 1; i < sortedMS->nrow(); ++i) {
     135       11252 :         Double nextTime = timeColumn_.convert(i, MEpoch::UTC).get("s").getValue();
     136       11252 :         if (abs(nextTime-currentTime) > 1.e-14*currentTime) {
     137        9208 :           uniqueAntTimeIdx(itime) = i;
     138        9208 :           uniqueAntTimes(itime) = nextTime;
     139        9208 :           ++itime;
     140             :         }
     141       11252 :         currentTime = nextTime;
     142             :       }
     143          12 :       if (itime != uniqueAntTimeIdx.size()) {
     144           4 :         uniqueAntTimeIdx.resize(itime, True);
     145           4 :         uniqueAntTimes.resize(itime, True); 
     146             :       }
     147          12 :       os << LogIO::DEBUG1 << uniqueAntTimeIdx.size() << " unique time stamps"  << LogIO::POST;
     148          12 :       if (uniqueAntTimes.size() == 1) {
     149           0 :         samplingVal = 0.0;
     150           0 :         sampling = Quantum< Vector<Double> >(samplingVal, directionUnit_);
     151           0 :         positionAngle = Quantity(0.0, "rad");
     152             :         os << LogIO::NORMAL
     153             :            << "Got only one pointing. exiting without calculating sampling interval."
     154           0 :            << LogIO::POST;
     155           0 :         return True;
     156             :       }
     157          12 :       Vector<Double> longitude(uniqueAntTimes.size());
     158          12 :       Vector<Double> latitude(longitude.size());
     159             :       {
     160          12 :         Vector<Double> all_longitude = pointingList.column(0); // time sorted pointing list
     161          12 :         Vector<Double> all_latitude = pointingList.column(1);
     162        9232 :         for (uint i = 0; i < uniqueAntTimeIdx.size(); ++i) {
     163        9220 :           longitude(i) = all_longitude[uniqueAntTimeIdx[i]];
     164        9220 :           latitude(i) = all_latitude[uniqueAntTimeIdx[i]];
     165             :         }
     166          12 :       }
     167             :       // calculate pointing interval assuming RASTER
     168          12 :       Vector<Double> delta_lon(longitude.size()-1);
     169          12 :       Vector<Double> delta_lat(delta_lon.size());
     170             :       Double min_lat, max_lat;
     171          12 :       minMax(min_lat, max_lat, latitude);
     172          12 :       Double center_lat = 0.5*(min_lat+max_lat);
     173        9220 :       for (size_t i=0; i < delta_lon.size(); ++i) {
     174        9208 :         Double dlon = (longitude[i+1]-longitude[i])*cos(center_lat);
     175        9208 :         delta_lon[i] = abs(dlon) > 1.e-8 ? dlon : 1.e-8;
     176        9208 :         delta_lat[i] = latitude[i+1]-latitude[i];
     177             :       }
     178             :       { // sampling interval along scan row
     179          12 :         Vector<Double> delta2(delta_lon.size());
     180          12 :         delta2 = square(delta_lon) + square(delta_lat);
     181          12 :         samplingVal(0) = sqrt(median(delta2));
     182          24 :         os << LogIO::DEBUG1 << "sampling interval along scan: " << samplingVal(0)
     183          12 :            << " " << directionUnit_.getName() << LogIO::POST;
     184          12 :       }
     185             :       { // position angle
     186          12 :         Vector<Double> delta_tan(delta_lon.size());
     187          12 :         delta_tan = delta_lat/delta_lon;
     188          12 :         Double positionAngleVal = atan(median(delta_tan));
     189          12 :         positionAngleVal = std::isfinite(positionAngleVal) ? positionAngleVal: 0.5*C::pi;
     190          12 :         positionAngle = Quantity(positionAngleVal, "rad");
     191          12 :         os << LogIO::DEBUG1 << "position angle of scan direction: " << positionAngle << LogIO::POST;
     192          12 :       }
     193             :       { // sampling interval orthogonal to scan row
     194          12 :         vector<uInt> gap_idx(0);
     195          12 :         uInt numAntGap = 0;
     196          12 :         os << LogIO::DEBUG1 << "start analysing raster pattern by time gap " << LogIO::POST;
     197             :         {// detect raster gap by time interval
     198          12 :           Vector<Double> deltaTime(uniqueAntTimes.size()-1);
     199          12 :           Vector<Double> positiveTimeGap(deltaTime.size());
     200          12 :           uInt itime = 0;
     201        9220 :           for (uInt i = 0; i < deltaTime.size(); ++i) {
     202        9208 :             deltaTime[i] = uniqueAntTimes[i+1] - uniqueAntTimes[i];
     203        9208 :             if (deltaTime[i] > 0.0) {
     204        9208 :               positiveTimeGap[itime] = deltaTime[i];
     205        9208 :               ++itime;
     206             :             }
     207             :           }
     208          12 :           positiveTimeGap.resize(itime, True);
     209          12 :           Double medianInterval5 = Double(5)*median(positiveTimeGap);
     210          12 :           os << LogIO::DEBUG1 << "Gap interval threshold = " << medianInterval5  << LogIO::POST;
     211        9220 :           for (uInt i = 0; i < deltaTime.size(); ++i) {
     212        9208 :             if (deltaTime[i] > medianInterval5) {// raster row gap
     213          24 :               gap_idx.push_back(i);
     214        9184 :             } else if (deltaTime[i] < 0.0 || i == deltaTime.size()-1) { // antenna gap
     215          12 :               gap_idx.push_back(i);
     216          12 :               ++numAntGap;
     217             :             }
     218             :           }
     219          12 :         }
     220          12 :         if (gap_idx.size()==numAntGap) {// no gap detected.
     221           4 :           os << LogIO::NORMAL << "No time gap found in scans. The scan pattern may not be RASTER. Median sampling interval will be returned." << LogIO::POST;
     222           4 :           samplingVal(1) = 0.0;
     223           4 :           sampling = Quantum< Vector<Double> >(samplingVal, directionUnit_);
     224           4 :           os << "sampling interval: " << sampling << ", pa: " << positionAngle  << LogIO::POST;
     225           4 :           return True;
     226             :         }
     227           8 :         os << LogIO::DEBUG1 << gap_idx.size() << " raster rows detected" << LogIO::POST;
     228             :         //os << LogIO::DEBUGGING << "gap idx = " << Vector<uInt>(gap_idx) << LogIO::POST;
     229             :         // a unit vector of orthogonal direction
     230           8 :         Vector<Double> orth_vec(2);
     231           8 :         orth_vec(0) = cos(positionAngle.getValue("rad")+0.5*C::pi);
     232           8 :         orth_vec(1) = sin(positionAngle.getValue("rad")+0.5*C::pi);
     233           8 :         os << LogIO::DEBUG1 << "orthogonal vector = " << orth_vec << LogIO::POST;
     234             :         // median lon, lat in each raster row
     235           8 :         Vector<Double> typical_lon(gap_idx.size());
     236           8 :         Vector<Double> typical_lat(gap_idx.size());
     237          40 :         for (uInt i = 0; i < gap_idx.size(); ++i) {
     238             :           uInt start_idx, num_dump;
     239          32 :           if (i==0) start_idx = 0;
     240          24 :           else start_idx = gap_idx[i-1] + 1;
     241          32 :           num_dump = gap_idx[i]-start_idx + 1;
     242          32 :           typical_lon(i) = median(longitude(Slice(start_idx, num_dump)));
     243          32 :           typical_lat(i) = median(latitude(Slice(start_idx, num_dump)));
     244             :         }
     245             :         os << LogIO::DEBUG1 << "Typical longitude of scan row (first 10 at max) = "
     246           8 :            << typical_lon(Slice(0, min(typical_lon.size(), 10))) << LogIO::POST;
     247             :         os << LogIO::DEBUG1 << "Typical latitude of scan row (first 10 at max) = "
     248           8 :            << typical_lat(Slice(0, min(typical_lat.size(), 10))) << LogIO::POST;
     249           8 :         Vector<Double> orth_dist(typical_lon.size()-1);
     250          32 :         for (uInt i = 0; i < typical_lon.size()-1; ++i) {
     251             :           Double delta_row_lon, delta_row_lat;
     252          24 :           delta_row_lon = typical_lon(i) - typical_lon(i+1);
     253          24 :           delta_row_lat = typical_lat(i) - typical_lat(i+1);
     254             :           // product of orthogonal vector and row gap vector
     255          48 :           orth_dist(i) = abs(delta_row_lon*orth_vec(0) + 
     256          24 :                              delta_row_lat*orth_vec(1));
     257             :         }
     258           8 :         samplingVal(1) = median(orth_dist);
     259          16 :         os << LogIO::DEBUG1 << "sampling interval between scan row: " << samplingVal(1)
     260           8 :            << " " << directionUnit_.getName() << LogIO::POST;
     261          12 :       }
     262           8 :       sampling = Quantum<Vector<Double>>(samplingVal, directionUnit_);
     263          64 :     }
     264           0 :     catch (AipsError &e) {
     265           0 :         os << LogIO::SEVERE << "Failed due to the rror \"" << e.getMesg() << "\"" << LogIO::POST;
     266           0 :         return False;
     267           0 :     }
     268           0 :     catch (...) {
     269           0 :         os << LogIO::SEVERE << "Failed due to unknown error" << LogIO::POST;
     270           0 :         throw;
     271             :         return False;
     272           0 :     }
     273             :     os << LogIO::NORMAL << "sampling interval: " << sampling
     274           8 :        << ", pa: " << positionAngle  << LogIO::POST;
     275           8 :     return True;
     276          12 : }
     277             : 
     278             : } //# NAMESPACE CASA - END

Generated by: LCOV version 1.16