Skip to content
93 changes: 66 additions & 27 deletions sotodlib/coords/det_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,26 @@ class PointingConfig:
-----
fp_file: str
Path to focal-plane file that is used by the optics module.
rx_file: str
Path to optics tube YAML file that is used by the optics module.
wafer_slot: str
Wafer slot of the UFM. For example: "ws0"
tel_type: str
Tel type for the optics model. Either "SAT" or "LAT"
platform: str
Which platform to use for the optics model.
zemax_path: str
If running for a "LAT" tel_type, the path to the zemax file must be specified.
If running for the lat platform, the path to the zemax file must be specified.
roll: float
Rotation about the line of sight.
For the LAT this is elev - 60 - corotator.
For the SAT this is -1*boresight.
tube_slot: str/int
If running for a "LAT" tel_type, the tube slot must be specified.
If running for the lat platform, the tube slot must be specified.
Either the tube name as a string or the tube number as an int.
"""
fp_file: str
rx_file: str
wafer_slot: str
tel_type: str
platform: str
zemax_path: Optional[str] = None
roll: Optional[float] = 0
tube_slot: Optional[Union[str, int]] = None
Expand All @@ -92,6 +95,10 @@ class PointingConfig:
fp_pars: dict = field(init=False)

def __post_init__(self):
if self.platform.lower() in ["satp1", "satp2", "satp3"]:
self.tel_type = "SAT"
elif self.platform.lower() == "lat":
self.tel_type = "LAT"
if self.tel_type == 'LAT':
if self.zemax_path is None:
raise ValueError("zemax path must be set for 'LAT' tel_type")
Expand All @@ -104,22 +111,37 @@ def __post_init__(self):
self.fp_pars = optics.get_ufm_to_fp_pars(
self.tel_type, self.wafer_slot, self.fp_file
)
self.dx = self.fp_pars['dx']
self.dy = self.fp_pars['dy']
self.theta = np.deg2rad(self.fp_pars['theta'])

def get_pointing(self, x, y, pol=0):
xp = x * np.cos(self.theta) - y * np.sin(self.theta) + self.dx
yp = x * np.sin(self.theta) + y * np.cos(self.theta) + self.dy
self.rx_pars = optics.get_fp_to_rx_pars(
self.tube_slot, self.rx_file)

def get_pointing(self, x, y, pol=0):
if self.tel_type.upper() == 'SAT':
xi, eta, gamma = optics.SAT_focal_plane(
None, x=xp, y=yp, pol=pol, roll=self.roll
xi, eta, gamma = optics.get_focal_plane(
None,
x=x,
y=y,
pol=pol,
roll=self.roll,
telescope_flavor="SAT",
tube_slot=self.tube_slot,
wafer_slot=self.wafer_slot,
ufm_to_fp_pars=self.fp_pars,
fp_to_rx_pars=self.rx_pars,
)
elif self.tel_type.upper() == 'LAT':
xi, eta, gamma = optics.LAT_focal_plane(
None, self.zemax_path, x=xp, y=yp, pol=pol,
roll=self.roll, tube_slot=self.tube_slot
xi, eta, gamma = optics.get_focal_plane(
None,
x=x,
y=y,
pol=pol,
roll=self.roll,
telescope_flavor="LAT",
tube_slot=self.tube_slot,
wafer_slot=self.wafer_slot,
ufm_to_fp_pars=self.fp_pars,
fp_to_rx_pars=self.rx_pars,
zemax_path=self.zemax_path,
)
return xi, eta, gamma

Expand All @@ -140,6 +162,8 @@ class Resonator:
smurf_subband: int = -1
readout_id: str = ''

wafer_slot: str = 'ws.'

xi: float = np.nan
eta: float = np.nan
gamma: float = np.nan
Expand Down Expand Up @@ -194,7 +218,7 @@ def apply_design_properties(smurf_res, design_res, in_place=False, apply_pointin
'det_pol', 'det_freq', 'det_bandpass', 'det_angle_raw_deg',
'det_angle_actual_deg', 'det_type', 'det_id', 'is_optical',
'mux_bondpad', 'mux_subband', 'mux_band', 'mux_channel',
'mux_layout_pos'
'mux_layout_pos', 'wafer_slot'
]

# for LF
Expand Down Expand Up @@ -285,7 +309,8 @@ def as_array(self):
return np.array(data, dtype=dtype)

@classmethod
def from_aman(cls, aman, stream_id, det_cal=None, name=None, pointing: Optional[AxisManager]=None):
def from_aman(cls, aman, stream_id, det_cal=None, name=None, pointing: Optional[AxisManager]=None,
ignore_north_south=False):
"""
Load a resonator set from a Context object based on an obs_id

