Repository navigation
Conversation
These changes are needed to account for the non-linear distortions due to the LAT's off-axis mirrors; otherwise only a single value of the "roll" can be treated in the existing framework. Introduces the idea of "focal_plane_template", a TOD member like focal_plane that will be used as the source to compute the (model-corrected) focal_plane. This changes the apply_pointing_model interface a bit but probably not in a way that users will notice.
mapcat's uuid7/datetime/path-rename migrations left this module behind: - ctime/start_time/stop_time were passed as raw unix-time floats into columns mapcat's "Swapping to datetime" migration made datetime, causing session.commit() to raise "SQLite DateTime type only accepts Python datetime and date objects" on the very first commit_depth1_tods call (outside the mapmaking try/except, so it hard-crashes the pipeline before a map is made). - commit_depth1_map passed time_path, a field DepthOneMapTable no longer has (split into start_time_path/mean_time_path/end_time_path). SQLModel silently drops unknown kwargs, so no time-map path was ever recorded. - commit_depth1_tods passed map_name into TODDepthOneTable, which has no such column anymore (association is via a many-to-many relationship through TODToMapTable). The kwarg was silently ignored, so TODs were never actually linked to their map. Verified against a real mapcat db migrated to head: commit succeeds, ctime/start_time/stop_time land as datetimes, mean_time_path is populated, and the TOD<->map relationship is linked in both directions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rh1TMcQhLGM8TyQZ947Rvt
…rument_centered_coordinates
…_centered_coordinates
…elobe cuts
This change was implemented with Claude Code (Opus 5.5).
It requires the companion so3g change that adds the det_left mode to
the projection engine (Assembly/ProjEng "det_left").
Summary
-------
Adds a way to build a pointing matrix in instrument-centered
coordinates of a source (Moon, Sun, or any name accepted by
planets.get_source_pos), and uses it to flag samples from per-wafer
sidelobe masks delivered as observation metadata.
Instrument-centered coordinates give the position of the source
relative to each detector, on axes fixed to the telescope structure
(the focal plane xi/eta axes at zero roll). Unlike the previous
object-centered frame (detector pointing relative to the source), a
sidelobe that is fixed to the instrument stays fixed in this frame.
Quaternion chain
----------------
For detector d and sample t:
q[d,t] = A[d] * X[t]
X[t] = Rz(roll[t]) * q_bore[t]^-1 * q_src[t] (sight.Q)
A[d] = Ry(pi/2) * Rz(roll0) * rotation_xieta(xi, eta, 0)^-1
* Rz(-roll0) (fp.quats)
- q_bore is the standard CelestialSightLine.az_el boresight
(qpoint * Rz(pi + roll)); q_src = rotation_lonlat(ra, dec) of the
source. Only q_bore^-1 * q_src enters, so the horizon->celestial
rotation cancels and the full-precision celestial pointing is used.
- Rz(roll[t]) removes the focal-plane roll so the axes are fixed to the
telescope structure; conjugating q_det by Rz(roll0) (circular mean
roll) expresses the detector offsets in the same axes. Exact for
constant roll; a warning is logged if roll varies by more than 0.1
deg from its mean.
- Detector quaternions are rebuilt with gamma = 0, since with q_det^-1
on the left gamma would rotate each detector's map about the
detector.
- Ry(pi/2) moves the detector from the pole to (lon, lat) = (0, 0).
Map conventions: each detector is at (0, 0); for small offsets
lat ~ eta_src - eta_det and lon ~ -(xi_src - xi_det), so in a standard
CAR geometry right = +az and up = +el (commanded). For LAT observations
past the zenith (el > 90) the axes follow the structure and are
therefore flipped relative to the sky.
Changes
-------
- coords/pmat.py:
- P has a det_left attribute, passed to Assembly.attach in _get_asm.
- P.for_tod(..., instrument_centered=<source name>) builds the
pointing above. Requires comps='T', geom given, rot=None and the
boresight in az/el/roll (boresight_equ is not supported); raises
ValueError otherwise.
- New get_instrument_centered(sight, fp, roll, source, timestamps,
site) returning the (sight, fp) pair for det_left projection.
- coords/planets.py: new get_source_quat(source, timestamps, site,
dt=600): source RA/Dec from get_source_pos on a grid of at most
dt seconds, RA unwrapped and linearly interpolated to each sample,
returned as rotation_lonlat quaternions.
- coords/sidelobes.py: rewritten. Masks are no longer fixed files per
object; they come from the observation metadata as aman.sidelobes
(Enmap metadata loader): .map is an enmap [nfields, ny, nx] of 0/1
in the instrument-centered coordinates of each object, labelled by
the "fields" LabelAxis (e.g. ['moon', 'sun']).
- get_cuts(aman, sidelobes="sidelobes", object_list=None,
rise_tol=1 deg) returns one RangesMatrix [ndet, nsamp] per object.
Clear errors if the metadata is missing or the map has no WCS.
- make_cuts: skips objects below rise_tol at the first, middle and
last sample; projects the mask with
P.for_tod(instrument_centered=...); uses unit responsivities so the
mask value is independent of the detector T response (the old
astype(int) could round 1*T < 1 down to 0); cuts samples where the
projected mask is non-zero.
- site_pipeline/make_ml_map.py: call sidelobes.get_cuts(obs) with the
new API. The --sun-mask/--moon-mask options are no longer used.
- tests/test_pmat.py: test_pmat_instrument_centered checks gamma
independence, that the angular distance from the origin equals the
detector-source distance on the sky, the orientation for the
boresight detector against the Moon's az/el, and that
from_map/to_map run; comps='TQU' is rejected.
Validation on a LAT observation (el = 120 deg, roll = 60 deg): the
spread across detectors at fixed time is ~0.6 x 0.5 deg (one wafer),
and the boresight orientation check agrees with skyfield's Moon az/el
to ~1e-4 at all tested samples.
WARNING
-------
This implementation can ONLY be used to go from a mask to flagged
samples, "T only". It CANNOT be used to make maps in
instrument-centered coordinates: polarization is not supported
(gamma is set to 0 and only comps='T' is allowed), and mapmaking in
these coordinates has not been designed or validated. That will need a
future implementation.
Known open point: the direction in which detector offsets are rotated
by the roll (+roll vs -roll) has not been verified independently; for a
~1 deg LAT wafer the effect is at most ~1 deg.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
this will fail tests without the so3g PR, so we need to merge that first |
This change was implemented with Claude Code (Opus 5.5).
It requires the companion so3g change that makes the det-left
multiplication order a template parameter ("_DetLeft" engines and the
Projectionist multiply_det_left constructor argument).
- coords/pmat.py:
- P's det_left attribute/argument is renamed multiply_det_left.
P.for_tod(instrument_centered=...) sets it.
- _get_proj passes multiply_det_left=True to
Projectionist.for_geom / for_tiled and
ProjectionistHealpix.for_healpix (only when set, so P keeps working
with older so3g in the default mode), instead of passing det_left
to Assembly.attach. _get_asm is back to Assembly.attach(sight, fp).
- Docstrings updated: the mode uses the so3g "_DetLeft" engines,
available for CAR and HEALPix with comps='T' only.
- tests/test_pmat.py: assert pmat.multiply_det_left.
No change to the instrument-centered quaternion chain,
get_instrument_centered, or sidelobes.get_cuts.
WARNING
-------
This implementation can ONLY be used to go from a mask to flagged
samples, "T only". It CANNOT be used to make maps in
instrument-centered coordinates; that will need a future
implementation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_centered_coordinates
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This change was implemented with Claude Code (Opus 5.5). Edited since the approach changed a little bit.
It requires the companion so3g PR, which adds projection engines that multiply the detector quaternion on the left ("_DetLeft" engines, selected with the Projectionist constructor argument
multiply_det_left).Summary
Adds a way to build a pointing matrix in instrument-centered coordinates of a source (Moon, Sun, or any name accepted by planets.get_source_pos), and uses it to flag samples from per-wafer sidelobe masks delivered as observation metadata.
Instrument-centered coordinates give the position of the source relative to each detector, on axes fixed to the telescope structure (the focal plane xi/eta axes at zero roll). Unlike the previous object-centered frame (detector pointing relative to the source), a sidelobe that is fixed to the instrument stays fixed in this frame.
Quaternion chain
For detector d and sample t:
The per-detector factor is on the left, which is why the so3g "_DetLeft" engines are needed.
Map conventions: each detector is at (0, 0); for small offsets lat ~ eta_src - eta_det and lon ~ -(xi_src - xi_det), so in a standard CAR geometry right = +az and up = +el (commanded). For LAT observations past the zenith (el > 90) the axes follow the structure and are therefore flipped relative to the sky.
Changes
multiply_det_leftattribute/constructor argument. When set,_get_projpassesmultiply_det_left=TruetoProjectionist.for_geom/for_tiled/ProjectionistHealpix.for_healpix, so the so3g "_DetLeft" engines are used (CAR and HEALPix, comps='T' only). It is only passed when set, so P keeps working with older so3g in the default mode.P.for_tod(..., instrument_centered=<source name>)builds the pointing above and sets multiply_det_left. Requires comps='T', geom given, rot=None and the boresight in az/el/roll (boresight_equ is not supported); raises ValueError otherwise.get_instrument_centered(sight, fp, roll, source, timestamps, site)returning the (sight, fp) pair for multiply_det_left projection.get_source_quat(source, timestamps, site, dt=600): source RA/Dec from get_source_pos on a grid of at most dt seconds, RA unwrapped and linearly interpolated to each sample, returned as rotation_lonlat quaternions.get_cuts(aman, sidelobes="sidelobes", object_list=None, rise_tol=1 deg)returns one RangesMatrix [ndet, nsamp] per object. Clear errors if the metadata is missing or the map has no WCS.make_cuts: skips objects below rise_tol at the first, middle and last sample; projects the mask with P.for_tod(instrument_centered=...); uses unit responsivities so the mask value is independent of the detector T response (the old astype(int) could round 1*T < 1 down to 0); cuts samples where the projected mask is non-zero.Validation on real data
WARNING
This implementation can ONLY be used to go from a mask to flagged samples, "T only". It CANNOT be used to make maps in instrument-centered coordinates: polarization is not supported (gamma is set to 0, and so3g only provides T engines for this mode), and mapmaking in these coordinates has not been designed or validated. That will need a future implementation.
Known open point: the direction in which detector offsets are rotated by the roll (+roll vs -roll) has not been verified independently; the roll = 0 observation above cannot distinguish the two. The difference between the two hypotheses is ~2 r |sin(roll)|, where r is the detector's distance from the boresight (~2 deg for the wafer tested), so a Moon crossing with a few degrees of roll is enough to settle it.
🤖 Generated with Claude Code