diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..01fdf4284655da0925c5e1e42c5bbea74ff230a5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,178 @@
+.*
+*~
+#*
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# UV
+#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#uv.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+# Ruff stuff:
+.ruff_cache/
+
+# PyPI configuration file
+.pypirc
\ No newline at end of file
diff --git a/README.md b/README.md
index 6b4908e1805430eee6d22fae9b1f296d4e4cbfa8..cf441516fff77dbab537616547592657f60176ad 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,198 @@
-# noche
+
+# Noche - Noctis Header. 
+## FITS Header Builder for the observatories of the NOCTIS network
+
+**Noche** is a Python module that helps construct FITS headers for
+imaging (and maybe spectroscopic) observations using templates and
+observatory-specific metadata.
+
+It is developed for the NOCTIS - Network of Observing Networked
+Observational Coordination of Telescopes for Teaching and Science
+network.
+
+https://noctis.inaf.it/en/index.html
+
+## Features
+- Load an empty base header from a default `.ini` file
+- Set a NOCTIS observatory from default `.ini` files, or from a custom file;
+- Resolve object coordinates via Simbad from name, or set coordinates;
+- Automatically compute WCS, parallactic angle, airmass, and moon/sun-related metadata, etc.
+
+## Installation
+
+```bash
+# Clone the repository
+git clone https://www.ict.inaf.it/gitlab/noctis/noche.git
+
+# Enter the directory
+cd noche
+
+# Optional: Install it locally if you want
+pip install -e .
+```
+
+## Usage Example
+```python
+from noche import Noche
+from astropy.time import Time
+
+# Instantiate a Noche class. It loads by default an empty header
+n = Noche() 
+
+# Inspect with
+print(n.header.tostring)
+
+# Show which observatories belongs to the NOCTIS network
+print(n.noctis_observatories)
+# ['grt', 'opc', 'oarpaf', 'ogg', 'ossfoligno', 'abobservatory']
+
+# Load the values specific of one of the NOCTIS observatories
+n.load_noctis_observatory("oarpaf")
+
+# You can inspect changes with
+print(n.header.tostring)
+
+# Load the values from a custom .ini file:
+n.load_observatory("./noche/observatories/opc.ini")
+
+# Set coordinates and observation time. 
+n.set_coordinates("05 30 00.0", "70 01 01", Time.now().isot)
+
+# Set an object name, 
+# optionally resolving and update coordinates from Simbad, 
+# optionally update observation time
+n.set_object("Polaris", update_coord=True, obstime="2025-10-13T22:33:44.5")
+
+# Update Wcs Keywords providing an angle:
+n.set_wcs(45)
+
+# List the header keywords that are still empty
+n.check_empty()
+
+```
+
+---
+
+## Class and Method Documentation
+
+### `class Noche`
+Builds and maintains an astropy FITS header for astronomical observations.
+
+#### `__init__(self, header_template_path=None)`
+Initializes a new FITS header.
+
+**Parameters**
+- `header_template_path` : str
+  Path to the header configuration template. If `None`, use default.
+
+#### `@property def noctis_observatories(self)`
+ist available [observatory].ini files in the module directory.
+
+#### `load_default_header(self, path)`
+Loads the base header from a default or a custom `.ini` configuration file.
+
+**Parameters**
+- `path` : str
+  Path to a config file in INI format.
+
+#### `load_noctis_observatory(self, name='oarpaf', flavor=None)`
+Loads observatory site data and updates header values accordingly.
+
+**Parameters**
+- `name` : str
+  One of the supported observatories.
+- `flavor` : str, optional
+  Specific section in the file to load.
+
+#### `load_observatory(self, path='oarpaf.ini', flavor=None)`
+Loads observatory site data and updates header values accordingly.
+
+**Parameters**
+- `path` : str
+  Path to the observatory INI file.
+- `flavor` : str, optional
+  Specific section in the file to load.
+
+#### `set_location(self, lon, lat, alt)`
+Sets the location of the observatory.
+
+**Parameters**
+- `lon` : float
+  Longitude in degrees
+- `lat` : float
+  Latitude in degrees
+- `alt` : float
+  Altitude in meters
+
+#### `set_obstime(self, obstime)`
+Sets the observation time.
+
+**Parameters**
+- `obstime` : str or `astropy.time.Time`
+  Time of the observation.
+
+#### `set_object(self, objname, update_coord=True, obstime=None)`
+Sets object name and optionally resolves coordinates via Simbad.
+
+**Parameters**
+- `objname` : str
+  Name of the astronomical object.
+- `update_coord` : bool, default=True
+  Whether to resolve and update coordinates.
+- `obstime` : str or `astropy.time.Time`, optional
+  Time of the observation.
+
+#### `set_coordinates(self, ra, dec, obstime=None)`
+Sets the RA and DEC of the object.
+
+**Parameters**
+- `ra` : float
+  Right Ascension (in hours or degrees).
+- `dec` : float
+  Declination (in degrees).
+- `obstime` : str or `astropy.time.Time`, optional
+  Time of the observation.
+
+#### `set_altaz_and_parallactic(self)`
+Calculates altitude, azimuth, airmass, hour angle, and parallactic angle.
+
+**Raises**
+- `ValueError` if observation time or location is not set.
+
+#### `set_wcs(self, angle=None)`
+Sets World Coordinate System (WCS) parameters in the header.
+
+**Parameters**
+- `angle` : float, optional
+  Rotation angle in radians. Defaults to DEROTANG header value.
+
+**Raises**
+- `ValueError` if coordinates or location are missing.
+
+#### `set_ambient(self)`
+Adds information on Sun altitude, Moon phase, and Moon distance to target.
+
+**Raises**
+- `ValueError` if coordinates or location are missing.
+
+#### `check_empty(self)`
+Prints out FITS header keys that have `None` values.
+
+#### `_update(self)`
+Automatically fills in derived keywords if coordinates, time, and location are available.
+
+#### `_parse(self, val)`
+Parses a value from a configuration string.
+
+**Parameters**
+- `val` : str
+  Value as string from config file.
+
+**Returns**
+- Parsed value as `int`, `float`, `bool`, or `str`.
+
+---
+
 
 
 
