Commit ed9cc079 authored by vertighel's avatar vertighel
Browse files

guider: unifica start/stop, Camera+Actuator solo imaging, Loop toggle



Guider start/stop unificato ("botta semplice"):
- Camera+Actuator estratti in guider_camera_actuator(), mostrata solo nel
  pannello imaging (unica modalità con scelta reale); i pannelli a camera
  fissa (spectro) passano cam_id/device via data-fixed-* sui bottoni
  Guide/Loop invece che tramite <select>.
- Campo Interval rimosso: era solo lo sleep tra correzioni in
  Guider._loop(), non necessario dato che _acquire() blocca già finché
  la camera non è libera. Sostituito da un selettore Binning, applicato
  una tantum a Guide-start (solo teccam; le scicam passive restano intoccate).
- Nuovo bottone Loop (pannello Guider + widget Expose) per pilotare
  dev.looping via nuovo modulo condiviso web/static/js/loop-toggle.js.
- Priorità di Expose/Guide sul looping: start() su stx.py/atik.py/mako.py
  non rifiuta più se la camera sta facendo loop, lo interrompe e procede.
- Fix bug preesistente: _acquire() controllava self.camera in
  ("cam", "cam2") invece di ("cam1", "cam2"), quindi il ramo passivo
  (lettura ultimo FITS di Expose) non scattava mai per scicam1.

Co-Authored-By: default avatarClaude Sonnet 5 <noreply@anthropic.com>
parent 3134ccd5
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -195,7 +195,7 @@ class Camera(BaseDevice):
    def start(self, duration, imagetype, datetime=None):
        """Start a single exposure.

        Refuses with an error if a loop is active; set ``looping = False`` first.
        Takes priority over a running acquisition loop: stops it first.

        Parameters
        ----------
@@ -207,9 +207,7 @@ class Camera(BaseDevice):
            UTC ISO-8601 string stored for later FITS header use.
        """
        if self._looping:
            log.error("Camera is looping. Set looping=False first.")
            self.error.append("Camera is looping")
            return
            self.looping = False
        self._start(duration, imagetype, datetime)

        
+2 −4
Original line number Diff line number Diff line
@@ -214,7 +214,7 @@ class Guider(Mako):
    def start(self, exptime=None):
        """Acquire a single frame synchronously.

        Refuses with an error if a loop is active; set ``looping = False`` first.
        Takes priority over a running acquisition loop: stops it first.
        The result is stored in ``matrix``.

        Parameters
@@ -223,9 +223,7 @@ class Guider(Mako):
            Exposure time in seconds. If None, the current camera setting is used.
        """
        if self._looping:
            log.error("Mako: camera is looping. Set looping=False first.")
            self.error.append("Camera is looping")
            return
            self.looping = False
        self._start(exptime)

    def _start(self, exptime=None):
+2 −4
Original line number Diff line number Diff line
@@ -242,7 +242,7 @@ class Camera(STX):
    def start(self, duration, imagetype, datetime=None):
        """Start a single exposure.

        Refuses with an error if a loop is active; set ``looping = False`` first.
        Takes priority over a running acquisition loop: stops it first.

        Parameters
        ----------
@@ -255,9 +255,7 @@ class Camera(STX):
            Defaults to the current UTC time.
        """
        if self._looping:
            log.error("Camera is looping. Set looping=False first.")
            self.error.append("Camera is looping")
            return
            self.looping = False
        self._start(duration, imagetype, datetime)


+30 −11
Original line number Diff line number Diff line
@@ -23,6 +23,11 @@ from noctua.utils.analysis import fit_star
from noctua.utils.logger import log


#: Camera devices that acquire passively (read the last FITS Expose wrote to
#: disk, no direct trigger) — the guider never drives their exposure/binning.
PASSIVE_CAMERAS = ("cam1", "cam2")


class Guider:
    """
    Secondary guiding loop that corrects telescope pointing using a guide star.
@@ -37,10 +42,10 @@ class Guider:
        self.camera        = "tec1"
        self.actuator      = "telescope"
        self.exptime       = 1.0
        self.binning       = 1
        self.box           = 300
        self.box_center    = None
        self.target        = "center"
        self.interval      = 5.0
        self.error           = []
        self.last_offset     = [0.0, 0.0]
        self.last_correction = [0.0, 0.0]
@@ -73,12 +78,12 @@ class Guider:
        Parameters
        ----------
        params : dict
            Keys matching any of: camera, actuator, exptime, box, box_center,
            target, interval, ao_offload_threshold.
            Keys matching any of: camera, actuator, exptime, binning, box,
            box_center, target, ao_offload_threshold.
        """

        for key in ("camera", "actuator", "exptime", "box", "box_center",
                    "target", "interval", "ao_offload_threshold", "ao_offload"):
        for key in ("camera", "actuator", "exptime", "binning", "box",
                    "box_center", "target", "ao_offload_threshold", "ao_offload"):
            if key in params:
                setattr(self, key, params[key])
        if "target" in params:
