ワンクリックで
new-instrument
Set up a new instrument in PyReduce with config.yaml, settings.json, __init__.py, and example script
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Set up a new instrument in PyReduce with config.yaml, settings.json, __init__.py, and example script
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | new-instrument |
| description | Set up a new instrument in PyReduce with config.yaml, settings.json, __init__.py, and example script |
Each instrument lives in pyreduce/instruments/{NAME}/ (uppercase) and needs at minimum three files plus an example script.
Read headers from one file of each type (bias, flat, wavecal, science) to determine:
DETGAIN, or literal like 1.2DATE-OBSOBJECTINSTRUME, note the value for id_instrumentOBSMODE, EXPTYPE, ESO DPR TYPE)To find the overscan/prescan regions, compare a flat field with the known active CCD area:
prescan_x + active_pixels + overscan_x = NAXIS1 (and similarly for y)The orientation code rotates the raw image so dispersion runs along x. Try to figure out which raw axis is longer (dispersion) and which is shorter (cross-dispersion). Common orientations:
0: no change1: 90 deg CCW3: 270 deg CCW (common when NAXIS2 > NAXIS1 is the dispersion axis)4: transposeIf uncertain, pick the most likely value and verify with a trace run. Traces should appear as roughly horizontal lines.
pyreduce/instruments/{NAME}/config.yamlRequired fields:
__instrument__: NAME
instrument: INSTRUME # header keyword containing instrument name
id_instrument: NAME # value to match in that keyword
telescope: TelescopeName
date: DATE-OBS
date_format: fits
extension: 0 # FITS extension with image data
orientation: 3 # rotation code
transpose: false
prescan_x: 53 # pixels to trim from raw NAXIS1 start
overscan_x: 47 # pixels to trim from raw NAXIS1 end
prescan_y: 0
overscan_y: 64
naxis_x: NAXIS1
naxis_y: NAXIS2
gain: DETGAIN # header keyword or literal number
readnoise: 5.5 # header keyword or literal number
dark: 0
sky: 0
exposure_time: EXPTIME
ra: OBJ_RA # or RA, etc
dec: OBJ_DEC # or DEC, etc
longitude: -17.8792 # observatory coordinates (literal)
latitude: 28.7603
altitude: 2333
target: OBJECT
observation_type: OBSMODE # header keyword for file type
# File classification: all kw_ fields use the same header keyword
# id_ fields are regex patterns to match against that keyword
kw_bias: OBSMODE
kw_flat: OBSMODE
kw_curvature: OBSMODE
kw_scatter: OBSMODE
kw_orders: OBSMODE
kw_wave: OBSMODE
kw_comb: null
kw_spec: OBSMODE
id_bias: BIAS
id_flat: HRF_FF
id_orders: HRF_FF # same files used for order tracing
id_curvature: HRF_TH # or same as flat
id_scatter: HRF_FF
id_wave: HRF_TH
id_comb: null
id_spec: HRF_OBJ
Multi-fiber instruments: Add a fibers: section if the instrument has multiple fibers per order.
fibers:
fibers_per_order: 2 # auto-pairs traces by gap analysis
groups:
obj:
range: [1, 2] # half-open: fiber_idx 1 only
merge: center
sky:
range: [2, 3] # half-open: fiber_idx 2 only
merge: center
use:
default: [obj, sky] # extract each group separately
Key points about fiber config:
range: [start, end] is half-open (1-based). [1, 2] = fiber 1 only, [2, 3] = fiber 2 onlymerge: center picks the center fiber; merge: average averages; merge: [1] picks the 1st fiber in the range (1-indexed within range)use.default: [obj, sky] makes each group extract independently. Without this, all grouped traces are mixed together which causes problems when the extraction height calculation needs overlapping column ranges between neighborsuse, the default is "groups" which concatenates all grouped traces into one list -- this breaks extraction for dual-fiber instruments because neighboring traces (obj/sky of same order) are only ~6px apart and edge orders may have non-overlapping column rangespyreduce/instruments/{NAME}/settings.jsonInherit defaults and override what's needed:
{
"__instrument__": "NAME",
"__inherits__": "defaults/settings.json",
"trace": {
"degree": 6,
"noise": 50,
"min_cluster": 3000,
"filter_y": 120
},
"norm_flat": {
"extraction_height": 6,
"oversampling": 10
},
"wavecal_master": {
"extraction_height": 6
},
"science": {
"extraction_height": 6,
"oversampling": 10
}
}
Important: For multi-fiber instruments, set extraction_height to an explicit pixel value (>= 2) in norm_flat, wavecal_master, and science. A fractional value like 0.5 triggers neighbor-distance calculation which fails when edge orders have non-overlapping column ranges. The pixel value should be less than half the inter-order separation between same-group traces.
pyreduce/instruments/{NAME}/__init__.pyMinimal version:
"""Instrument-specific info for {NAME}."""
import logging
from ..common import Instrument
logger = logging.getLogger(__name__)
class NAME(Instrument):
def add_header_info(self, header, channel, **kwargs):
header = super().add_header_info(header, channel)
return header
The class name must match the directory name (uppercase). Override methods only if needed (e.g. RA unit conversion, JD offset, custom wavecal filename).
examples/{name}_example.pyUse Pipeline.from_instrument() for instruments with proper config:
from pyreduce.pipeline import Pipeline
import os
base_dir = os.environ.get("REDUCE_DATA", os.path.expanduser("~/REDUCE_DATA"))
data = Pipeline.from_instrument(
instrument="NAME",
target="TargetName",
night="2026-04-12",
channel="",
steps=("bias", "flat", "trace", "norm_flat", "wavecal_master", "science"),
base_dir=base_dir,
input_dir=os.path.join(base_dir, "NAME"),
plot=1,
).run()
Test incrementally:
load_instrument('NAME') -- config loads without errorssort_files(...) -- files classified correctlybias, flat, trace -- check trace count and positionsnorm_flat -- extraction succeedswavecal_master -- extraction succeedsscience -- extraction succeedsUse PYREDUCE_PLOT=0 for headless testing.
kw_* points to the right header keyword and id_* patterns match (case-insensitive regex).use.default: [group1, group2] to extract groups independently.channels is not set in config. Only needed for instruments with separate detector chips/arms.order_centers_{channel}.yaml: Known y-positions for order matching (assigns physical order numbers m)wavecal_*.npz: Pre-computed wavelength solutionsmask_*.fits.gz: Bad pixel maskssettings_{channel}.json: Per-channel setting overrides (uses "__inherits__": "{NAME}/settings.json")