Commit 5377f013 authored by vertighel's avatar vertighel
Browse files

Tweaks

parent 80e3c620
Loading
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ OBSTYPE = | Observation type
[Detector]          
DETECTOR =            | Detector identifier
DETSIZE =             | [px] Physical CCD dimensions
DETROT =              | [deg] Rotation offset of the detector
XPIXSZ =              | [um] Pixel X axis size
YPIXSZ =              | [um] Pixel Y axis size
PIXSCALE =            | [arcsec/px] Plate scale in binning 1
+112 −53
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ import configparser
from pathlib import Path
from configparser import DuplicateOptionError
from ast import literal_eval
import sys

import numpy as np
from astropy.coordinates import SkyCoord, EarthLocation
@@ -48,6 +49,8 @@ class Noche:
        if debug:
            log.setLevel('DEBUG')
            
        log.debug(sys._getframe().f_code.co_name)
            
        self._coord = None
        self._location = None
        self._obstime = None
@@ -69,8 +72,11 @@ class Noche:
        Returns
        -------
        list of str
            Names of available observatory configuration files (without .ini extension).
            Names of available observatory configuration files
            (without .ini extension).
        """
        log.debug(sys._getframe().f_code.co_name)

        data_dir = Path(__file__).parent / self._obs_dir
        return [f.stem for f in data_dir.glob("*.ini")]

@@ -84,6 +90,7 @@ class Noche:
        path : str
            Configuration file in .ini format
        """
        log.debug(sys._getframe().f_code.co_name)
        
        if not path:
            path = Path(__file__).parent / self._head_dir / "header_base_v1.ini"
@@ -117,11 +124,11 @@ class Noche:
        name : str
            One of the supported observatories.
        """
        log.debug(sys._getframe().f_code.co_name)

        data_dir = Path(__file__).parent / self._obs_dir
        ini_path = data_dir / f"{name}.ini"

        log.debug(ini_path)
        if not ini_path.exists():
            msg = f"Observatory config '{ini_path}' not found."
            # raise FileNotFoundError(msg)
@@ -141,6 +148,7 @@ class Noche:
        path : str
            Configuration file in .ini format.
        """
        log.debug(sys._getframe().f_code.co_name)

        config = self._load_config(path)        

