ossama-othman / MaRC

MaRC - Map Reprojections and Conversions
GNU Lesser General Public License v2.1
1 stars 0 forks source link

Automatically determine best map plane data scale/offset for map data type. #13

Closed ossama-othman closed 7 years ago

ossama-othman commented 7 years ago

Automatically determine best map plane and data scale and offset based on the map data type and plane type. To reduce confusion, the scale should probably be a power of 10 (e.g. 10, 100, 1000, etc), and the offset should be zero for all map data types except BYTE since an offset may be necessary to support negative source image data values.

The goal is to automatically determine the best source image data scale and offset parameters to retain as many significant digits as possible when storing that data in integer typed maps. It is is currently intended for use with MaRC virtual images, such as cosine of emission angle images (MaRC::MuImage), latitude images (MaRC::LatitudeImage), etc. This would allow for the removal of the hard-coded scale and offset values in MuImage, Mu0Image and CosPhaseImage implementations.

The computed scale and offset should not override MaRC DATA_SCALE and DATA_OFFSET input file value, and should instead be adjusted to work with those values.

The map FITS header should also document the computed scale and offset. If virtual images with the same data range (e.g. [-1,1]) are the only the ones mapped, the FITS BSCALE and BZERO keyword values should be set. The FITS BUNIT keyword value may also be set in that case. Otherwise, document the scale and offset needed to convert the virtual image map values in a set of FITS HISTORY entries.

ossama-othman commented 7 years ago

Proposed implementation to automatically compute scale and offset for MaRC virtual images:

// By Ossama Othman - LGPLv2.1+
namespace MaRC
{
    // Only increase significant digits, not reduce!
    template <typename T>
    static bool scale_and_offset(double min,
                                 double max,
                                 double & scale,
                                 double & offset)
    {
        constexpr double T_lowest = std::numeric_limits<T>::lowest();
        constexpr double T_max    = std::numeric_limits<T>::max();
        constexpr double type_range = T_max - T_lowest;

        double const data_range = max - min;

        if (data_range < 0 || data_range > type_range) {
            // min > max or can't fit all data into desired type T.
            return false;
        }

        int const exponent =
            static_cast<int>(std::numeric_limits<T>::digits10)
            - static_cast<int>(std::log10(data_range));

        scale = std::pow(10, exponent);

        if (min < T_lowest)
            offset = (T_lowest - min) * scale;
        else if (max > T_max)
            offset = (max - T_max) * scale;
        else
            offset = 0;

        return true;
    }
}

The inverse scale and offset values (1/scale and -offset/scale) would be embedded in the FITS header.

ossama-othman commented 7 years ago

The offset calculation was not correct for the case where the minimum and maximum physical data values were not symmetrical (e.g. [0, 360] for longitudes compared to [-90, 90] latitudes). Corrected in the fix for bug #42.