Expand Down Expand Up @@ -315,7 +340,10 @@ def from_aman(cls, aman, stream_id, det_cal=None, name=None, pointing: Optional[
resonators = []
for i, ri in enumerate(np.where(m)[0]):
band, channel = aman.det_info.smurf.band[ri], aman.det_info.smurf.channel[ri]
is_north = north_is_highband ^ (band < 4)
if not ignore_north_south:
is_north = north_is_highband ^ (band < 4)
else:
is_north = 1
readout_id = aman.det_info.readout_id[ri]
bg = det_cal.bg[ri]
res_freq=aman.det_info.smurf.frequency[ri]
Expand All @@ -336,7 +364,7 @@ def from_aman(cls, aman, stream_id, det_cal=None, name=None, pointing: Optional[

@classmethod
def from_tunefile(cls, tunefile, name=None, north_is_highband=True,
resfit_file=None, bgmap_file=None):
resfit_file=None, bgmap_file=None, ignore_north_south=False):
"""
Creates an instance based on a smurf-tune file. If a resfit or bgmap
file is included, that data will be added to the Resonance objects as
Expand All @@ -363,7 +391,10 @@ def from_tunefile(cls, tunefile, name=None, north_is_highband=True,
continue

for res_idx, d in _v['resonances'].items():
is_north = north_is_highband ^ (band < 4)
if not ignore_north_south:
is_north = north_is_highband ^ (band < 4)
else:
is_north = 1
res = Resonator(
idx=idx, smurf_res_idx=res_idx, res_freq=d['freq'],
smurf_band=band, is_north=is_north
Expand All @@ -385,7 +416,8 @@ def from_tunefile(cls, tunefile, name=None, north_is_highband=True,

@classmethod
def from_wafer_info_file(cls, wafer_info_file, array_name, name=None,
pt_cfg: Optional[PointingConfig]=None):
pt_cfg: Optional[PointingConfig]=None,
ignore_north_south=False):
"""
Initialize a ResSet from a wafer info file. This is a file that contains
detector design information.
Expand Down Expand Up @@ -415,7 +447,10 @@ def from_wafer_info_file(cls, wafer_info_file, array_name, name=None,
resonators = []
idx = 0
for r in wafer_array:
is_north = r['dets:wafer.coax'] == b'N'
if not ignore_north_south:
is_north = r['dets:wafer.coax'] == b'N'
else:
is_north = 1
kwargs = dict(
idx=idx,
det_id=r['dets:det_id'].decode(),
Expand Down Expand Up @@ -457,7 +492,8 @@ def from_wafer_info_file(cls, wafer_info_file, array_name, name=None,

@classmethod
def from_solutions(cls, sol_file, north_is_highband=True, name=None,
fp_pars=None, platform='SAT', zemax_path=None):
fp_pars=None, platform='LAT', zemax_path=None,
ignore_north_south=False):
"""
Creates an instance from an input-solution file. This will include both design data, along with smurf-band
and smurf-channel info. Resonance frequencies used here are the VNA
Expand All @@ -475,7 +511,7 @@ def from_solutions(cls, sol_file, north_is_highband=True, name=None,
Result of the function ``sotododlib.coords.optics.get_ufm_to_fp_pars``. If this is None, detector positions will
not be mapped to pointing angles.
platform (str):
'SAT' or 'LAT'. Used to determine which focal plane function to
Which platform. Used to determine which focal plane function to
use for pointing
zemax_path (str):
zemax path, required to get pointing for LAT optics
Expand Down Expand Up @@ -505,7 +541,10 @@ def _int(val, null_val=None):
continue
# is_north = north_is_highband ^ (_int(d['smurf_band']) < 4)
# is_north = d['is_north'].lower().strip() == 'true'
is_north = _int(d['bias_line']) < 6
if not ignore_north_south:
is_north = _int(d['bias_line']) < 6
else:
is_north = 1
is_optical = (d['is_optical'].lower() == 'true')

kwargs = dict(
Expand Down Expand Up @@ -745,7 +784,7 @@ def _get_biadjacency_matrix(self) -> np.ndarray:

mat = np.zeros((len(self.src), len(self.dst)), dtype=float)

# N/S mismatch
# N/S mismatch (all N for LF)
m = src_arr['is_north'][:, None] != dst_arr['is_north'][None, :]
mat[m] = np.inf

Expand Down
Loading
Loading