Coverage for src/spectroflat/smile/offset_map.py: 58%
72 statements
« prev ^ index » next coverage.py v7.3.2, created at 2024-03-28 07:59 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2024-03-28 07:59 +0000
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""
4provides OffsetMap
6@author: hoelken
7"""
8from __future__ import annotations
10import os
12import numpy as np
13from astropy.io import fits
15from ..base import Logging
17log = Logging.get_logger()
20class OffsetMap:
21 """
22 ## Offset Map
23 This class holds the map of offsets by the smile per line.
24 """
26 @staticmethod
27 def from_file(path) -> OffsetMap:
28 with fits.open(path) as hdul:
29 hdul[0].verify('silentfix')
30 om = OffsetMap()
31 om.header = hdul[0].header
32 om.map = hdul[0].data
33 om.error = hdul[1].data
34 om.path = path
35 return om
37 def __init__(self, header: dict = None):
38 #: The map holds a numpy array with the shifts per mod state.
39 #: If the map was squashed the map has shape y,x, else state,y,x
40 self.map = np.zeros((0, 0, 0))
41 #: The error is the per pixel chi² error from the central line.
42 #: If the map was squashed the map has shape y,x, else state,y,x
43 self.error = np.zeros((0, 0, 0))
44 self.header = None
45 self.path = None
46 header = header if header is not None else {}
47 header['squashed'] = False
48 self.set_header(header)
50 def set_header(self, header: dict) -> None:
51 self.header = fits.Header()
52 for key, value in header.items():
53 self.header[key] = str(value)
55 def get_offsets(self, row: int, state: int = 0) -> np.array:
56 img = self.map[state] if self.is_squashed else self.map
57 return img[int(row)]
59 def get_map(self, state: int = 0) -> np.array:
60 return self.map[state] if self.is_squashed() else self.map
62 def get_mean_errors(self):
63 if self.is_squashed():
64 return [np.mean(self.error)]
65 return [np.mean(self.error[s]) for s in range(self.map.shape[0])]
67 def get_max_errors(self):
68 if self.is_squashed():
69 return [np.max(self.error)]
70 return [np.max(self.error[s]) for s in range(self.map.shape[0])]
72 def dump(self, out_file, overwrite: bool = True):
73 """
74 Dump a SmileMap to a `fits` file.
75 HDU 0: shifts + header
76 HDU 1: chi2 error map
78 :param out_file: The file to write to
79 :param overwrite: Overwrite existing file with same name?
80 """
81 os.makedirs(os.path.dirname(out_file), exist_ok=True)
82 hdul = fits.HDUList()
83 phdu = fits.PrimaryHDU(self.map, header=self.header)
84 hdul.append(phdu)
85 hdul.append(fits.ImageHDU(data=self.error))
86 log.info('Write OffsetMap to "%s"...', out_file)
87 hdul.writeto(out_file, overwrite=overwrite)
88 self.path = out_file
90 def is_squashed(self) -> bool:
91 return self.header['squashed'] in ['True', True]
93 def squash(self) -> None:
94 if self.is_squashed():
95 return
97 self.header['squashed'] = True
98 s = self.map.shape[0]
99 self.map = np.repeat(self.map.mean(axis=0, keepdims=True), s, axis=0)
100 self.error = np.repeat(self.error.mean(axis=0, keepdims=True), s, axis=0)
102 def enforce_same_offsets_on_all_states(self):
103 states = self.map.shape[0]
104 self.map = np.repeat(np.mean(self.map, axis=0, keepdims=True, dtype='float32'), states, axis=0)
105 self.error = np.repeat(np.mean(self.error, axis=0, keepdims=True), states, axis=0)
107 def __repr__(self) -> str:
108 text = f'{self.__class__.__name__}\n'
109 for k, v in self.header.items():
110 text += f'{k}:\t {v}\n'
111 text += f'Shape:\t\t {self.map.shape}\n'
112 text += f'Mean Error:\t{np.mean(self.error)}'
113 return text