@@ -156,68 +164,105 @@ class Noche:
            if k in self.header:
                self.header[k] = val

        #self.header["DETSIZE"] = f'[1:{self.header["NAXIS1"]},1:{self.header["NAXIS2"]}]'

        if fits_file:
            self.fill_from_fits_file(path, fits_file)

        else:
            self._update()


    
    def fill_from_fits_file(self, path=None, fits_file=None):
        """
        Load relevant header info from a fits file of the specific observatory.
    
         - [Mapping] section defines how the new keywords are related
           to the old ones.
           For example: FOCUSPOS = TELFOCUS

         - [Formula] section defines the formula to apply to a header
           value to match the specifics of the new one.
           For example: FOCUSPOS = (x)/1000 # Transforming mm in um.
         - [Mapping] defines how the new keywords are related to the old ones.
         - [Formula] defines formulas to apply *before* generating derived keywords.
         - [Tweak] defines formulas to apply *after* generating all keywords.
    
        Parameters
        ----------
        path : str
            Configuration file in .ini format.
        fits_file : str
            FITS file position.

            FITS file path.
        """
        log.debug(sys._getframe().f_code.co_name)

        config = self._load_config(path)

        filename = Path(fits_file)
        self.load_fits(filename)
        fits_file_header = self.hdu.header
    
        sections = config.sections()
        # Load formulas (if sections exist)
        pre_formulas = dict(config["Formula"]) if "Formula" in config else {}
        post_formulas = dict(config["Tweak"]) if "Tweak" in config else {}
    
        # Apply Mapping + PreFormula
        loc = config["Mapping"]
        for k in loc.keys():

            if not (k in self.header):
                log.error(f"{k} not in header")
                continue
            
            fits_keyword = loc[k]
            fits_value = fits_file_header[fits_keyword]
            val = self._parse(fits_value)
            self.header[k] = val
            log.info(f"{k:<8} : {val}")
    
        loc = config["Formula"]
        for k in loc.keys():
            fits_formula = loc[k]
            if k in pre_formulas:
                x = self.header[k]
            self.header[k] = eval(fits_formula)
            log.info(f"{k:<8} : from {x} to {self.header[k]}")
            del x

                try:
                    self.header[k] = eval(pre_formulas[k])
                    log.warning(f"{k:<8} (Pre): from {x} to {self.header[k]}")
                except Exception as e:
                    log.error(f"Failed to evaluate PreFormula for {k}: {e}")
        # # Apply Mapping
        # loc = config["Mapping"]
        # for k in loc.keys():

        #     if not (k in self.header):
        #         log.error(f"{k} not in header")
        #         continue
            
        #     fits_keyword = loc[k]
        #     fits_value = fits_file_header[fits_keyword]
        #     val = self._parse(fits_value)
        #     self.header[k] = val
        #     log.info(f"{k:<8} : {val}")
            
        # # Apply PreFormula    
        # for k, formula in pre_formulas.items():
        #     if k in self.header:
        #         x = self.header[k]
        #         try:
        #             self.header[k] = eval(formula)
        #             log.warning(f"{k:<8} (Pre): from {x} to {self.header[k]}")
        #         except Exception as e:
        #             log.error(f"Failed to evaluate PreFormula for {k}: {e}")
        #     else:
        #         log.debug(f"PreFormula skipped: {k} not in header")
    
        # Set coordinates (calls _update(), which triggers set_wcs and others)
        self.set_coordinates(self.header["RA"],
                             self.header["DEC"],
                             obstime=self.header["DATE-OBS"])
        
        self._update()
        # Apply PostFormula
        for k, formula in post_formulas.items():
            if k in self.header:
                x = self.header[k]
                try:
                    self.header[k] = eval(formula)
                    log.warning(f"{k:<8} (Post): from {x} to {self.header[k]}")
                except Exception as e:
                    log.error(f"Failed to evaluate PostFormula for {k}: {e}")
            else:
                log.warning(f"PostFormula skipped: {k} not in header")
    
        self.header["FILEORIG"] = filename.name
        

    def set_location(self, lon, lat, alt):
        """
        Location of the observatory, Separate so that it is
@@ -232,6 +277,7 @@ class Noche:
        alt : float
            Elevation in meters.
        """
        log.debug(sys._getframe().f_code.co_name)

        self._location = EarthLocation(lon, lat, alt)

@@ -248,6 +294,7 @@ class Noche:
        obstime : str, astropy.time.Time
            Time of the observation.
        """
        log.debug(sys._getframe().f_code.co_name)

        time = Time(obstime)
        self._obstime = time
@@ -259,7 +306,7 @@ class Noche:
        self.header['DATE-OBS'] = time.isot
        self.header['MJD-OBS'] = time.mjd

        self._update()
        #self._update()


    def set_object(self, objname, update_coord=False, obstime=None):
@@ -275,16 +322,17 @@ class Noche:
        obstime : str or astropy.time.Time, optional
            Observation time to associate with coordinates.
        """
        log.debug(sys._getframe().f_code.co_name)

        if obstime:
            time = Time(obstime)
            self.set_obstime(time)

        if update_coord:
            try:
                coord = SkyCoord.from_name(objname)
                self._coord = coord

                if obstime:
                    time = Time(obstime)
                    self.set_obstime(time)

                log.info("Found catalog name")
                coorstr = coord.to_string(style='hmsdms', sep=' ', precision=1, pad=True)
                log.info(f"Corresponds to {coorstr}")
@@ -315,6 +363,7 @@ class Noche:
        obstime : str or astropy.time.Time, optional
            Observation time.
        """
        log.debug(sys._getframe().f_code.co_name)

        coord = SkyCoord(ra=ra, dec=dec, unit=(u.hourangle, u.deg))
        self._coord = coord
@@ -325,8 +374,8 @@ class Noche:

        self.header['RA'] = coord.ra.to_string(unit=u.hourangle, sep=':',
                                               pad=True, precision=1)
        self.header['DEC'] = coord.dec.to_string(unit=u.deg, sep=':', pad=True,
                                                 precision=1)
        self.header['DEC'] = coord.dec.to_string(unit=u.deg, sep=':',
                                                 pad=True, precision=1)
        self.header['RA_DEG'] = coord.ra.deg
        self.header['DEC_DEG'] = coord.dec.deg

@@ -342,6 +391,7 @@ class Noche:
        ValueError
            If observation time or location is not set.
        """
        log.debug(sys._getframe().f_code.co_name)

        if self._obstime == None or self._location == None:
            raise ValueError("Observation Time, Instrument parameters must be set.")
