Commit 2c9dd8ab authored by vertighel's avatar vertighel
Browse files

Started developing daemon

parent 299df585
Loading
Loading
Loading
Loading
Loading
+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
+2 −0
Original line number Diff line number Diff line
@@ -152,6 +152,8 @@ class Noche:

        config = self._load_config(path)        

        log.info(f"Loaded path {path}")
        
        sections = config.sections()

        loc = config["Fixed"]

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)
+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"
+3 −2
Original line number Diff line number Diff line
@@ -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',