Loading noche/daemon_config.ini 0 → 100644 +10 −0 Original line number Diff line number Diff line [Daemon] monitor_directory = ./input_fits output_directory = ./processed_fits observatory_config = oarpaf # or path/to/your/config.ini header_template = [Logging] level = INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL noche/noche.py +2 −0 Original line number Diff line number Diff line Loading @@ -152,6 +152,8 @@ class Noche: config = self._load_config(path) log.info(f"Loaded path {path}") sections = config.sections() loc = config["Fixed"] Loading noche/noche_daemon.py 0 → 100644 +205 −0 Original line number Diff line number Diff line import time import sys import configparser from pathlib import Path from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from astropy import log from noche import Noche def load_daemon_config(config_file): """ Loads daemon configuration from an INI file. Parameters ---------- config_file : str Path to the configuration INI file. Returns ------- 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.") sys.exit(1) config.read(config_file) daemon_settings = {} try: daemon_settings['monitor_directory'] = config.get('Daemon', 'monitor_directory') daemon_settings['output_directory'] = config.get('Daemon', 'output_directory') daemon_settings['observatory_config'] = config.get('Daemon', 'observatory_config', fallback=None) daemon_settings['header_template'] = config.get('Daemon', 'header_template', fallback=None) log_level_str = config.get('Logging', 'level', fallback='INFO').upper() log.setLevel(log_level_str) except configparser.NoSectionError as e: print(f"Error: Missing section in {config_file}: {e}") sys.exit(1) except configparser.NoOptionError as e: print(f"Error: Missing option in {config_file}: {e}") sys.exit(1) except Exception as e: print(f"Error loading configuration from {config_file}: {e}") sys.exit(1) return daemon_settings 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. Parameters ---------- output_dir : str Directory to save processed FITS files. observatory_config : str or Path Configuration for the observatory (name or path). 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 self.header_template = header_template self.processed_files = set() self.output_dir.mkdir(parents=True, exist_ok=True) self.noche = Noche(header_template_path=self.header_template) def on_created(self, event): """ Called when a file or directory is created. Parameters ---------- 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) try: self.process_fits_file(file_path) 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 observatory configuration, and computing derived keywords. Parameters ---------- input_fits_path : Path The path to the input FITS file. """ log.info(f"Processing: {input_fits_path.name}") try: if isinstance(self.observatory_config, Path) and self.observatory_config.is_file(): self.noche.load_observatory(path=str(self.observatory_config), fits_file=input_fits_path) elif isinstance(self.observatory_config, str): self.noche.load_noctis_observatory(name=self.observatory_config, fits_file=input_fits_path) else: log.warning("No observatory configuration specified. Header will be built from template only.") except Exception as e: 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) log.info(f"Successfully processed and saved: {output_filename.name}") 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. Parameters ---------- daemon_settings : dict Dictionary containing daemon operational settings. """ input_dir = Path(daemon_settings['monitor_directory']) output_dir = Path(daemon_settings['output_directory']) obs_config = daemon_settings['observatory_config'] header_template = daemon_settings['header_template'] input_dir.mkdir(parents=True, exist_ok=True) log.info("Starting Noche daemon.") log.info(f"Monitoring directory: {input_dir.resolve()}") log.info(f"Output directory: {output_dir.resolve()}") log.info(f"Observatory Configuration: {obs_config}") log.info(f"Header Template: {header_template}") event_handler = FitsFileHandler( output_dir=str(output_dir), observatory_config=obs_config, header_template=header_template ) observer = Observer() observer.schedule(event_handler, str(input_dir), recursive=False) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: log.info("Shutting down Noche daemon...") observer.stop() except Exception as e: log.error(f"Daemon encountered an unhandled exception: {e}") observer.stop() finally: observer.join() log.info("Noche daemon stopped.") if __name__ == "__main__": try: daemon_settings = load_daemon_config(sys.argv[1]) except SystemExit: sys.exit(1) start_daemon(daemon_settings) pyproject.toml +1 −1 Original line number Diff line number Diff line [build-system] requires = ["setuptools>=42", "wheel"] requires = ["setuptools>=42", "wheel", "watchdog"] build-backend = "setuptools.build_meta" setup.py +3 −2 Original line number Diff line number Diff line Loading @@ -2,14 +2,15 @@ from setuptools import setup, find_packages setup( name='noche', version='0.1.0', version='0.2.0', description='Noctis Header - FITS Header Builder for observatories of the NOCTIS network', author='Davide Ricci', packages=find_packages(), include_package_data=True, install_requires=[ 'numpy', 'astropy' 'astropy', 'watchdog' ], classifiers=[ 'Programming Language :: Python :: 3', Loading Loading
noche/daemon_config.ini 0 → 100644 +10 −0 Original line number Diff line number Diff line [Daemon] monitor_directory = ./input_fits output_directory = ./processed_fits observatory_config = oarpaf # or path/to/your/config.ini header_template = [Logging] level = INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL
noche/noche.py +2 −0 Original line number Diff line number Diff line Loading @@ -152,6 +152,8 @@ class Noche: config = self._load_config(path) log.info(f"Loaded path {path}") sections = config.sections() loc = config["Fixed"] Loading
noche/noche_daemon.py 0 → 100644 +205 −0 Original line number Diff line number Diff line import time import sys import configparser from pathlib import Path from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from astropy import log from noche import Noche def load_daemon_config(config_file): """ Loads daemon configuration from an INI file. Parameters ---------- config_file : str Path to the configuration INI file. Returns ------- 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.") sys.exit(1) config.read(config_file) daemon_settings = {} try: daemon_settings['monitor_directory'] = config.get('Daemon', 'monitor_directory') daemon_settings['output_directory'] = config.get('Daemon', 'output_directory') daemon_settings['observatory_config'] = config.get('Daemon', 'observatory_config', fallback=None) daemon_settings['header_template'] = config.get('Daemon', 'header_template', fallback=None) log_level_str = config.get('Logging', 'level', fallback='INFO').upper() log.setLevel(log_level_str) except configparser.NoSectionError as e: print(f"Error: Missing section in {config_file}: {e}") sys.exit(1) except configparser.NoOptionError as e: print(f"Error: Missing option in {config_file}: {e}") sys.exit(1) except Exception as e: print(f"Error loading configuration from {config_file}: {e}") sys.exit(1) return daemon_settings 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. Parameters ---------- output_dir : str Directory to save processed FITS files. observatory_config : str or Path Configuration for the observatory (name or path). 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 self.header_template = header_template self.processed_files = set() self.output_dir.mkdir(parents=True, exist_ok=True) self.noche = Noche(header_template_path=self.header_template) def on_created(self, event): """ Called when a file or directory is created. Parameters ---------- 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) try: self.process_fits_file(file_path) 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 observatory configuration, and computing derived keywords. Parameters ---------- input_fits_path : Path The path to the input FITS file. """ log.info(f"Processing: {input_fits_path.name}") try: if isinstance(self.observatory_config, Path) and self.observatory_config.is_file(): self.noche.load_observatory(path=str(self.observatory_config), fits_file=input_fits_path) elif isinstance(self.observatory_config, str): self.noche.load_noctis_observatory(name=self.observatory_config, fits_file=input_fits_path) else: log.warning("No observatory configuration specified. Header will be built from template only.") except Exception as e: 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) log.info(f"Successfully processed and saved: {output_filename.name}") 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. Parameters ---------- daemon_settings : dict Dictionary containing daemon operational settings. """ input_dir = Path(daemon_settings['monitor_directory']) output_dir = Path(daemon_settings['output_directory']) obs_config = daemon_settings['observatory_config'] header_template = daemon_settings['header_template'] input_dir.mkdir(parents=True, exist_ok=True) log.info("Starting Noche daemon.") log.info(f"Monitoring directory: {input_dir.resolve()}") log.info(f"Output directory: {output_dir.resolve()}") log.info(f"Observatory Configuration: {obs_config}") log.info(f"Header Template: {header_template}") event_handler = FitsFileHandler( output_dir=str(output_dir), observatory_config=obs_config, header_template=header_template ) observer = Observer() observer.schedule(event_handler, str(input_dir), recursive=False) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: log.info("Shutting down Noche daemon...") observer.stop() except Exception as e: log.error(f"Daemon encountered an unhandled exception: {e}") observer.stop() finally: observer.join() log.info("Noche daemon stopped.") if __name__ == "__main__": try: daemon_settings = load_daemon_config(sys.argv[1]) except SystemExit: sys.exit(1) start_daemon(daemon_settings)
pyproject.toml +1 −1 Original line number Diff line number Diff line [build-system] requires = ["setuptools>=42", "wheel"] requires = ["setuptools>=42", "wheel", "watchdog"] build-backend = "setuptools.build_meta"
setup.py +3 −2 Original line number Diff line number Diff line Loading @@ -2,14 +2,15 @@ from setuptools import setup, find_packages setup( name='noche', version='0.1.0', version='0.2.0', description='Noctis Header - FITS Header Builder for observatories of the NOCTIS network', author='Davide Ricci', packages=find_packages(), include_package_data=True, install_requires=[ 'numpy', 'astropy' 'astropy', 'watchdog' ], classifiers=[ 'Programming Language :: Python :: 3', Loading