@@ -365,11 +415,11 @@ class Noche:

        # Position angle: with respect to Celestial North Pole
        north_celestial = SkyCoord(ra=0*u.deg, dec=90*u.deg, frame='icrs')
        posang = self._coord.position_angle(north_celestial).to(u.deg).value
        self.header['POSANGLE'] = round(posang, 2)
        posangle = self._coord.position_angle(north_celestial).to(u.deg).value
        self.header['POSANGLE'] = round(posangle, 2)

        # Parallactic angle:  between local meridian and celestial axis
        parangle = (posang - altaz.az.deg + 360) % 360
        parangle = (posangle - altaz.az.deg + 360) % 360
        if parangle > 180:
            parangle -= 360  # Wrap to [-180, 180]

@@ -390,11 +440,11 @@ class Noche:
        ValueError
            If coordinates or location are not set.
        """
        log.debug(sys._getframe().f_code.co_name)

        if self._coord == None or self._location == None:
            raise ValueError("Observation Coordinates, Instrument parameters must be set.")


        detsize = self.header['DETSIZE'].strip('[]')
        x_str, y_str = detsize.split(',')

@@ -407,20 +457,22 @@ class Noche:
        cdelt2 = round(self.header["PIXSCALE"]*self.header["YBINNING"]*u.arcsec.to(u.deg), 7)

        if not angle:
            angle = self.header["DEROTANG"]
            angle = self.header["DEROTANG"] + self.header["DETROT"]

        angle = np.deg2rad(angle)

        crval_ra = self._coord.ra.deg
        crval_dec = self._coord.dec.deg

        flip = -1 # East to the left
        
        self.header['CRPIX1'] = crpix[0] 
        self.header['CRPIX2'] = crpix[1]
        self.header['CRVAL1'] = crval_ra
        self.header['CRVAL2'] = crval_dec
        self.header['CDELT1'] = cdelt1
        self.header['CDELT1'] = cdelt1 * flip
        self.header['CDELT2'] = cdelt2
        self.header["PC1_1"] = +np.cos(angle) *-1 # E to left
        self.header["PC1_1"] = +np.cos(angle)
        self.header["PC1_2"] = -np.sin(angle)
        self.header["PC2_1"] = +np.sin(angle)
        self.header["PC2_2"] = +np.cos(angle)
@@ -435,6 +487,7 @@ class Noche:
        ValueError
            If coordinates or location are not set.
        """
        log.debug(sys._getframe().f_code.co_name)

        if self._coord == None or self._location == None:
            raise ValueError("Observation Time, Observing location must be set.")
@@ -467,12 +520,14 @@ class Noche:
        """
        List the header keywords that are still empty
        """
        log.debug(sys._getframe().f_code.co_name)

        for k in self.header:
            if self.header[k] == None:
                print(k, self.header[k])

    def load_fits(self, fits_file):
        log.debug(sys._getframe().f_code.co_name)

        filename = fits_file
        hdul = fits.open(filename)
@@ -487,6 +542,7 @@ class Noche:

    @staticmethod
    def _load_config(path):
        log.debug(sys._getframe().f_code.co_name)
                
        config = configparser.ConfigParser(inline_comment_prefixes=('#',))

@@ -504,8 +560,10 @@ class Noche:
        If coordinates, observation and location are provided, then
        additional keywords can be filled
        """
        log.debug(sys._getframe().f_code.co_name)

        if self._coord != None and self._obstime != None and self._location != None:

            self.set_altaz_and_parallactic()
            self.set_ambient()
            self.set_wcs()
@@ -525,6 +583,7 @@ class Noche:
        int, float, bool, or str
            Parsed value.
        """
        # log.debug(sys._getframe().f_code.co_name)

        try:
            val = val.strip()
