Skip to content

Commit

Permalink
Merge pull request #82 from GalSim-developers/new_obseq
Browse files Browse the repository at this point in the history
Fixed obseq
  • Loading branch information
rmandelb authored Dec 11, 2024
2 parents 9904ad1 + 393936e commit d521969
Show file tree
Hide file tree
Showing 9 changed files with 622 additions and 16 deletions.
2 changes: 1 addition & 1 deletion euclidlike/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .instrument_params import gain, pixel_scale, diameter, obscuration, collecting_area
from .instrument_params import focal_length, fratio
from .instrument_params import vis_bands, nisp_bands
from .instrument_params import long_exptime, short_exptime_vis, short_exptime_nisp, read_noise, n_dithers
from .instrument_params import long_exptime, short_exptime_vis, nisp_exptime_eff, read_noise, n_dithers
from .instrument_params import n_ccd, n_pix_row, n_pix_col, pixel_scale_mm, det2ccd
from .instrument_params import vis_red_limit, vis_blue_limit
from . import instrument_params
Expand Down
3 changes: 2 additions & 1 deletion euclidlike/instrument_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
fratio = focal_length / diameter # dimension-less, inferred from diameter and focal_length
collecting_area = 9926 # cm^2, from https://www.euclid-ec.org/science/overview/
long_exptime = 566 # s (for the longer exposures used for VIS images)
short_exptime_nisp = 112 # s (for the shorter NISP imaging exposures)
nisp_exptime_total = 112 # s (total time NISP imaging exposures)
nisp_exptime_eff = 87.2 # s (effective time NISP imaging exposures) (This is the one to use for flux computation)
short_exptime_vis = 95 # s (for the shorter exposures with VIS taken in parallel with NISP imaging)
read_noise = 4.4 # e-, https://www.euclid-ec.org/public/mission/vis/
saturation = 200000 # e-, from https://www.euclid-ec.org/public/mission/vis/
Expand Down
22 changes: 11 additions & 11 deletions euclidlike_imsim/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
* `euclid_obseq.pkl` contains the observation sequence for Euclid in pandas DataFrame format. It makes use of MultiIndex system to access the 5 kinds of observation for each pointing. See (Scaramella et al.)[https://arxiv.org/pdf/2108.01201] Fig. 8 for an illustration of the observation sequence (the timings used here are slightly different).
It looks like:
```python
date exptime ra dec pa saa filter date_euclid obs_id pointing_id patch_id dither_id line_obseq
date exptime ra dec pa lon lat pa_ecliptic saa filter date_euclid obs_id pointing_id patch_id dither_id
visit obs_kind
0 VIS_LONG 61085.020984 566.0 51.745 -56.853 -92.463 89.377 VIS 60354.020984 1 1 1 0 77
VIS_SHORT 61085.024549 95.0 51.745 -56.853 -92.463 89.377 VIS 60354.024549 1 1 1 0 77
NISP_J 61085.027998 87.2 51.745 -56.853 -92.463 89.377 NISP_J 60354.027998 1 1 1 0 77
NISP_H 61085.028539 87.2 51.745 -56.853 -92.463 89.377 NISP_H 60354.028539 1 1 1 0 77
NISP_Y 61085.029081 87.2 51.745 -56.853 -92.463 89.377 NISP_Y 60354.029081 1 1 1 0 77
1 VIS_LONG 61085.033322 566.0 51.774 -56.821 -92.487 89.384 VIS 60354.033322 1 2 1 1 79
VIS_SHORT 61085.036887 95.0 51.774 -56.821 -92.487 89.384 VIS 60354.036887 1 2 1 1 79
NISP_J 61085.040336 87.2 51.774 -56.821 -92.487 89.384 NISP_J 60354.040336 1 2 1 1 79
NISP_H 61085.040877 87.2 51.774 -56.821 -92.487 89.384 NISP_H 60354.040877 1 2 1 1 79
NISP_Y 61085.041419 87.2 51.774 -56.821 -92.487 89.384 NISP_Y 60354.041419 1 2 1 1 79
0 VIS_LONG 61346.858513 566.0 65.025888 -36.679767 249.647989 51.745415 -56.852863 267.537147 89.376641 VIS 59155.858513 1 1 0 0
VIS_SHORT 61346.862077 95.0 65.025888 -36.679767 249.647989 51.745415 -56.852863 267.537147 89.376641 VIS 59155.862077 1 1 0 0
NISP_J 61346.865526 87.2 65.025888 -36.679767 249.647989 51.745415 -56.852863 267.537147 89.376641 NISP_J 59155.865526 1 1 0 0
NISP_H 61346.866068 87.2 65.025888 -36.679767 249.647989 51.745415 -56.852863 267.537147 89.376641 NISP_H 59155.866068 1 1 0 0
NISP_Y 61346.866610 87.2 65.025888 -36.679767 249.647989 51.745415 -56.852863 267.537147 89.376641 NISP_Y 59155.866610 1 1 0 0
1 VIS_LONG 61346.869173 566.0 65.032330 -36.644968 249.644155 51.773928 -56.821327 267.513288 89.384369 VIS 59155.869173 1 2 0 1
VIS_SHORT 61346.872737 95.0 65.032330 -36.644968 249.644155 51.773928 -56.821327 267.513288 89.384369 VIS 59155.872737 1 2 0 1
NISP_J 61346.876186 87.2 65.032330 -36.644968 249.644155 51.773928 -56.821327 267.513288 89.384369 NISP_J 59155.876186 1 2 0 1
NISP_H 61346.876728 87.2 65.032330 -36.644968 249.644155 51.773928 -56.821327 267.513288 89.384369 NISP_H 59155.876728 1 2 0 1
NISP_Y 61346.877270 87.2 65.032330 -36.644968 249.644155 51.773928 -56.821327 267.513288 89.384369 NISP_Y 59155.877270 1 2 0 1
...
```
To access the information of the long VIS observation for the pointing 42 you will do:
Expand Down
Binary file modified euclidlike_imsim/data/euclid_obseq.pkl
Binary file not shown.
20 changes: 17 additions & 3 deletions examples/focal_plane_layout.ipynb

Large diffs are not rendered by default.

Binary file modified examples/focal_plane_layout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
258 changes: 258 additions & 0 deletions examples/obseq_example.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ include-package-data = true

[project.scripts]
euclidlike_download_psf = "scripts.download_psf:run_main"
euclidlike_make_obseq = "scripts.make_obseq:run_main"
332 changes: 332 additions & 0 deletions scripts/make_obseq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
"""
This script processes Euclid observation sequence XML file and convert it into
a pandas DataFrame.
It handle the conversion from ecliptic coordinates (from EC) to equatorial.
The dates of the observations are derived from Figure 4.4 of MOCDC_v4.2
(https://euclid.roe.ac.uk/dmsf/files/20821/view EC only).
NOTE: The true observation date is shifted by 6 years to match the current
Roman/Rubin simulations. That allows us to include transients.
NOTE 2: This observation sequence has been made from the `rsd2024a`
https://euclid.roe.ac.uk/dmsf/files/20839/view (EC only).
To run the script run the command:
python make_obseq.py <path_to_xml_file>
"""
import os
import sys
from tqdm import tqdm
import xml.etree.ElementTree as ET
from argparse import ArgumentParser
import importlib.util
from importlib.resources import files

import pandas as pd
import numpy as np

from euclidlike.instrument_params import (
long_exptime,
short_exptime_vis,
nisp_exptime_total,
nisp_exptime_eff,
)

from astropy.time import Time
from astropy.coordinates import SkyCoord, GeocentricTrueEcliptic
import astropy.units as u


def s2days(s):
return s / 86400.


def convert_ecliptic_to_equatorial(elon, elat, epa):
""" Transform coordinate frame.
Parameters
----------
elon : np.ndarray
longitude coordinate (deg)
elat : np.ndarray
latitude coordinate (deg)
epa : np.ndarray
position angle (deg)
Returns
-------
lon, lat, pa : np.ndarray
"""

skycoord = SkyCoord(
elon*u.deg,
elat*u.deg,
frame=GeocentricTrueEcliptic,
)

out = skycoord.transform_to("icrs")

lon = out.ra.deg
lat = out.dec.deg

pole_target = SkyCoord(
0*u.deg,
90*u.deg,
frame="icrs"
)

pole_0 = pole_target.transform_to(GeocentricTrueEcliptic)

pa = (epa - skycoord.position_angle(pole_0).deg) % 360

return lon, lat, pa


shift_2_years = 365 * 2 + 1
shift_6_years = 365 * 6 + 1

VIS_exp_long = long_exptime
VIS_exp_short = short_exptime_vis
NISP_exp_int = nisp_exptime_total
NISP_exp_eff = nisp_exptime_eff
NISP_exp_wait = NISP_exp_int - NISP_exp_eff

# Figure 4.4 of MOCDC_v4.2
# https://euclid.roe.ac.uk/dmsf/files/20821/view (EC only)
dither_seq = {
0: {
"VIS": [
{"wait": 8},
{"VIS_LONG": VIS_exp_long},
{"wait": 40 + NISP_exp_int + 22 + NISP_exp_int + 22},
{"VIS_SHORT": VIS_exp_short},
],
"NISP": [
{"wait": 574 + 40},
{"NISP_J": NISP_exp_eff},
{"wait": 22 + NISP_exp_wait},
{"NISP_H": NISP_exp_eff},
{"wait": 22 + NISP_exp_wait},
{"NISP_Y": NISP_exp_eff},
],
},
1: {
"VIS": [
{"wait": 8},
{"VIS_LONG": VIS_exp_long},
{"wait": 40 + NISP_exp_int + 22 + NISP_exp_int + 22},
{"VIS_SHORT": VIS_exp_short},
],
"NISP": [
{"wait": 574 + 40},
{"NISP_J": NISP_exp_eff},
{"wait": 22 + NISP_exp_wait},
{"NISP_H": NISP_exp_eff},
{"wait": 22 + NISP_exp_wait},
{"NISP_Y": NISP_exp_eff},
],
},
2: {
"VIS": [
{"wait": 8},
{"VIS_LONG": VIS_exp_long},
{"wait": 40 + NISP_exp_int + 22 + NISP_exp_int + 22},
{"VIS_SHORT": 0},
],
"NISP": [
{"wait": 574 + 40},
{"NISP_J": NISP_exp_eff},
{"wait": 22 + NISP_exp_wait},
{"NISP_H": NISP_exp_eff},
{"wait": 22 + NISP_exp_wait},
{"NISP_Y": NISP_exp_eff},
],
},
3: {
"VIS": [
{"wait": 8},
{"VIS_LONG": VIS_exp_long},
{"wait": 40 + NISP_exp_int + 22 + NISP_exp_int + 22},
{"VIS_SHORT": 0},
],
"NISP": [
{"wait": 574 + 40},
{"NISP_J": NISP_exp_eff},
{"wait": 22 + NISP_exp_wait},
{"NISP_H": NISP_exp_eff},
{"wait": 22 + NISP_exp_wait},
{"NISP_Y": NISP_exp_eff},
],
},
}

def make_obseq(input_path, output_path):
date = []
date_euclid = []
obs_id = []
pointing_id = []
dither_id = []
center_ra = []
center_dec = []
pa = []
center_lon = []
center_lat = []
pa_ecl = []
saa = []

tree = ET.parse(input_path)
root = tree.getroot()
obs_req = list(root.iter("ObservationRequest"))
for obs in tqdm(obs_req, total=len(obs_req)):
survey_name = obs.find('SurveyId').text
if survey_name != "WIDE_SURVEY":
continue

obs_id_ = int(obs.get('id'))

for pointing in obs.iter('PointingRequest'):
# Convert from mjd2000 to mjd
mjd = int(pointing.find('Mjd2000').text)/100_000 + 51544.0
saa_ = float(pointing.find("SAA").text)
attitude = pointing.find('Attitude')
lon_ = float(attitude.find('Longitude').text)
lat_ = float(attitude.find('Latitude').text)
pa_ = float(attitude.find('PositionAngle').text)
pointing_id_ = int(pointing.attrib['id'])
dither_id_ = int(pointing.attrib['ditherId'])

t = Time(mjd, format="mjd")
new_t = t.mjd + shift_6_years

# Ecliptic to ra, dec
ra_, dec_, pa_eq_ = convert_ecliptic_to_equatorial(lon_, lat_, pa_)

date.append(new_t)
date_euclid.append(t.mjd)
obs_id.append(obs_id_)
pointing_id.append(pointing_id_)
dither_id.append(dither_id_)
center_ra.append(ra_)
center_dec.append(dec_)
pa.append(pa_eq_)
center_lon.append(lon_)
center_lat.append(lat_)
pa_ecl.append(pa_)
saa.append(saa_)

col_dtype = [
("date", np.float64),
("exptime", np.float64),
("ra", np.float64),
("dec", np.float64),
("pa", np.float64),
("lon", np.float64),
("lat", np.float64),
("pa_ecliptic", np.float64),
("saa", np.float64),
("filter", "<U6"),
("date_euclid", np.float64),
("obs_id", np.int64),
("pointing_id", np.int64),
("patch_id", np.int64),
("dither_id", np.int16),
]
obs_kind = {
"VIS_LONG": 0,
"VIS_SHORT": 1,
"NISP_J": 2,
"NISP_H": 3,
"NISP_Y": 4,
}

data = np.zeros((len(date), len(obs_kind)), dtype=col_dtype)

for i in tqdm(range(len(date)), total=len(date)):
dither = dither_id[i]
for instrum in ["VIS", "NISP"]:
for j, obs_kind_ in enumerate(dither_seq[dither][instrum]):
obs_kind_name = list(obs_kind_.keys())[0]
if j == 0:
d = date[i]
d_eucl = date_euclid[i]
if obs_kind_name == "wait":
d += s2days(obs_kind_["wait"])
d_eucl += s2days(obs_kind_["wait"])
continue
if "VIS" in obs_kind_name:
filter_name = "VIS"
else:
filter_name = obs_kind_name
obs_kind_ind = obs_kind[obs_kind_name]
data["date"][i, obs_kind_ind] = d
data["exptime"][i, obs_kind_ind] = obs_kind_[obs_kind_name]
data["ra"][i, obs_kind_ind] = center_ra[i]
data["dec"][i, obs_kind_ind] = center_dec[i]
data["pa"][i, obs_kind_ind] = pa[i]
data["lon"][i, obs_kind_ind] = center_lon[i]
data["lat"][i, obs_kind_ind] = center_lat[i]
data["pa_ecliptic"][i, obs_kind_ind] = pa_ecl[i]
data["saa"][i, obs_kind_ind] = saa[i]
data["filter"][i, obs_kind_ind] = filter_name
data["date_euclid"][i, obs_kind_ind] = d_eucl
data["obs_id"][i, obs_kind_ind] = obs_id[i]
data["pointing_id"][i, obs_kind_ind] = pointing_id[i]
data["dither_id"][i, obs_kind_ind] = dither

df = pd.DataFrame(
data.reshape(len(date)*len(obs_kind)),
index=pd.MultiIndex.from_product(
[
range(len(date)),
obs_kind,
],
names=["visit", "obs_kind"],
),
)
df.to_pickle(output_path)


def parse_args(command_args):
parser = ArgumentParser()
parser.add_argument(
"input_xml",
help="Input obseq file from Euclid (.xml format).",
type=str,
)
parser.add_argument(
"-o",
"--output",
dest="output_path",
help="Path where to save the obseq file. Default to the euclidlike_imsim"
" data directory if it exist.",
type=str,
)

args = parser.parse_args(command_args)
return args


def main(command_args):

args = parse_args(command_args)

# Deal with the output
if args.output_path is None:
if importlib.util.find_spec("euclidlike_imsim") is None:
raise ValueError(
"No output path provided and euclidlike_imsim not found."
" Please provide an output path."
)
output_path = str(
files("euclidlike_imsim.data").joinpath("euclid_obseq.pkl")
)
else:
output_path = os.path.abspath(args.output_path)

make_obseq(args.input_xml, output_path)

print(f"obseq saved at: {output_path}")


def run_main():
main(sys.argv[1:])

0 comments on commit d521969

Please sign in to comment.