Skip to content

Commit

Permalink
Merge pull request #11 from lsst-sssc/ephemeris_service
Browse files Browse the repository at this point in the history
Ephemeris service
  • Loading branch information
mschwamb authored Aug 27, 2024
2 parents eab97bd + 0705ba0 commit b19efc8
Show file tree
Hide file tree
Showing 18 changed files with 1,976 additions and 40 deletions.
26 changes: 26 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
# Faint Solar System Object Detection Service

[![Template](https://img.shields.io/badge/Template-LINCC%20Frameworks%20Python%20Project%20Template-brightgreen)](https://lincc-ppt.readthedocs.io/en/latest/)

## Project Overview

This repository contains a set of Python modules designed to identify potential low signal-to-noise (S/N) detections of solar system objects specified by users.
The service searches for suitable survey images along the object's probable path and returns detections (within SNR and distance constraints) to the user, along with
associated metadata and optionally, image cutouts.

## Features

- Obtain list of solar system objects to query from users via web form or API
- Compute expected positions and uncertainty regions of given objects
- Identify survey exposures containing the object's predicted position
- Generate cutout images of each object's uncertainty region
- Retrieve existing higher-S/N (S/N>=5) survey sources in each uncertainty region
- Detect new low-S/N (S/N<5) sources within the uncertainty region
- Conduct forced photometry at the expected position of given objects

<h1 align="center">
<img src="https://raw.githubusercontent.com/lsst-sssc/ssoforcedphot/ephemeris_service/docs/SSSC_Faint_object_service.png" width="800">
</h1><br>

## Installation

(Instructions for installation will be added once more components are completed)
Binary file added docs/SSSC_Faint_object_service.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ nbsphinx
sphinx
sphinx-autoapi
sphinx-copybutton
sphinx-rtd-theme
sphinx-rtd-theme
astropy
astroquery
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ classifiers = [
dynamic = ["version"]
requires-python = ">=3.9"
dependencies = [
"astropy",
"astroquery",
"pandas",
"numpy"
]

[project.urls]
Expand Down Expand Up @@ -46,6 +50,9 @@ write_to = "src/forcedphot/_version.py"
testpaths = [
"tests",
]
pythonpath = [
"src",
]

[tool.black]
line-length = 110
Expand Down
3 changes: 0 additions & 3 deletions src/forcedphot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
from .example_module import greetings, meaning

__all__ = ["greetings", "meaning"]
Empty file.
154 changes: 154 additions & 0 deletions src/forcedphot/ephemeris/data_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import logging

import numpy as np
from astropy.table import Table
from astropy.time import Time

from forcedphot.ephemeris.data_model import EphemerisData


class DataLoader:
"""
DataLoader is a class for loading ephemeris data from ECSV files and converting it into the
EphemerisData class.
This class provides methods to load ephemeris data from ECSV files and convert it to the
EphemerisData class. It supports loading ephemeris data from a single ECSV file or from
multiple ECSV files.
Attributes:
logger (logging.Logger): Logger for the class.
Methods:
load_ephemeris_from_ecsv(file_path: str) -> EphemerisData:
Load ephemeris data from an ECSV file and return it as an EphemerisData object.
load_multiple_ephemeris_files(file_paths: list[str]) -> list[EphemerisData]:
Load ephemeris data from multiple ECSV files and return them as a list of EphemerisData objects.
"""

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@staticmethod
def load_ephemeris_from_ecsv(file_path: str) -> EphemerisData:
"""
Load ephemeris data from an ECSV file and return it as an EphemerisData object.
Parameters:
-----------
file_path : str
Path to the ECSV file containing ephemeris data.
Returns:
--------
EphemerisData
An EphemerisData object populated with the data from the ECSV file.
Raises:
-------
FileNotFoundError
If the specified file does not exist.
ValueError
If the ECSV file is missing required columns.
"""
try:
# Read the ECSV file
table = Table.read(file_path, format="ascii.ecsv")

# Check if all required columns are present
required_columns = [
"datetime",
"RA_deg",
"DEC_deg",
"RA_rate_arcsec_per_h",
"DEC_rate_arcsec_per_h",
"AZ_deg",
"EL_deg",
"r_au",
"delta_au",
"V_mag",
"alpha_deg",
"RSS_3sigma_arcsec",
]
missing_columns = [col for col in required_columns if col not in table.colnames]
if missing_columns:
raise ValueError(f"Missing columns in ECSV file: {', '.join(missing_columns)}")

# Create and populate the EphemerisData object
ephemeris_data = EphemerisData(
datetime=Time(table["datetime"], scale="utc", format="jd"),
RA_deg=np.array(table["RA_deg"]),
DEC_deg=np.array(table["DEC_deg"]),
RA_rate_arcsec_per_h=np.array(table["RA_rate_arcsec_per_h"]),
DEC_rate_arcsec_per_h=np.array(table["DEC_rate_arcsec_per_h"]),
AZ_deg=np.array(table["AZ_deg"]),
EL_deg=np.array(table["EL_deg"]),
r_au=np.array(table["r_au"]),
delta_au=np.array(table["delta_au"]),
V_mag=np.array(table["V_mag"]),
alpha_deg=np.array(table["alpha_deg"]),
RSS_3sigma_arcsec=np.array(table["RSS_3sigma_arcsec"]),
)

DataLoader.logger.info(
f"Loaded ephemeris data with {len(ephemeris_data.datetime)} points from {file_path}."
)

return ephemeris_data

except FileNotFoundError:
DataLoader.logger.error(f"The file {file_path} was not found.")
raise
except ValueError as ve:
DataLoader.logger.error(f"Value error in file {file_path}: {str(ve)}")
raise
except Exception as e:
DataLoader.logger.error(f"Unexpected error loading ECSV file {file_path}: {str(e)}")
raise ValueError(f"Error loading ECSV file: {str(e)}") from e

@staticmethod
def load_multiple_ephemeris_files(file_paths: list[str]) -> list[EphemerisData]:
"""
Load multiple ephemeris files and return a list of EphemerisData objects.
Parameters:
-----------
file_paths : List[str]
A list of paths to ECSV files containing ephemeris data.
Returns:
--------
List[EphemerisData]
A list of EphemerisData objects, each populated with data from one ECSV file.
"""
ephemeris_list = []
for file_path in file_paths:
try:
ephemeris_data = DataLoader.load_ephemeris_from_ecsv(file_path)
ephemeris_list.append(ephemeris_data)
except (FileNotFoundError, ValueError) as e:
DataLoader.logger.error(f"Error loading file {file_path}: {str(e)}")
raise # Re-raise the exception to be caught by the calling function

return ephemeris_list


if __name__ == "__main__":
# Example usage
# file_path = "./Ceres_2024-01-01_00-00-00.000_2025-12-31_23-59-00.000.ecsv"
# try:
# ephemeris_data = DataLoader.load_ephemeris_from_ecsv(file_path)
# except Exception as e:
# print(f"Error: {str(e)}")

# Example of loading multiple files
file_paths = [
"./Ceres_2024-01-01_00-00-00.000_2025-12-31_23-59-00.000.ecsv",
"./Encke_2024-01-01_00-00-00.000_2024-06-30_23-59-00.000.ecsv",
]
try:
ephemeris_list = DataLoader.load_multiple_ephemeris_files(file_paths)
print(f"Loaded {len(ephemeris_list)} ephemeris files.")
except Exception as e:
print(f"Error: {str(e)}")
96 changes: 96 additions & 0 deletions src/forcedphot/ephemeris/data_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from dataclasses import dataclass, field

import numpy as np
from astropy.time import Time


@dataclass
class QueryInput:
"""
A data class representing the input parameters for an ephemeris query.
Attributes:
target (str): The name or identifier of the celestial target.
target_type (str): The type of celestial target.
start (Time): The start time of the query period.
end (Time): The end time of the query period.
step (str): The time step interval for the query results.
"""

target: str
target_type: str
start: Time
end: Time
step: str


@dataclass
class QueryInputMiriade:
"""
A data class representing the input parameters for the Miriade ephemeris query.
Attributes:
target (str): The name or identifier of the celestial target.
objtype (str): The type of celestial target.
start (Time): The start time of the query period.
step (str): The time step interval for the query results.
nsteps (int): The number of steps in the query.
"""

target: str
objtype: str
start: Time
step: str
nsteps: int


@dataclass
class EphemerisData:
"""
A data class representing the ephemeris data for a celestial object.
Attributes:
datetime (Time): Time for the ephemeris data points.
RA_deg (np.ndarray): Right Ascension in degrees.
DEC_deg (np.ndarray): Declination in degrees.
RA_rate_arcsec_per_h (np.ndarray): Rate of change of Right Ascension in arcseconds per hour.
DEC_rate_arcsec_per_h (np.ndarray): Rate of change of Declination in arcseconds per hour.
AZ_deg (np.ndarray): Azimuth in degrees.
EL_deg (np.ndarray): Elevation in degrees.
r_au (np.ndarray): Heliocentric distance in astronomical units.
delta_au (np.ndarray): Geocentric distance in astronomical units.
V_mag (np.ndarray): Visual magnitude.
alpha_deg (np.ndarray): Phase angle in degrees.
RSS_3sigma_arcsec (np.ndarray): Root Sum Square 3-sigma positional uncertainty in arcseconds.
"""

datetime: Time = field(default_factory=lambda: Time([], scale="utc", format="jd"))
RA_deg: np.ndarray = field(default_factory=lambda: np.array([]))
DEC_deg: np.ndarray = field(default_factory=lambda: np.array([]))
RA_rate_arcsec_per_h: np.ndarray = field(default_factory=lambda: np.array([]))
DEC_rate_arcsec_per_h: np.ndarray = field(default_factory=lambda: np.array([]))
AZ_deg: np.ndarray = field(default_factory=lambda: np.array([]))
EL_deg: np.ndarray = field(default_factory=lambda: np.array([]))
r_au: np.ndarray = field(default_factory=lambda: np.array([]))
delta_au: np.ndarray = field(default_factory=lambda: np.array([]))
V_mag: np.ndarray = field(default_factory=lambda: np.array([]))
alpha_deg: np.ndarray = field(default_factory=lambda: np.array([]))
RSS_3sigma_arcsec: np.ndarray = field(default_factory=lambda: np.array([]))


@dataclass
class QueryResult:
"""
A data class representing the result of an ephemeris query.
Attributes:
target (str): The name or identifier of the celestial target.
start (Time): The start time of the query period.
end (Time): The end time of the query period.
ephemeris (EphemerisData): The ephemeris data for the celestial object.
"""

target: str
start: Time
end: Time
ephemeris: EphemerisData
Loading

0 comments on commit b19efc8

Please sign in to comment.