Commit 61702073 authored by vertighel's avatar vertighel
Browse files

Fix acquisition bugs: camera-agnostic templates, mkdir, letterbox



- observation.py + fillheader.py: resolve camera device from
  params["camera"] instead of hardcoded cam; use viewer_fits_path
  per device so scicam/teccam FITS land in their viewer.ini paths
- stx.py + atik.py: mkdir -p before writing download file
- fits-viewer.js: letterbox image in square canvas instead of
  stretching; _canvasToImage accounts for letterbox offset

Co-Authored-By: default avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent ffb967ea
Loading
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ class Camera(BaseDevice):
            self._lib.ArtemisConnect.restype = ctypes.c_void_p
            self._lib.ArtemisImageBuffer.restype = ctypes.c_void_p
            self._lib.ArtemisExposureTimeRemaining.restype = ctypes.c_float
            log.debug(f"SDK reported {count} devices.")
        except Exception as e:
            log.error(f"Atik SDK load failed: {e}")
            
@@ -60,7 +61,6 @@ class Camera(BaseDevice):
        
        if self._handle is None and self._lib:
            count = self._lib.ArtemisDeviceCount()
            log.debug(f"SDK reported {count} devices.")
            
            if count > 0:
                self._handle = self._lib.ArtemisConnect(0)
@@ -135,6 +135,8 @@ class Camera(BaseDevice):

        buf_ptr = self._lib.ArtemisImageBuffer(h)
        if buf_ptr:
            from pathlib import Path
            Path(filepath).parent.mkdir(parents=True, exist_ok=True)
            size = w.value * h_img.value
            buffer = (ctypes.c_uint16 * size).from_address(buf_ptr)
            data = np.frombuffer(buffer, dtype=np.uint16).reshape(h_img.value, w.value)
+2 −0
Original line number Diff line number Diff line
@@ -243,6 +243,8 @@ class Camera(STX):
        res = requests.get(f"{self.addr}/{self.element}.FIT")
        log.debug(f"Got original FITS")

        from pathlib import Path
        Path(filepath).parent.mkdir(parents=True, exist_ok=True)
        with open(filepath, 'wb') as f:
            log.debug(f"Writing on disk")
            f.write(res.content)
+5 −2
Original line number Diff line number Diff line
@@ -9,7 +9,8 @@ from astropy.time import Time

# Other templates
from ..config.constants import alt, lat, lon, pixscale, rotangle, temp_fits
from ..devices import cam, dom, foc, lamp, light, rot, tel, tel_temp
from .. import devices as _dev
from ..devices import dom, foc, lamp, light, rot, tel, tel_temp
from ..utils.logger import log
from ..utils.structure import save_filename
from .basetemplate import BaseTemplate
@@ -44,7 +45,9 @@ class Template(BaseTemplate):
        ##### Header update #####
        #########################

        filename = temp_fits
        camera_name = params.get("camera") or "cam"
        cam = getattr(_dev, camera_name)
        filename = params.get("fits_file") or temp_fits
        gps = Time(utc, format="unix")

        try:
+42 −36
Original line number Diff line number Diff line
@@ -10,8 +10,9 @@ from astropy.time import Time
# Other templates
from ..config.constants import (camera_state, filter_name, filter_number,
                                filter_state, frame_number, frame_type,
                                image_state, temp_fits)
from ..devices import cam, dom, tel
                                image_state, temp_fits, viewer_fits_path)
from .. import devices as _dev
from ..devices import dom, tel
from ..utils.coordinates import apply_offset
from ..utils.logger import log
from .basetemplate import BaseTemplate
@@ -38,11 +39,16 @@ class Template(BaseTemplate):
        ########################

        try:
            camera_name = params.get("camera") or "cam"
            cam = getattr(_dev, camera_name)
            fits_file = viewer_fits_path(getattr(cam, '_viewer_key', None))

            objname = params.get("object") or "test"
            repeat = params.get("repeat") or 1
            frametype = params.get("frametype") or "Light"
            binning = params.get("binning") or cam.binning
            filt = params.get("filter") or filter_name[cam.filter]
            filt = params.get("filter") or (
                filter_name.get(cam.filter) if hasattr(cam, 'filter') else None)
            xystart = params.get("xystart") or cam.xystart
            xyend = params.get("xyend") or cam.xyend
            exptime = params["exptime"]
