From 0b5db28ebf5c8ee7fba765a6478dcda5ec42755a Mon Sep 17 00:00:00 2001 From: vertighel Date: Fri, 25 Jul 2025 17:08:14 +0200 Subject: [PATCH] Modified pyproject, now deamon works launching executable noche-daemon --- noche/__init__.py | 1 + noche/{noche_daemon.py => daemon.py} | 32 +++---- noche/noche.py | 130 +++++++++++++-------------- pyproject.toml | 49 ++++++++++ 4 files changed, 130 insertions(+), 82 deletions(-) rename noche/{noche_daemon.py => daemon.py} (97%) diff --git a/noche/__init__.py b/noche/__init__.py index 9fd4e27..712f317 100644 --- a/noche/__init__.py +++ b/noche/__init__.py @@ -1 +1,2 @@ from .noche import Noche + diff --git a/noche/noche_daemon.py b/noche/daemon.py similarity index 97% rename from noche/noche_daemon.py rename to noche/daemon.py index d7ca655..e729829 100644 --- a/noche/noche_daemon.py +++ b/noche/daemon.py @@ -22,13 +22,13 @@ def load_daemon_config(config_file): ------- tuple A tuple containing two dictionaries: daemon settings and logging settings. - + Raises ------ SystemExit If configuration file is not found or parsing fails. """ - + config = configparser.ConfigParser(inline_comment_prefixes=('#',)) if not Path(config_file).exists(): print(f"Error: Configuration file '{config_file}' not found.") @@ -63,7 +63,7 @@ class FitsFileHandler(FileSystemEventHandler): """ Handles file system events for detecting and processing new FITS files. """ - + def __init__(self, output_dir, observatory_config, header_template=None): """ Initializes the FitsFileHandler. @@ -77,7 +77,7 @@ class FitsFileHandler(FileSystemEventHandler): header_template : str, optional Path to the header template file. Defaults to None. """ - + super().__init__() self.output_dir = Path(output_dir) self.observatory_config = observatory_config @@ -88,7 +88,7 @@ class FitsFileHandler(FileSystemEventHandler): self.noche = Noche(header_template_path=self.header_template) - + def on_created(self, event): """ Called when a file or directory is created. @@ -98,12 +98,12 @@ class FitsFileHandler(FileSystemEventHandler): event : watchdog.events.FileSystemEvent The event object containing information about the file system change. """ - + if event.is_directory: return file_path = Path(event.src_path) - + if file_path not in self.processed_files: log.info(f"Detected new file: {file_path.name}") self.processed_files.add(file_path) @@ -112,7 +112,7 @@ class FitsFileHandler(FileSystemEventHandler): except Exception as e: log.error(f"Error processing {file_path.name}: {e}") - + def process_fits_file(self, input_fits_path: Path): """ Processes a single FITS file by loading it into Noche, applying @@ -123,7 +123,7 @@ class FitsFileHandler(FileSystemEventHandler): input_fits_path : Path The path to the input FITS file. """ - + log.info(f"Processing: {input_fits_path.name}") try: @@ -137,7 +137,7 @@ class FitsFileHandler(FileSystemEventHandler): log.error(f"Failed to load observatory configuration: {e}") try: - + output_filename = self.output_dir / f"processed_{input_fits_path.name}" log.info(f"Writing processed file to: {output_filename}") self.noche.write_noctis_fits(filename=str(output_filename), overwrite=True) @@ -146,7 +146,7 @@ class FitsFileHandler(FileSystemEventHandler): except Exception as e: log.error(f"An unexpected error occurred processing {input_fits_path.name}: {e}") - + def start_daemon(daemon_settings): """ Starts the FITS file monitoring daemon with provided settings. @@ -193,13 +193,15 @@ def start_daemon(daemon_settings): observer.join() log.info("Noche daemon stopped.") - -if __name__ == "__main__": - +def run(): try: daemon_settings = load_daemon_config(sys.argv[1]) except SystemExit: - sys.exit(1) + sys.exit(1) start_daemon(daemon_settings) + + +if __name__ == "__main__": + run() diff --git a/noche/noche.py b/noche/noche.py index e2d6ad6..fa672bc 100644 --- a/noche/noche.py +++ b/noche/noche.py @@ -124,7 +124,7 @@ class Noche: self._update() - def fill_keyword(self, k, val): + def fill_keyword(self, k, val, silent=False): """ Fill new header keyword with its value. Why use this function instea of simply self.header[keyword] = value? @@ -146,13 +146,14 @@ class Noche: val = self._parse(val) - # log.debug(f"{val}, {typ}, {fmt}") - if typ.strip() == "float": - val = header_round(val, int(fmt)) + with custom_float(): + val = header_round(val, int(fmt)) self.header[k] = val - log.info(f"{k:<11}: filled with {val} as {typ:<5}, {fmt:<1} digits") + + if not silent: + log.info(f"{k:<11} : filled with value : {val} as {typ:<5}, {fmt:<1} digits") def load_noctis_observatory(self, name='oarpaf', fits_file=None): @@ -214,8 +215,8 @@ class Noche: log.warning(f"{k:<8} : fixed value missing! Leave blank") pass else: - log.info(f"{k:<11} : set to fixed value, value: {val:<28}") - self.fill_keyword(k, val) + log.info(f"{k:<11} : fixed to the value : {val:<28}") + self.fill_keyword(k, val, silent=True) if fits_file: self.fill_from_fits_file(path, fits_file) @@ -262,8 +263,8 @@ class Noche: try: val = fits_file_header[fits_keyword] - self.fill_keyword(k, val) - log.info(f"{k:<11} : mapped to {fits_keyword:<11}, value: {val:<28}") + self.fill_keyword(k, val, silent=True) + log.info(f"{k:<11} : mapped to {fits_keyword:<11} : {val:<28}") except TypeError as e: log.debug(e) log.warning(f"{k:<8} : mapped to {fits_keyword:<11}, but empty! Leave empty.") @@ -279,8 +280,8 @@ class Noche: x = self.header[k] # i.e. value of RA: x=12.345 new_val = eval(pre_formulas[k]) try: - self.fill_keyword(k, new_val) - log.warning(f"{k:<11}: pre-formula from {x:<28} to {self.header[k]:<28}") + self.fill_keyword(k, new_val, silent=True) + log.warning(f"{k:<8} : pre-formula from value : {x:<11} to {self.header[k]:<11}") except Exception as e: log.error(f"Failed to evaluate pre-formula for {k}: {e}") @@ -294,8 +295,8 @@ class Noche: x = self.header[k] new_val = eval(formula) try: - self.fill_keyword(k, new_val) - log.warning(f"{k:<8}: tweak from {x:<28} to {self.header[k]:<28}") + self.fill_keyword(k, new_val, silent=True) + log.warning(f"{k:<8} : tweak from value : {x:11} to {self.header[k]:<11}") except Exception as e: log.error(f"Failed to evaluate tweak for {k}: {e}") else: @@ -413,14 +414,13 @@ class Noche: time = Time(obstime) self.set_obstime(time) - with custom_float(): - self.fill_keyword("RA", coord.ra.to_string(unit=u.hourangle, sep=':', - pad=True, precision=1) ) - self.fill_keyword("DEC", coord.dec.to_string(unit=u.deg, sep=':', - pad=True, precision=1) ) - self.fill_keyword("RA_DEG", coord.ra.deg) - self.fill_keyword("DEC_DEG", coord.dec.deg) + self.fill_keyword("RA", coord.ra.to_string(unit=u.hourangle, sep=':', + pad=True, precision=1) ) + self.fill_keyword("DEC", coord.dec.to_string(unit=u.deg, sep=':', + pad=True, precision=1) ) + self.fill_keyword("RA_DEG", coord.ra.deg) + self.fill_keyword("DEC_DEG", coord.dec.deg) self._update() @@ -444,31 +444,30 @@ class Noche: altaz = self._coord.altaz # Altitudine and Azimuth - with custom_float(): - self.fill_keyword("ALT", altaz.alt.deg) - self.fill_keyword("AZ", altaz.az.deg) - self.fill_keyword("AIRMASS", altaz.secz.value) + self.fill_keyword("ALT", altaz.alt.deg) + self.fill_keyword("AZ", altaz.az.deg) + self.fill_keyword("AIRMASS", altaz.secz.value) - # Local Sideral Time and Hour Angle - lst = self._obstime.sidereal_time('mean', longitude=self._location.lon) - ha = (lst - self._coord.ra).hour - lst_hours = lst.hour + # Local Sideral Time and Hour Angle + lst = self._obstime.sidereal_time('mean', longitude=self._location.lon) + ha = (lst - self._coord.ra).hour + lst_hours = lst.hour - self.fill_keyword("LST", lst_hours) - self.fill_keyword("HA", ha) + self.fill_keyword("LST", lst_hours) + self.fill_keyword("HA", ha) - # Position angle: with respect to Celestial North Pole - north_celestial = SkyCoord(ra=0*u.deg, dec=90*u.deg, frame='icrs') - posangle = self._coord.position_angle(north_celestial).to(u.deg).value - self.fill_keyword("POSANGLE", posangle) + # Position angle: with respect to Celestial North Pole + north_celestial = SkyCoord(ra=0*u.deg, dec=90*u.deg, frame='icrs') + posangle = self._coord.position_angle(north_celestial).to(u.deg).value + self.fill_keyword("POSANGLE", posangle) - # Parallactic angle: between local meridian and celestial axis - parangle = (posangle - altaz.az.deg + 360) % 360 - if parangle > 180: - parangle -= 360 # Wrap to [-180, 180] + # Parallactic angle: between local meridian and celestial axis + parangle = (posangle - altaz.az.deg + 360) % 360 + if parangle > 180: + parangle -= 360 # Wrap to [-180, 180] - self.fill_keyword("PARANGLE", parangle) + self.fill_keyword("PARANGLE", parangle) def set_wcs(self, angle=None): @@ -497,10 +496,9 @@ class Noche: _, xsize = map(int, x_str.split(":")) _, ysize = map(int, y_str.split(":")) - with custom_float(): - crpix = [xsize/self.header["XBINNING"]/2, ysize/self.header["YBINNING"]/2] - cdelt1 = self.header["PIXSCALE"]*self.header["XBINNING"]*u.arcsec.to(u.deg) - cdelt2 = self.header["PIXSCALE"]*self.header["YBINNING"]*u.arcsec.to(u.deg) + crpix = [xsize/self.header["XBINNING"]/2, ysize/self.header["YBINNING"]/2] + cdelt1 = self.header["PIXSCALE"]*self.header["XBINNING"]*u.arcsec.to(u.deg) + cdelt2 = self.header["PIXSCALE"]*self.header["YBINNING"]*u.arcsec.to(u.deg) if not angle: angle = self.header["DEROTANG"] + self.header["DETROT"] @@ -515,17 +513,16 @@ class Noche: flip = -1 # East to the left - with custom_float(): - self.fill_keyword("CRPIX1", crpix[0]) - self.fill_keyword("CRPIX2", crpix[1]) - self.fill_keyword("CRVAL1", crval_ra) - self.fill_keyword("CRVAL2", crval_dec) - self.fill_keyword("CDELT1", cdelt1 * flip) - self.fill_keyword("CDELT2", cdelt2) - self.fill_keyword("PC1_1", +np.cos(angle)) - self.fill_keyword("PC1_2", -np.sin(angle)) - self.fill_keyword("PC2_1", +np.sin(angle)) - self.fill_keyword("PC2_2", +np.cos(angle)) + self.fill_keyword("CRPIX1", crpix[0]) + self.fill_keyword("CRPIX2", crpix[1]) + self.fill_keyword("CRVAL1", crval_ra) + self.fill_keyword("CRVAL2", crval_dec) + self.fill_keyword("CDELT1", cdelt1 * flip) + self.fill_keyword("CDELT2", cdelt2) + self.fill_keyword("PC1_1", +np.cos(angle)) + self.fill_keyword("PC1_2", -np.sin(angle)) + self.fill_keyword("PC2_1", +np.sin(angle)) + self.fill_keyword("PC2_2", +np.cos(angle)) def set_ambient(self): @@ -552,21 +549,18 @@ class Noche: moon = get_body("moon", time) moon.location = loc + # MOONDIST: angular distance between target and Moon + moondist = moon.separation(self._coord, origin_mismatch="ignore").deg + self.fill_keyword("MOONDIST", moondist) - with custom_float(): - - # MOONDIST: angular distance between target and Moon - moondist = moon.separation(self._coord, origin_mismatch="ignore").deg - self.fill_keyword("MOONDIST", moondist) + # MOONPHAS: Moon phase + elongation = moon.separation(sun).deg + moonphas = (1 + np.cos(np.radians(elongation))) / 2 + self.fill_keyword("MOONPHAS", moonphas) - # MOONPHAS: Moon phase - elongation = moon.separation(sun).deg - moonphas = (1 + np.cos(np.radians(elongation))) / 2 - self.fill_keyword("MOONPHAS", moonphas) - - # SUNALT: Sun altitude above the horizon - sunalt = sun.altaz.alt.deg - self.fill_keyword("SUNALT", sunalt) + # SUNALT: Sun altitude above the horizon + sunalt = sun.altaz.alt.deg + self.fill_keyword("SUNALT", sunalt) def check_empty(self): @@ -579,6 +573,7 @@ class Noche: if self.header[k] == None: log.warning(f"{k:<8}: still empty ({self.header[k]})") + def load_fits(self, fits_file): log.debug(sys._getframe().f_code.co_name) @@ -593,6 +588,7 @@ class Noche: noche_hdu.writeto(filename, overwrite=overwrite, checksum=True) + @staticmethod def _load_config(path): log.debug(sys._getframe().f_code.co_name) diff --git a/pyproject.toml b/pyproject.toml index 7662c9b..5e5db44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,52 @@ [build-system] requires = ["setuptools>=42", "wheel", "watchdog"] build-backend = "setuptools.build_meta" + +[project] +name = "noche" +version = "0.1.0" +authors = [ + { name="Davide Ricci", email="davide.ricci@inaf.it" }, +] + +description = "NOCTIS common header." + +readme = "README.md" +requires-python = ">=3.12" + +#license = { file="LICENSE" } # If you have a LICENSE file +classifiers = [ + "Programming Language :: Python :: 3", +# "License :: OSI Approved :: MIT License", # Choose your license + "Operating System :: OS Independent", + "Topic :: Scientific/Engineering :: Astronomy", + "Intended Audience :: Developers", + "Development Status :: 3 - Alpha", # Or appropriate status +] + +dependencies = [ + "astropy", + "numpy", +] + +[project.urls] +"Homepage" = "https://www.ict.inaf.it/gitlab/noctis/noche" +# "Bug Tracker" = "..." + +[project.scripts] +noche-daemon = "noche.daemon:run" + + +[tool.setuptools.packages.find] +where = ["."] # list of folders that contain the packages (["."] by default) +include = ["noche"] # package names should match these glob patterns (["*"] by default) +exclude = [] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) + + +[tool.setuptools.package-data] +noche = [ + "headers/*.ini", + "observatories/*.ini", + "daemon_config.ini" +] -- GitLab