+5 −2
Original line number Diff line number Diff line
@@ -5,14 +5,14 @@ FOCALLEN = 1170 # [mm] Telescope focal length
OBS-LONG = -11.2430135            # [deg] Observatory longitude (East > 0)  
OBS-LAT = 43.5235203              # [deg] Observatory latitude (North > 0)  
OBS-ELEV = 1025                   # [m] Observatory altitude above sea level
DEROTANG = 0                      # [deg] Rotator angle, if any
INSTRUME = ABOb instrument        # Instrument name
OBSTYPE = Imaging                 # Observation type
DETECTOR = Atik 383 L             # Detector identifier
DETSIZE = [1:3354,1:2529]         # [px] [1:x,1:y] Physical CCD dimensions
DETROT = -89.67                   # [deg] Rotation offset of the detector
XPIXSZ = 5.4                      # [um] Pixel X axis size
YPIXSZ = 5.4                      # [um] Pixel Y axis size
PIXSCALE = 36                     # [arcsec/px] Plate scale in binning 1
PIXSCALE = 1.0                      # [arcsec/px] Plate scale in binning 1
GAIN =                            # [e-/ADU] Gain
RDNOISE =                         # [e- RMS] Readout noise

@@ -38,3 +38,6 @@ SWCREATE = SWCREATE # Software that created FILEORIG

[Formula]


[Tweak]
CDELT2 = -1*x
+6 −1
Original line number Diff line number Diff line
@@ -5,11 +5,11 @@ FOCALLEN = 1520 # [mm] Telescope focal length
OBS-LONG = 14.020563              # [deg] Observatory longitude (East > 0)  
OBS-LAT =  37.9391183             # [deg] Observatory latitude (North > 0)  
OBS-ELEV = 606                    # [m] Observatory altitude above sea level
DEROTANG = 0                      # [deg] Rotator angle, if any
INSTRUME = GRT instrument         # Instrument name
OBSTYPE = Imaging                 # Observation type
DETECTOR = Moravian CMOS C4-16000 # Detector identifier
DETSIZE = [1:4096,1:4096]         # [px] [1:x,1:y] Physical CCD dimensions
DETROT = +5.0                     # [deg] Rotation offset of the detector
XPIXSZ = 9                        # [um] Pixel X axis size
YPIXSZ = 9                        # [um] Pixel Y axis size
PIXSCALE = 1.22                   # [arcsec/px] Plate scale in binning 1
@@ -37,3 +37,8 @@ YBINNING = YBINNING # Binning factor in Y
SWCREATE = SWCREATE               # Software that created FILEORIG

[Formula]

[Tweak]
CDELT2 = -1*x
CRPIX1 = x+134
CRPIX2 = x-500
+6 −2
Original line number Diff line number Diff line
@@ -5,11 +5,11 @@ FOCALLEN = 6400 # [mm] Telescope focal length
OBS-LONG = 9.2034                 # [deg] Observatory longitude (East > 0)  
OBS-LAT = 44.5912                 # [deg] Observatory latitude (North > 0)  
OBS-ELEV = 1469                   # [m] Observatory altitude above sea level
DEROTANG = -89.67                 # [deg] Rotator angle, if any
INSTRUME = Cerbero                # Instrument name
OBSTYPE = Imaging                 # Observation type
DETECTOR = SBIG STX-16081         # Detector identifier
DETSIZE = [1:4144,1:4126]         # [px] [1:x,1:y] Physical CCD dimensions
DETROT = -89.67                   # [deg] Rotation offset of the detector
XPIXSZ = 9                        # [um] Pixel X axis size
YPIXSZ = 9                        # [um] Pixel Y axis size
PIXSCALE = 0.283                  # [arcsec/px] Plate scale in binning 1
@@ -37,4 +37,8 @@ TEMPERAT = HIERARCH CAM AMBIENT # [C] Ambient temperature
SWCREATE = SWCREATE               # Software that created FILEORIG

[Formula]
RA = (x)/15
RA = x/15

[Tweak]
CRPIX1 = x+234
CRPIX2 = x-100
Loading