@@ -90,7 +96,8 @@ class Template(BaseTemplate):

        log.info(f"Camera is {camera_state[cam.state]}")

        # Changing filter if not the same as the current one
        # Changing filter if not the same as the current one (only if camera has a filter wheel)
        if filt and hasattr(cam, 'filter'):
            # On bad ctrl-c, filter can be 15 (?!)
            log.warning(f"Filter is number {cam.filter}")
            log.warning(
@@ -101,11 +108,9 @@ class Template(BaseTemplate):
                msg += f"Which is number {filter_number[filt]}"
                log.info(msg)

            # Setting filter based on its number
                cam.filter = filter_number[filt]
                sleep(0.2)

            # Check until filter is not moving
                status = 1
                while status != 0:
                    self.check_pause_or_abort()
@@ -117,7 +122,7 @@ class Template(BaseTemplate):
                        log.error(msg)
                        self.error.append(msg)

                if status == 2:  # i.e. error in moving filter
                    if status == 2:
                        msg = f"Filter {filter_state[status]}"
                        log.error(msg)
                        self.error.append(msg)
@@ -258,7 +263,8 @@ class Template(BaseTemplate):

            fih_params = {
                "object": objname,
                # Add in some way also ob_file and template name from json
                "camera": camera_name,
                "fits_file": fits_file,
            }

            fih_params.update(basic_cabinet)  # radec, altaz, utc, lst
@@ -285,7 +291,7 @@ class Template(BaseTemplate):
                if self.recenter:
                    try:
                        apply_offset(
                            fits_file=temp_fits, box=self.box, display=False)
                            fits_file=fits_file, box=self.box, display=False)
                    except Exception as e:
                        log.warning(f"Cannot apply offset: {e}")
        return
+36 −10
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ export class FitsViewer {
            autoUpdate: true,
            renderer:   null,
            dtype:      '',
            letterbox:  { dx: 0, dy: 0, dw: 800, dh: 800 },
        };

        // ── Secondary camera states (keyed by camera id) ───────────────
@@ -170,9 +171,11 @@ export class FitsViewer {

            this._updateRangeInputs('primary', state.vmin, state.vmax);

            const ratio = `${state.width} / ${state.height}`;
            if (this._el.canvasMain) this._el.canvasMain.style.aspectRatio = ratio;
            if (this._el.canvasPano) this._el.canvasPano.style.aspectRatio = ratio;
            const cv = this._el.canvasMain;
            if (cv) {
                this._primary.letterbox = this._letterboxRect(
                    state.width, state.height, cv.width, cv.height);
            }
            this._updatePixelInfo(-1, -1, null);

        } catch {
@@ -212,8 +215,9 @@ export class FitsViewer {

        const img = await this._loadImage(url);
        const ctx = canvas.getContext('2d');
        const lb  = state.letterbox;
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
        ctx.drawImage(img, lb.dx, lb.dy, lb.dw, lb.dh);
    }


@@ -697,6 +701,19 @@ export class FitsViewer {
    // Geometry helpers
    // -----------------------------------------------------------------------

    /**
     * Compute a letterbox rect (in canvas HTML px) that fits imgW×imgH
     * inside cvW×cvH without stretching.
     * @private
     */
    _letterboxRect(imgW, imgH, cvW = 800, cvH = 800) {
        if (!imgW || !imgH) return { dx: 0, dy: 0, dw: cvW, dh: cvH };
        const scale = Math.min(cvW / imgW, cvH / imgH);
        const dw    = imgW * scale;
        const dh    = imgH * scale;
        return { dx: (cvW - dw) / 2, dy: (cvH - dh) / 2, dw, dh };
    }

    /** @private */
    _zoomAround(state, mx, my, factor, canvas) {
        const newZoom = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, state.zoom * factor));
@@ -716,11 +733,18 @@ export class FitsViewer {
    /** @private */
    _canvasToImage(state, wrapper, cx, cy) {
        const rect  = wrapper.getBoundingClientRect();
        const cvW   = this._el.canvasMain?.width  ?? 800;
        const cvH   = this._el.canvasMain?.height ?? 800;
        const cssX  = (cx - state.panX) / state.zoom;
        const cssY  = (cy - state.panY) / state.zoom;
        // CSS px → canvas HTML px
        const canvX = cssX * (cvW / rect.width);
        const canvY = cssY * (cvH / rect.height);
        // Canvas HTML px → image px via letterbox
        const lb    = state.letterbox;
        return {
            x: cssX * (state.width  / rect.width),
            y: cssY * (state.height / rect.height),
            x: (canvX - lb.dx) * (state.width  / lb.dw),
            y: (canvY - lb.dy) * (state.height / lb.dh),
        };
    }

@@ -849,8 +873,10 @@ export class FitsViewer {
            const img  = new Image();
            img.onload  = () => {
                const ctx = canvas.getContext('2d');
                const lb  = this._letterboxRect(
                    img.naturalWidth, img.naturalHeight, canvas.width, canvas.height);
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                ctx.drawImage(img, lb.dx, lb.dy, lb.dw, lb.dh);
                resolve();
            };
            img.onerror = reject;