@@ -98,6 +103,17 @@ class Guider:
            self.configure(params)
        if self._task and not self._task.done():
            return

        cam = getattr(devices, self.camera, None)
        if cam is not None:
            if getattr(cam, "looping", False):
                cam.looping = False
            if self.camera not in PASSIVE_CAMERAS:
                try:
                    cam.binning = [self.binning, self.binning]
                except Exception as e:
                    log.warning(f"Guider: could not set binning: {e}")

        if self.actuator == "ao_x":
            ao = getattr(devices, "ao", None)
            if ao is None:
@@ -136,7 +152,10 @@ class Guider:
                msg = f"step error: {e}"
                log.error(f"Guider: {msg}")
                self.error.append(msg)
            await asyncio.sleep(self.interval)
            # No fixed delay: _step()/_acquire() already block on the camera
            # being free, so the next correction starts as soon as it is.
            # The sleep(0) just yields control for cancellation/scheduling.
            await asyncio.sleep(0)

    def _step(self):

@@ -244,7 +263,7 @@ class Guider:

    def _acquire(self, cam):

        if self.camera in ("cam", "cam2"):
        if self.camera in PASSIVE_CAMERAS:
            fits_path = viewer_fits_path(getattr(cam, "_viewer_key", None))
            try:
                return fits.getdata(fits_path).astype(np.float32)
@@ -328,10 +347,10 @@ class Guider:
            "camera":        self.camera,
            "actuator":      self.actuator,
            "exptime":       self.exptime,
            "binning":       self.binning,
            "box":           self.box,
            "box_center":    self.box_center,
            "target":        self.target,
            "interval":      self.interval,
            "last_offset":     self.last_offset,
            "last_correction": self.last_correction,
            "ao_pos":          self.ao_pos,
@@ -379,8 +398,8 @@ def cli():
                        help="Box centre in pixels (default: image centre)")
    parser.add_argument("--target",   default="center",
                        help="Guide target: 'center', 'stick', or 'X,Y' pixel coords")
    parser.add_argument("--interval", type=float, default=5.0,
                        help="Loop interval in seconds")
    parser.add_argument("--binning",  type=int,   default=1,
                        help="Camera binning (applied once at start, teccam only)")
    parser.add_argument("--ao-offload-threshold", type=int, default=90,
                        help="AO stroke threshold (0-100) before offloading to telescope")
    parser.add_argument("--no-ao-offload", action="store_true",
@@ -400,9 +419,9 @@ def cli():
        "camera":               args.camera,
        "actuator":             args.actuator,
        "exptime":              args.exptime,
        "binning":              args.binning,
        "box":                  args.box,
        "target":               target,
        "interval":             args.interval,
        "ao_offload_threshold": args.ao_offload_threshold,
        "ao_offload":           not args.no_ao_offload,
    }
+25 −3
Original line number Diff line number Diff line
@@ -156,12 +156,16 @@

      <h4 class="mt-2 mb-2">Guider</h4>

      {{ ctrl.guider_panel("imaging",
      {{ ctrl.guider_camera_actuator("imaging",
                           [("cam1","scicam1","ctrl-combo1-sci"),
                            ("tec1","teccam1","ctrl-combo1-tec")],
                           "scicam1",
                           has_actuator_select=True) }}

      {{ ctrl.guider_panel("imaging",
                           [("cam1","scicam1","ctrl-combo1-sci"),
                            ("tec1","teccam1","ctrl-combo1-tec")],
                           "scicam1") }}

    </section>

    <section id="mode-panel-spectro" class="mode-panel d-none" data-subsystem="scicam2">
@@ -182,6 +186,24 @@

    </section>

    <section id="mode-panel-echelle" class="mode-panel d-none" data-subsystem="scicam3">

      {{ ctrl.widget_frame("echelle", "scicam3",
                          [("Full","full")],
                          binning_max=2) }}

      {{ ctrl.spectro_panel() }}

      {{ ctrl.expose_widget("echelle", "scicam3", has_filter=False) }}

      <h4 class="mt-2 mb-2">Guider</h4>

      {{ ctrl.guider_panel("echelle",
                           [("tec3","teccam3","ctrl-combo3-tec")],
                           "scicam3") }}

    </section>

  </article>

  <!-- RIGHT: Monitors -->
Loading