11from __future__ import annotations
22
3- from typing import Sequence
3+ from dataclasses import dataclass , field
4+ from typing import TYPE_CHECKING , Sequence
45
56import numpy as np
67from numpy .typing import ArrayLike
78
8- from .model import AstromerInputs , SingleBandModel
9- from .reduction import Reduction
9+ from light_curve .embed .input_tensors import InputTensors
10+ from light_curve .embed .model import SingleBandModel
11+ from light_curve .embed .reduction import Reduction
1012
11- _ONNX_INSTALL_HINT = (
12- "An ONNX runtime is required to run embedding models. "
13- "Install the variant that matches your hardware:\n "
14- " CPU / Apple Silicon: pip install onnxruntime\n "
15- " NVIDIA GPU (CUDA): pip install onnxruntime-gpu\n "
16- " Windows DirectML: pip install onnxruntime-directml\n "
17- "See https://onnxruntime.ai for the full list of packages."
18- )
13+ if TYPE_CHECKING :
14+ from typing import Self
1915
2016
21- def create_onnx_session (model_path : str , ** kwargs ):
22- """Create an ``onnxruntime.InferenceSession``, with a helpful error if the package is missing.
17+ @dataclass
18+ class AstromerInputs (InputTensors ):
19+ """Input tensors for Astromer-family models.
2320
24- Parameters
25- ----------
26- model_path : str
27- Path to the ONNX model file.
28- **kwargs
29- Forwarded verbatim to ``onnxruntime.InferenceSession``.
30-
31- Returns
32- -------
33- onnxruntime.InferenceSession
34- A ready-to-use inference session.
35-
36- Raises
37- ------
38- ImportError
39- If no onnxruntime variant is installed, with installation instructions.
21+ ``times`` and ``input`` are float32 arrays of shape
22+ ``(n_windows, seq_size, 1)`` ready for ONNX inference. ``mask`` is the
23+ same validity information cast to float32 for the model (1 = valid,
24+ 0 = padded), shape ``(n_windows, seq_size, 1)``. ``bool_mask`` (inherited)
25+ is the boolean equivalent, shape ``(n_windows, seq_size)``.
4026 """
41- try :
42- import onnxruntime as ort
43- except ImportError as exc :
44- raise ImportError (_ONNX_INSTALL_HINT ) from exc
45- return ort .InferenceSession (model_path , ** kwargs )
27+
28+ times : np .ndarray = field (kw_only = True )
29+ input : np .ndarray = field (kw_only = True )
30+ mask_in : np .ndarray = field (kw_only = True )
4631
4732
4833class _AstromerModel (SingleBandModel ):
4934 """Internal base class for Astromer-family embedding models.
5035
5136 Provides shared preprocessing (per-window zero-mean normalisation) and
5237 ONNX inference logic for all Astromer variants. Concrete subclasses set
53- :attr:`_HF_REPO ` and, if needed, override the default ``reduction``.
38+ :attr:`hf_repo ` and, if needed, override the default ``reduction``.
5439
5540 Output shape
5641 ------------
@@ -71,8 +56,8 @@ class _AstromerModel(SingleBandModel):
7156
7257 seq_size : int = 200
7358 dtype : type = np .float32
74- _HF_REPO : str
75- _OUTPUTS : frozenset [str ] = frozenset ({"mean" , "max" , "sequence" })
59+ hf_filename : str
60+ model_outputs : frozenset [str ] = frozenset ({"mean" , "max" , "sequence" })
7661
7762 def __init__ (
7863 self ,
@@ -81,29 +66,54 @@ def __init__(
8166 output : str = "mean" ,
8267 bands : Sequence [str | int ] | None = None ,
8368 reduction : str | list [str ] | Reduction = "non-overlapping-windows" ,
84- time_red_kwargs : dict [str , object ] | None = None ,
69+ reduction_kwargs : dict [str , object ] | None = None ,
8570 ) -> None :
8671 super ().__init__ (
8772 session ,
8873 bands = bands ,
8974 reduction = reduction ,
90- time_red_kwargs = time_red_kwargs ,
75+ reduction_kwargs = reduction_kwargs ,
9176 )
92- if output not in self ._OUTPUTS :
93- raise ValueError (f"Unknown output '{ output } '. Must be one of: { ', ' .join (sorted (self ._OUTPUTS ))} " )
77+ if output not in self .model_outputs :
78+ raise ValueError (f"Unknown output '{ output } '. Must be one of: { ', ' .join (sorted (self .model_outputs ))} " )
9479 self .output = output
9580
81+ def __call__ (self , time : ArrayLike , mag : ArrayLike , band : ArrayLike | None = None ) -> np .ndarray :
82+ """Embed a light curve.
83+
84+ Parameters
85+ ----------
86+ Parameters
87+ ----------
88+ time : array-like, shape ``(n,)``
89+ Observation times in days (e.g. MJD).
90+ mag : array-like, shape ``(n,)``
91+ Magnitudes.
92+ band : array-like, shape ``(n,)``, optional
93+ Band labels, required when ``self.bands`` is not ``None``.
94+
95+ Returns
96+ -------
97+ np.ndarray, shape ``(n_bands, n_subsamples, seq_size, embed_dim)``
98+ Embedding tensor. ``n_bands`` is 1 when ``self.bands`` is ``None``.
99+
100+ Raises
101+ ------
102+ ValueError
103+ If ``band`` is provided but ``self.bands`` is ``None``, or vice versa.
104+ """
105+ return super ().__call__ (time , mag , band = band )
106+
96107 @classmethod
97108 def from_hf (
98109 cls ,
99110 output : str = "mean" ,
100111 * ,
101112 bands : Sequence [str | int ] | None = None ,
102113 reduction : str | list [str ] | Reduction = "non-overlapping-windows" ,
103- time_red_kwargs : dict [str , object ] | None = None ,
104- providers = None ,
105- sess_options = None ,
106- ) -> "_AstromerModel" :
114+ reduction_kwargs : dict [str , object ] | None = None ,
115+ ort_session_kwargs : dict [str , object ] | None = None ,
116+ ) -> Self :
107117 """Load a model from the HuggingFace Hub.
108118
109119 Downloads (and caches) the ONNX model file, creates an
@@ -117,27 +127,24 @@ def from_hf(
117127 Named ONNX output to return. One of:
118128
119129 * ``"mean"`` (default) — masked mean pooling over valid timesteps,
120- output shape ``(batch , 256)``
130+ output shape ``(bands, reductions, 1 , 256)``
121131 * ``"max"`` — masked max pooling over valid timesteps,
122- output shape ``(batch , 256)``
132+ output shape ``(bands, reductions, 1 , 256)``
123133 * ``"sequence"`` — per-timestep embeddings (no aggregation),
124- output shape ``(batch, seq_size , 256)``
134+ output shape ``(bands, reductions, 200 , 256)``
125135
126- bands : sequence of str or int, optional
136+ bands : sequence of str or int or None , optional
127137 Ordered band labels to embed. ``None`` (default) treats the whole
128138 light curve as one band.
129139 reduction : str, list of str, or Reduction, optional
130140 Windowing / subsampling strategy. Defaults to
131141 ``"non-overlapping-windows"``.
132- time_red_kwargs : dict, optional
142+ reduction_kwargs : dict or None , optional
133143 Extra keyword arguments forwarded to :func:`reduction_from_str`
134144 when ``reduction`` is given as a string.
135- providers : list of str, optional
136- ONNX Runtime execution providers, e.g.
137- ``["CUDAExecutionProvider", "CPUExecutionProvider"]``.
138- sess_options : onnxruntime.SessionOptions, optional
139- Advanced session configuration passed directly to
140- ``onnxruntime.InferenceSession``.
145+ ort_session_kwargs : dict or None, optional
146+ Additional keyword arguments forwarded to ``onnxruntime.InferenceSession``:
147+ "sess_options", "providers", "provider_options".
141148
142149 Returns
143150 -------
@@ -149,48 +156,17 @@ def from_hf(
149156 ValueError
150157 If ``output`` is not one of the recognised output names.
151158 ImportError
152- If ``huggingface_hub`` is not installed, with instructions to
153- install it or to download the model file manually.
159+ If ``huggingface_hub`` is not installed.
154160 ImportError
155- If no ``onnxruntime`` variant is installed, with hardware-specific
156- installation instructions.
161+ If no ``onnxruntime`` variant is installed.
157162 """
158- if output not in cls ._OUTPUTS :
159- raise ValueError (f"Unknown output '{ output } '. Must be one of: { ', ' .join (sorted (cls ._OUTPUTS ))} " )
160-
161- model_prefix = cls ._HF_REPO .split ("/" )[- 1 ]
162- filename = f"{ model_prefix } .onnx"
163-
164- try :
165- from huggingface_hub import hf_hub_download
166- except ImportError as exc :
167- hf_url = f"https://huggingface.co/{ cls ._HF_REPO } /resolve/main/{ filename } "
168- raise ImportError (
169- "huggingface_hub is required to download models from HuggingFace.\n "
170- "Install it with:\n "
171- " pip install huggingface-hub\n "
172- "Or download the model file directly:\n "
173- f" { hf_url } \n "
174- "then load it with:\n "
175- " import onnxruntime as ort\n "
176- f' { cls .__name__ } (session=ort.InferenceSession("/path/to/{ filename } "), output="{ output } ")'
177- ) from exc
178-
179- model_path = hf_hub_download (repo_id = cls ._HF_REPO , filename = filename )
180-
181- session_kwargs = {}
182- if providers is not None :
183- session_kwargs ["providers" ] = providers
184- if sess_options is not None :
185- session_kwargs ["sess_options" ] = sess_options
186-
187- session = create_onnx_session (model_path , ** session_kwargs )
188- return cls (
189- session = session ,
163+ return super ().from_hf (
164+ cls .hf_filename ,
190165 output = output ,
191166 bands = bands ,
192167 reduction = reduction ,
193- time_red_kwargs = time_red_kwargs ,
168+ reduction_kwargs = reduction_kwargs ,
169+ ort_session_kwargs = ort_session_kwargs ,
194170 )
195171
196172 def preprocess_lc (
@@ -230,7 +206,7 @@ def preprocess_lc(
230206 mask = mask .astype (self .dtype )
231207
232208 idx = (..., np .newaxis )
233- return AstromerInputs (times = time [idx ], input = mag [idx ], mask = mask [idx ], bool_mask = bool_mask )
209+ return AstromerInputs (times = time [idx ], input = mag [idx ], mask_in = mask [idx ], bool_mask = bool_mask )
234210
235211 def predict_tensors (self , tensors : AstromerInputs ) -> np .ndarray :
236212 """Run the ONNX model on pre-processed tensors and return reduced embeddings.
@@ -248,7 +224,8 @@ def predict_tensors(self, tensors: AstromerInputs) -> np.ndarray:
248224 """
249225 (raw_embedding ,) = self .session .run (
250226 [self .output ],
251- {"input" : tensors .input , "times" : tensors .times , "mask_in" : tensors .mask },
227+ # {"input": tensors.input, "times": tensors.times, "mask_in": tensors.mask},
228+ tensors .asdict (),
252229 )
253230
254231 # Aggregated outputs (mean / max) have shape (n_windows, embed_dim); add SEQUENCE=1 axis
@@ -289,7 +266,8 @@ class Astromer1(_AstromerModel):
289266 Windowing strategy. Defaults to :class:`NonOverlappingWindows`.
290267 """
291268
292- _HF_REPO = "light-curve/astromer1"
269+ hf_repo : str = "light-curve/astromer1"
270+ hf_filename : str = "astromer1.onnx"
293271
294272
295273class Astromer2 (_AstromerModel ):
@@ -324,4 +302,5 @@ class Astromer2(_AstromerModel):
324302 embeddings on HuggingFace.
325303 """
326304
327- _HF_REPO = "light-curve/astromer2"
305+ hf_repo : str = "light-curve/astromer2"
306+ hf_filename : str = "astromer2.onnx"
0 commit comments