@@ -44,50 +238,6 @@ Use the built-in continuous integration in GitLab.
 
 ***
 
-# Editing this README
-
-When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
-
-## Suggestions for a good README
-
-Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
-
-## Name
-Choose a self-explaining name for your project.
-
-## Description
-Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
-
 ## Badges
 On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
 
-## Visuals
-Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
-
-## Installation
-Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
-
-## Usage
-Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
-
-## Support
-Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
-
-## Roadmap
-If you have ideas for releases in the future, it is a good idea to list them in the README.
-
-## Contributing
-State if you are open to contributions and what your requirements are for accepting them.
-
-For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
-
-You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
-
-## Authors and acknowledgment
-Show your appreciation to those who have contributed to the project.
-
-## License
-For open source projects, say how it is licensed.
-
-## Project status
-If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
diff --git a/noche/__init__.py b/noche/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fd4e27cdc0cf01d363eaf89dced07a5b6b09967
--- /dev/null
+++ b/noche/__init__.py
@@ -0,0 +1 @@
+from .noche import Noche
diff --git a/noche/headers/header_base_v1.ini b/noche/headers/header_base_v1.ini
new file mode 100644
index 0000000000000000000000000000000000000000..57d62c1bb686c6db21e4e2ee3f2bae9e74ed1a6b
--- /dev/null
+++ b/noche/headers/header_base_v1.ini
@@ -0,0 +1,96 @@
+#------------------------------------------------------------------------------|
+[Initial]
+SIMPLE = True         | Standard FITS format
+BITPIX = 16           | Array data type
+NAXIS = 2             | Number of data axes
+NAXIS1 =              | [px] Length of data axis 1 (X)
+NAXIS2 =              | [px] Length of data axis 2 (Y)
+EXTEND = True         | FITS file may contain extensions
+BSCALE = 1            | Scale factor applied to data
+BZERO = 32768         | Offset applied to data after scaling
+OBSERVER =            | Observer name
+
+[Object]
+OBJECT =              | Name of observed object
+RA =                  | [hh:mm:ss.ss] Right Ascension in sexagesimal format
+DEC =                 | [+dd:mm:ss.ss] Declination in sexagesimal format
+RA_DEG =              | [deg] Right Ascension in decimal degrees
+DEC_DEG =             | [deg] Declination in decimal degrees
+ALT =                 | [deg] Altitude of object above horizon
+AZ =                  | [deg] Azimuth of object from North (E=90)
+HA =                  | [h] Hour Angle
+LST =                 | [h] Local Sidereal Time
+PARANGLE =            | [deg] Object Parallactic angle
+POSANGLE =            | [deg] Object Position angle
+AIRMASS =             | Approximate air mass
+
+[Time]
+DATE =                | [YYYY-MM-DD] File creation date
+DATE-OBS =            | [YYYY-MM-DDTHH:MM:SS] UTC date and time of observation
+MJD-OBS =             | [d] Modified Julian Date of observation
+                      
+[Telescope]           
+TELESCOP =            | Telescope name
+FOCALLEN =            | [mm] Telescope focal length
+APTDIA =              | [mm] Telescope aperture diameter
+OBS-LONG =            | [deg] Observatory longitude (East < 0)
+OBS-LAT =             | [deg] Observatory latitude (North > 0)
+OBS-ELEV =            | [m] Observatory altitude above sea level
+FOCUSPOS =            | [um] Focuser position
+FOCUSTEM =            | [C] Focuser temperature
+DEROTANG = 0          | [deg] Rotator angle
+                      
+[Imaging]          
+INSTRUME =            | Instrument name
+OBSTYPE =             | Observation type
+
+[Detector]          
+DETECTOR =            | Detector identifier
+DETSIZE =             | [px] Physical CCD dimensions
+XPIXSZ =              | [um] Pixel X axis size
+YPIXSZ =              | [um] Pixel Y axis size
+PIXSCALE =            | [arcsec/px] Plate scale in binning 1
+SET-TEMP =            | [C] CCD temperature set point
+CCD-TEMP =            | [C] CCD temperature
+GAIN =                | [e-/ADU] Gain
+RDNOISE =             | [e- RMS] Readout noise
+
+IMAGETYP =            | Frame type (LIGHT, DARK, BIAS, FLAT)
+FILTER =              | Photometric filter used
+EXPTIME =             | [s] Exposure time
+XBINNING =            | Binning factor in X
+YBINNING =            | Binning factor in Y
+                      
+[Ambient]             
+SUNALT =              | [deg] Sun altitude (< 0 means night)
+MOONDIST =            | [deg] Moon-target angular distance
+MOONPHAS =            | Lunar phase (0=new, 1=full)
+TEMPERAT =            | [C] Ambient temperature
+
+[WCS]
+WCSAXES = 2           | Number of axes
+CTYPE1 = RA---TAN     | Right ascension, gnomonic projection
+CTYPE2 = DEC--TAN     | Declination, gnomonic projection
+CUNIT1 = deg          | Units of coordinate axis 1
+CUNIT2 = deg          | Units of coordinate axis 2
+CRPIX1 =              | [px] X reference pixel
+CRPIX2 =              | [px] Y reference pixel
+CRVAL1 =              | [deg] RA at reference pixel
+CRVAL2 =              | [deg] DEC at reference pixel
+CDELT1 =              | [deg/px] RA pixel scale
+CDELT2 =              | [deg/px] DEC pixel scale
+PC1_1 = -1.0          | Rotation matrix element
+PC1_2 = 0.0           | Rotation matrix element
+PC2_1 = 0.0           | Rotation matrix element
+PC2_2 = 1.0           | Rotation matrix element
+RADESYS = ICRS        | Coordinate system reference frame
+EQUINOX = 2000.0      | [yr] Equinox of coordinates
+
+[Final]
+FILEORIG =            | Original file name
+SWCREATE =            | Software that created this file
+CHECKSUM =            | Checksum of header
+DATASUM =             | Data sum of FITS file
+ORIGIN = NOCHE v0.1   | Origin of FITS file
+COMMENT = NOCTIS common header
+HISTORY = Updated by NOCHE
diff --git a/noche/noche.py b/noche/noche.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c31bb7c47a81f57a1afe548f1878892161a0e85
--- /dev/null
+++ b/noche/noche.py
@@ -0,0 +1,451 @@
+import configparser
+from pathlib import Path
+
+import numpy as np
+from astropy.coordinates import SkyCoord, EarthLocation
+from astropy.coordinates import get_body, solar_system_ephemeris
+from astropy.coordinates.name_resolve import NameResolveError
+from astropy.time import Time
+from astropy.io import fits
+from astropy import units as u
+from astropy import log
+
+class Noche:
+    """
+    This class provides tools to initialize and populate a FITS header for
+    imaging or spectroscopic observations based on a configurable header
+    template and site-specific observatory data.
+    
+    Parameters
+    ----------
+    header_template_path : str, optional
+        Path to the .ini configuration file with base header structure.
+    
+    Attributes
+    ----------
+    header : astropy.io.fits.Header
+        The FITS header object being constructed.
+    _coord : astropy.coordinates.SkyCoord or None
+        Sky coordinates of the observed object.
+    _obstime : astropy.time.Time or None
+        Observation time.
+    _location : astropy.coordinates.EarthLocation or None
+        Observatory location.
+    """
+    
+    def __init__(self, header_template_path=None, debug=False):
+        """
+        Initialize the header builder with a template .ini file.
+
+        Parameters
+        ----------
+        header_template_path_ini : str
+            Path to the configuration file containing the base header.
+        """
+
+        if debug:
+            log.setLevel('DEBUG')
+        
+        self._coord = None
+        self._location = None
+        self._obstime = None
+
+        self._head_dir = "headers"
+        self._obs_dir = "observatories"
+        
+        self.header = fits.Header()
+        self.load_default_header(header_template_path)
+        # self.load_noctis_observatory("oarpaf")
+        # self.set_coordinates(05.3, 70.0, Time.now().isot)
+
+        
+    @property
+    def noctis_observatories(self):
+        """
+        List available [observatory].ini files in the module directory.
+
+        Returns
+        -------
+        list of str
+            Names of available observatory configuration files (without .ini extension).
+        """
+        data_dir = Path(__file__).parent / self._obs_dir
+        return [f.stem for f in data_dir.glob("*.ini")]
+
+        
+    def load_default_header(self, path):
+        """
+        Load header from configuration file
+        
+        Parameters
+        ----------
+        path : str
+            Configuration file in .ini format
+        """
+
+        if not path:
+            path = Path(__file__).parent / self._head_dir / "header_base_v1.ini"
+            log.info(path)
+        
+        config = configparser.ConfigParser(inline_comment_prefixes=('#',))
+        try:
+            config.read(path)
+        except IndexError as e:
+            log.error("File not found")
+            
+        for section in config.sections():
+            for key, value in config.items(section):
+                
+                try:
+                    val, comment = value.split("|")
+                except ValueError:
+                    # HISTORY and COMMENT have no value
+                    val, comment = value, None
+
+                val = self._parse(val)
+                self.header[key] = val, comment
+
+        self._update()        
+        
+        
+    def load_noctis_observatory(self, name='oarpaf', flavor=None):
+        data_dir = Path(__file__).parent / self._obs_dir
+        ini_path = data_dir / f"{name}.ini"
+
+        log.debug(ini_path)
+        if not ini_path.exists():
+            raise FileNotFoundError(f"Observatory config '{ini_path}' not found.")
+
+        self.load_observatory(str(ini_path), flavor=flavor)
+        
+        
+    def load_observatory(self, path=None, flavor=None):
+        """
+        Load observatory parameters such as location and detector specifics,
+        and update relevant FITS keywords.
+        
+        Parameters
+        ----------
+        path : str
+            Configuration file in .ini format.
+        flavor : str, optional
+            Section name of the configuration file. If not specified, takes the first one.
+        """
+        
+        config = configparser.ConfigParser(inline_comment_prefixes=('#',))
+        config.read(path)
+
+        sections = config.sections()
+
+        if not flavor:
+            loc = config[sections[0]]
+        else:
+            loc = config[flavor]
+
+        self.set_location(loc["OBS-LONG"], loc["OBS-LAT"], loc["OBS-ELEV"])
+            
+        for k in loc.keys():
+            val = self._parse(loc[k])
+            if k in self.header:
+                self.header[k] = val
+
+        self.header["DETSIZE"] = f'[1:{self.header["NAXIS1"]},1:{self.header["NAXIS2"]}]'
+                
+        self._update()
+
+        
+    def set_location(self, lon, lat, alt):
+        """
+        Location of the observatory, Separate so that it is
+        possible to add a location without a config file
+
+        Parameters
+        ----------
+        lon : float
+            Longitude in degrees.
+        lat : float
+            Latitude in degrees.
+        alt : float
+            Elevation in meters.
+        """
+        
+        self._location = EarthLocation(lon, lat, alt)
+        
+        self.header["OBS-LONG"] = lon
+        self.header["OBS-LAT"] = lat
+        self.header["OBS-ELEV"] = alt
+        
+        
+    def set_obstime(self, obstime):
+        """ Set observation time
+
+        Parameters
+        ----------
+        obstime : str, astropy.time.Time
+            Time of the observation.
+        """
+        
+        time = Time(obstime)        
+        self._obstime = time
+        
+        if self._coord != None:
+            self._coord.obstime = time
+        
+        self.header['DATE'] = time.isot.split("T")[0]
+        self.header['DATE-OBS'] = time.isot
+        self.header['MJD-OBS'] = time.mjd
+
+        self._update()
+        
+
+    def set_object(self, objname, update_coord=True, obstime=None):
+        """
+        Resolve object coordinates and set OBJECT keyword.
+    
+        Parameters
+        ----------
+        objname : str
+            Name of the object to be resolved.
+        update_coord : bool, optional
+            If True, updates coordinate keywords.
+        obstime : str or astropy.time.Time, optional
+            Observation time to associate with coordinates.
+        """
+            
+        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}")
+                
+                if obstime:
+                    self.set_coordinates(coord.ra, coord.dec, obstime)
+                else:
+                    self.set_coordinates(coord.ra, coord.dec)
+                    
+            except NameResolveError as e:
+                log.error("Cannot resolve name")
+                return [None, None]
+        
+        self.header["OBJECT"] = objname
+
+            
+    def set_coordinates(self, ra, dec, obstime=None):
+        """
+        RA e DEC in decimal or sexagesimal format, and ISO observation time
+        Set the object sky coordinates and compute related quantities.
+
+        Parameters
+        ----------
+        ra : float
+            Right Ascension in decimal degrees or hours.
+        dec : float
+            Declination in decimal degrees.
+        obstime : str or astropy.time.Time, optional
+            Observation time.
+        """
+
+        coord = SkyCoord(ra=ra, dec=dec, unit=(u.hourangle, u.deg))
+        self._coord = coord
+
+        if obstime:
+            time = Time(obstime)
+            self.set_obstime(time)
+            
+        self.header['RA'] = coord.ra.to_string(unit=u.hourangle, sep=':')
+        self.header['DEC'] = coord.dec.to_string(unit=u.deg, sep=':')
+        self.header['RA_DEG'] = coord.ra.deg
+        self.header['DEC_DEG'] = coord.dec.deg
+
+        self._update()
+
+            
+    def set_altaz_and_parallactic(self):
+        """
+        Compute and update Alt/Az, Airmass, LST, HA, Position and Parallactic Angle.
+    
+        Raises
+        ------
+        ValueError
+            If observation time or location is not set.
+        """
+        
+        if self._obstime == None or self._location == None:
+            raise ValueError("Observation Time, Instrument parameters must be set.")
+        
+        self._coord.obstime = self._obstime
+        self._coord.location = self._location
+        altaz = self._coord.altaz
+
+        # Altitudine and Azimuth
+        self.header['ALT'] = round(altaz.alt.deg, 7)
+        self.header['AZ'] = round(altaz.az.deg, 7)
+        self.header['AIRMASS'] = round(altaz.secz.value, 2)
+
+        # 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.header['LST'] = round(lst_hours, 2)
+        self.header['HA'] = round(ha, 2)
+
+        # 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)
+
+        # Parallactic angle:  between local meridian and celestial axis
+        parangle = (posang - altaz.az.deg + 360) % 360
+        if parangle > 180:
+            parangle -= 360  # Wrap to [-180, 180]
+
+        self.header['PARANGLE'] = round(parangle, 2)
+
+
+    def set_wcs(self, angle=None):
+        """
+        Set World Coordinate System (WCS) parameters.
+    
+        Parameters
+        ----------
+        angle : float, optional
+            Rotation angle in degrees. Defaults to DEROTANG.
+    
+        Raises
+        ------
+        ValueError
+            If coordinates or location are not set.
+        """
+          
+        if self._coord == None or self._location == None:
+            raise ValueError("Observation Coordinates, Instrument parameters must be set.")
+
+        crpix = [self.header['NAXIS1']/2, self.header['NAXIS2']/2]
+        cdelt = [round(self.header["PIXSCALE"]*u.arcsec.to(u.deg), 7),
+                 round(self.header["PIXSCALE"]*u.arcsec.to(u.deg), 7)]
+
+        if not angle:
+            angle = self.header["DEROTANG"]
+            
+        angle = np.deg2rad(angle)
+            
+        crval_ra = self._coord.ra.deg
+        crval_dec = self._coord.dec.deg
+
+        self.header['CRPIX1'] = crpix[0]
+        self.header['CRPIX2'] = crpix[1]
+        self.header['CRVAL1'] = crval_ra
+        self.header['CRVAL2'] = crval_dec
+        self.header['CDELT1'] = cdelt[0]
+        self.header['CDELT2'] = cdelt[1]
+        self.header["PC1_1"] = -np.cos(angle)
+        self.header["PC2_1"] = +np.sin(angle)
+        self.header["PC1_2"] = +np.sin(angle)
+        self.header["PC2_2"] = +np.cos(angle)
+
+        
+    def set_ambient(self):
+        """
+        Set ambient keywords: Moon distance, phase and Sun altitude.
+    
+        Raises
+        ------
+        ValueError
+            If coordinates or location are not set.
+        """
+            
+        if self._coord == None or self._location == None:
+            raise ValueError("Observation Time, Observing location must be set.")
+
+        time = self._coord.obstime
+        loc = self._location
+        
+        # Sun and Moon in alt-az coordinates
+        with solar_system_ephemeris.set('builtin'):
+            sun = get_body("sun", time)
+            sun.location = loc
+            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.header['MOONDIST'] = round(moondist, 1)
+    
+        # MOONPHAS: Moon phase
+        elongation = moon.separation(sun).deg
+        moonphas = (1 + np.cos(np.radians(elongation))) / 2
+        self.header['MOONPHAS'] = round(moonphas, 2)
+    
+        # SUNALT: Sun altitude above the horizon
+        sunalt = sun.altaz.alt.deg
+        self.header['SUNALT'] = round(sunalt, 1)
+
+        
+    def check_empty(self):
+        """
+        List the header keywords that are still empty
+        """
+        
+        for k in self.header:
+            if self.header[k] == None:
+                print(k, self.header[k])
+
+    
+    def _update(self):
+        """
+        If coordinates, observation and location are provided, then
+        additional keywords can be filled
+        """
+
+        if self._coord != None and self._obstime != None and self._location != None:
+            self.set_altaz_and_parallactic()
+            self.set_ambient()
+            self.set_wcs()
+
+            
+    def _parse(self, val):
+        """
+        Parse values from configuration file.
+    
+        Parameters
+        ----------
+        val : str
+            Value to be parsed.
+    
+        Returns
+        -------
+        int, float, bool, or str
+            Parsed value.
+        """
+                   
+        val = val.strip()
+            
+        if not val:
+            val = None
+        else:
+            try:
+                # float
+                val = float(val)
+                if  val.is_integer():
+                    # int
+                    val = int(val)
+            except ValueError as e:
+                # bool
+                if val.lower() == "true":
+                    val = True
+                elif val.lower() == "false":
+                    val = False
+                else:
+                    # string
+                    pass
+                
+        return val
+
diff --git a/noche/observatories/abobservatory.ini b/noche/observatories/abobservatory.ini
new file mode 100644
index 0000000000000000000000000000000000000000..83c09c832d57e64f1aa091712663d3347ec8dc67
--- /dev/null
+++ b/noche/observatories/abobservatory.ini
@@ -0,0 +1,35 @@
+[ABObservatory]
+TELESCOP = ABObservatory          # Telescope name
+APTDIA = 300                      # [mm] Telescope aperture diameter
+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
+INSTRUME = ABOb instrument        # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = Atik 383 L             # Detector identifier
+NAXIS1 = 3354                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 2529                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 5.4                      # [um] Pixel X axis size
+YPIXSZ = 5.4                      # [um] Pixel Y axis size
+PIXSCALE = 3600                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
+
+[ABObservatory-bis]
+TELESCOP = ABObservatory          # Telescope name
+APTDIA = 300                      # [mm] Telescope aperture diameter
+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
+INSTRUME = ABOb instrument        # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = Atik 383 L             # Detector identifier
+NAXIS1 = 3354                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 2529                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 5.4                      # [um] Pixel X axis size
+YPIXSZ = 5.4                      # [um] Pixel Y axis size
+PIXSCALE = 3600                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
diff --git a/noche/observatories/grt.ini b/noche/observatories/grt.ini
new file mode 100644
index 0000000000000000000000000000000000000000..3147d02ea70f8f7280c119ecc65e7532af7c4726
--- /dev/null
+++ b/noche/observatories/grt.ini
@@ -0,0 +1,35 @@
+[GRT]
+TELESCOP = GRT                    # Telescope name
+APTDIA = 400                      # [mm] Telescope aperture diameter
+FOCALLEN = 1520                   # [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
+INSTRUME = GRT instrument         # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = Moravian CMOS C4-16000 # Detector identifier
+NAXIS1 = 4096                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 4096                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 9                        # [um] Pixel X axis size
+YPIXSZ = 9                        # [um] Pixel Y axis size
+PIXSCALE = 1.22                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
+
+[GRT-bis]
+TELESCOP = GRT                    # Telescope name
+APTDIA = 400                      # [mm] Telescope aperture diameter
+FOCALLEN = 1520                   # [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
+INSTRUME = GRT instrument         # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = Moravian CMOS C4-16000 # Detector identifier
+NAXIS1 = 4096                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 4096                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 9                        # [um] Pixel X axis size
+YPIXSZ = 9                        # [um] Pixel Y axis size
+PIXSCALE = 1.22                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
diff --git a/noche/observatories/oarpaf.ini b/noche/observatories/oarpaf.ini
new file mode 100644
index 0000000000000000000000000000000000000000..cc7d8b3a45241075a86a147d8003e74c93fd42ec
--- /dev/null
+++ b/noche/observatories/oarpaf.ini
@@ -0,0 +1,35 @@
+[OARPAF]
+TELESCOP = OARPAF                 # Telescope name
+APTDIA = 800                      # [mm] Telescope aperture diameter
+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
+INSTRUME = Cerbero                # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = SBIG STX-16081         # Detector identifier
+NAXIS1 = 4144                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 4126                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 9                        # [um] Pixel X axis size
+YPIXSZ = 9                        # [um] Pixel Y axis size
+PIXSCALE = 0.29                   # [arcsec/px] Plate scale in binning 1
+GAIN = 1.5                        # [e-/ADU] Gain
+RDNOISE = 8.0                     # [e- RMS] Readout noise
+
+[OARPAF-bis]
+TELESCOP = OARPAF                 # Telescope name
+APTDIA = 800                      # [mm] Telescope aperture diameter
+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
+INSTRUME = Cerbero                # Instrument name
+OBSTYPE = Spectroscopy            # Observation type
+DETECTOR = SBIG STL-11000M        # Detector identifier
+NAXIS1 = 2048                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 4096                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 9                        # [um] Pixel X axis size
+YPIXSZ = 9                        # [um] Pixel Y axis size
+PIXSCALE = 0.29                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
diff --git a/noche/observatories/ogg.ini b/noche/observatories/ogg.ini
new file mode 100644
index 0000000000000000000000000000000000000000..9e9ea6341f009e6a1c93d4ba761a858f40fed209
--- /dev/null
+++ b/noche/observatories/ogg.ini
@@ -0,0 +1,35 @@
+[OGG]
+TELESCOP = OGG                    # Telescope name
+APTDIA = 300                      # [mm] Telescope aperture diameter
+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
+INSTRUME = OGG obs instrument     # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = CMOS QHY268            # Detector identifier
+NAXIS1 = 6280                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 4210                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 3.7                      # [um] Pixel X axis size
+YPIXSZ = 3.7                      # [um] Pixel Y axis size
+PIXSCALE = 4644                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
+
+[OGG-bis]
+TELESCOP = OGG                    # Telescope name
+APTDIA = 300                      # [mm] Telescope aperture diameter
+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
+INSTRUME = OGG obs instrument     # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = CMOS QHY268            # Detector identifier
+NAXIS1 = 6280                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 4210                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 3.7                      # [um] Pixel X axis size
+YPIXSZ = 3.7                      # [um] Pixel Y axis size
+PIXSCALE = 4644                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
diff --git a/noche/observatories/opc.ini b/noche/observatories/opc.ini
new file mode 100644
index 0000000000000000000000000000000000000000..d3d10b3804de312f0b29b214adb97a354313ebbe
--- /dev/null
+++ b/noche/observatories/opc.ini
@@ -0,0 +1,35 @@
+[OPC]
+TELESCOP = OPC                    # Telescope name
+APTDIA = 800                      # [mm] Telescope aperture diameter
+FOCALLEN = 6400                   # [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
+INSTRUME = OPC Instrument         # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = Moravian G4 - KAF09000 # Detector identifier
+NAXIS1 = 3056                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 3056                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 12                       # [um] Pixel X axis size
+YPIXSZ = 12                       # [um] Pixel Y axis size
+PIXSCALE = 0.38                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
+
+[OPC-bis]
+TELESCOP = OPC                    # Telescope name
+APTDIA = 800                      # [mm] Telescope aperture diameter
+FOCALLEN = 6400                   # [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
+INSTRUME = OPC Instrument         # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = Moravian G4 - KAF09000 # Detector identifier
+NAXIS1 = 3056                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 3056                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 12                       # [um] Pixel X axis size
+YPIXSZ = 12                       # [um] Pixel Y axis size
+PIXSCALE = 0.38                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
diff --git a/noche/observatories/ossfoligno.ini b/noche/observatories/ossfoligno.ini
new file mode 100644
index 0000000000000000000000000000000000000000..0e23e7b65fbbcc51e5870a090ee3a70d14d6539e
--- /dev/null
+++ b/noche/observatories/ossfoligno.ini
@@ -0,0 +1,35 @@
+[OSS-Foligno]
+TELESCOP = Oss-Foligno            # Telescope name
+APTDIA = 300                      # [mm] Telescope aperture diameter
+FOCALLEN = 1950                   # [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
+INSTRUME = Foligno instrument     # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = QHY174MM               # Detector identifier
+NAXIS1 = 1900                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 1200                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 5.86                     # [um] Pixel X axis size
+YPIXSZ = 5.86                     # [um] Pixel Y axis size
+PIXSCALE = 0.62                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
+
+[OSS-Foligno-bis]
+TELESCOP = Oss-Foligno            # Telescope name
+APTDIA = 300                      # [mm] Telescope aperture diameter
+FOCALLEN = 1950                   # [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
+INSTRUME = Foligno instrument     # Instrument name
+OBSTYPE = Imaging                 # Observation type
+DETECTOR = QHY174MM               # Detector identifier
+NAXIS1 = 1900                     # [px] X Dimensions of detector in binning 1
+NAXIS2 = 1200                     # [px] Y dimension of detector in binning 1
+XPIXSZ = 5.86                     # [um] Pixel X axis size
+YPIXSZ = 5.86                     # [um] Pixel Y axis size
+PIXSCALE = 0.62                   # [arcsec/px] Plate scale in binning 1
+GAIN =                            # [e-/ADU] Gain
+RDNOISE =                         # [e- RMS] Readout noise
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..8fe2f47af9a18ad9f272357023d09f78aefebe2d
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools>=42", "wheel"]
+build-backend = "setuptools.build_meta"
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a703bb76cfd942db9374656420b54a4a66d874e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,20 @@
+from setuptools import setup, find_packages
+
+setup(
+    name='noche',
+    version='0.1.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'
+    ],
+    classifiers=[
+        'Programming Language :: Python :: 3',
+        'License :: OSI Approved :: MIT License',
+        'Operating System :: OS Independent',
+    ],
+    python_requires='>=3.12',
+)
diff --git a/tests/test_core.py b/tests/test_core.py
new file mode 100644
index 0000000000000000000000000000000000000000..510287f5584254a320ee887d5e0b6bca1b763ba9
--- /dev/null
+++ b/tests/test_core.py
@@ -0,0 +1,54 @@
+import unittest
+from astropy.time import Time
+from noche import Noche
+from astropy.io.fits import Header
+
+class TestNoche(unittest.TestCase):
+
+    def setUp(self):
+        self.noche = Noche("noche/data/header_base.ini")
+        self.noche.load_observatory("noche/data/oarpaf.ini", flavor="OARPAF")
+
+    def test_init_creates_header(self):
+        self.assertIsInstance(self.noche.header, Header)
+
+    def test_set_location(self):
+        self.noche.set_location(10.0, 45.0, 100.0)
+        self.assertEqual(self.noche.header['OBS-LONG'], 10.0)
+        self.assertEqual(self.noche.header['OBS-LAT'], 45.0)
+        self.assertEqual(self.noche.header['OBS-ELEV'], 100.0)
+
+    def test_set_obstime(self):
+        now = Time.now()
+        self.noche.set_obstime(now)
+        self.assertEqual(self.noche.header['DATE-OBS'], now.isot)
+        self.assertAlmostEqual(self.noche.header['MJD-OBS'], now.mjd, places=5)
+
+    def test_set_coordinates(self):
+        self.noche.set_coordinates(ra=10.0, dec=20.0)
+        self.assertIn('RA', self.noche.header)
+        self.assertIn('DEC', self.noche.header)
+        self.assertAlmostEqual(self.noche.header['RA_DEG'], 150.0, places=4)  # 10h -> 150deg
+        self.assertAlmostEqual(self.noche.header['DEC_DEG'], 20.0, places=4)
+
+    def test_set_object_and_resolve(self):
+        self.noche.set_object("Vega")
+        self.assertEqual(self.noche.header['OBJECT'], "Vega")
+
+    def test_check_empty_does_not_raise(self):
+        # Should not raise even if empty fields exist
+        try:
+            self.noche.check_empty()
+        except Exception as e:
+            self.fail(f"check_empty() raised an exception: {e}")
+
+    def test_parse_various_types(self):
+        self.assertEqual(self.noche._parse("42"), 42)
+        self.assertEqual(self.noche._parse("3.14"), 3.14)
+        self.assertEqual(self.noche._parse("True"), True)
+        self.assertEqual(self.noche._parse("False"), False)
+        self.assertEqual(self.noche._parse("some string"), "some string")
+        self.assertIsNone(self.noche._parse("   "))
+
+if __name__ == '__main__':
+    unittest.main()