Commit c3de04a6 authored by vertighel's avatar vertighel
Browse files

test complete mako frame in fork

parent d30e1ab5
Loading
Loading
Loading
Loading
Loading
+40 −4
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@ import numpy as np

# Third-party modules
from astropy.io import fits
from vmbpy import VmbSystem
from vmbpy import FrameStatus, VmbSystem

# Custom modules
from .basedevice import BaseDevice
@@ -30,12 +30,33 @@ class Mako(BaseDevice):
    The camera ID is the IP address on the GigE network.
    """

    # Conservative default: caps the GVSP stream well below a 100Mb/s link
    # (~12.5MB/s raw). Weak/old links on the observatory LAN (e.g. an
    # aging PC's Fast Ethernet port) otherwise get flooded at the camera's
    # native GigE rate and silently drop packets, producing frames with
    # missing row bands. Frame rate ends up throughput_limit / frame_size,
    # which is plenty for the 1-10 fps this driver actually needs.
    throughput_limit_bps = 8_000_000

    def __init__(self, url):
        super().__init__(url)
        self.id = url
        self.vmb = VmbSystem.get_instance()
        self._cam = None

    def _set_throughput_limit(self, cam):
        """Cap the camera's GVSP output rate so it never exceeds what the
        network path to the client can actually drain."""
        limit = self.throughput_limit_bps
        try:
            cam.get_feature_by_name('DeviceLinkThroughputLimitMode').set('On')
            cam.get_feature_by_name('DeviceLinkThroughputLimit').set(limit)
        except Exception:
            try:
                cam.get_feature_by_name('StreamBytesPerSecond').set(limit)
            except Exception as e:
                log.warning(f"Mako: could not set throughput limit: {e}")

    def _check_connection(self):
        """Open and cache the VmbSystem + Camera context."""
        if self._cam is None:
@@ -50,6 +71,7 @@ class Mako(BaseDevice):
                        pass
                except Exception:
                    pass
                self._set_throughput_limit(self._cam)
            except Exception as e:
                self._cam = None
                msg = f"Mako connection failed: {e}"
@@ -165,13 +187,27 @@ class Guider(Mako):
        except Exception:
            pass

    max_acquire_retries = 3

    def _acquire(self, cam, exptime_s=None):
        """Call get_frame() with a timeout derived from the exposure time."""
        """Call get_frame() with a timeout derived from the exposure time.

        Retries on incomplete frames (dropped GVSP packets on a slow/lossy
        link) rather than silently returning a partially-filled buffer.
        """
        if exptime_s is None:
            exptime_s = self.loop_exposure
        timeout_ms = int(exptime_s * 1000) + 5000
        last_status = None
        for attempt in range(self.max_acquire_retries):
            frame = cam.get_frame(timeout_ms=timeout_ms)
            last_status = frame.get_status()
            if last_status == FrameStatus.Complete:
                return np.squeeze(frame.as_numpy_ndarray().copy())
            log.warning(f"Mako: incomplete frame ({last_status}), "
                        f"retry {attempt + 1}/{self.max_acquire_retries}")
        raise IOError(f"Mako: {self.max_acquire_retries} consecutive incomplete frames "
                      f"(last status: {last_status})")

    # --- Acquisition ---

+43 −4
Original line number Diff line number Diff line
@@ -28,8 +28,17 @@ HERE = Path(__file__).parent
class TecCam:
    """Wrapper minimale per acquisizione singola da Mako GigE."""

    def __init__(self, ip):
    # Limita il rate GVSP della camera sotto la capacita' reale del link piu'
    # lento (es. 100Mb/s ~ 12.5MB/s su un vecchio PC). Senza questo limite la
    # camera trasmette al suo rate nativo GigE, il link piu' lento non riesce
    # a smaltirlo e si perdono pacchetti (bande di righe mancanti in fondo
    # all'immagine). A 8MB/s il frame rate risultante e' comunque piu' che
    # sufficiente per 1-10 fps.
    max_acquire_retries = 3

    def __init__(self, ip, maxrate=8_000_000):
        self.ip = ip
        self.maxrate = maxrate
        self.array = None   # numpy (H, W), aggiornato da acquire()

    def acquire(self, exptime_s, gain=None, binning=1):
@@ -42,19 +51,38 @@ class TecCam:
            cam = vmb.get_camera_by_id(self.ip)
            with cam:
                self._adjust_packet_size(cam)
                self._set_throughput_limit(cam)
                self._set_exposure(cam, exptime_s)
                if gain is not None:
                    self._set_gain(cam, float(gain))
                self._set_binning(cam, int(binning))

                frame = cam.get_frame(timeout_ms=timeout_ms)
                raw = frame.as_numpy_ndarray()
                raw = self._acquire_frame(cam, timeout_ms)

        self.array = np.squeeze(raw.copy())
        print(f"Acquisita: shape={self.array.shape} dtype={self.array.dtype} "
              f"min={self.array.min()} max={self.array.max()}")
        return self.array

    def _acquire_frame(self, cam, timeout_ms):
        """get_frame() con verifica di completezza e retry.

        Un frame Incomplete (pacchetti GVSP persi) non viene mai accettato
        silenziosamente: si ritenta fino a max_acquire_retries volte.
        """
        import vmbpy

        last_status = None
        for attempt in range(self.max_acquire_retries):
            frame = cam.get_frame(timeout_ms=timeout_ms)
            last_status = frame.get_status()
            if last_status == vmbpy.FrameStatus.Complete:
                return frame.as_numpy_ndarray()
            print(f"  [warn] frame incompleto ({last_status}), "
                  f"retry {attempt + 1}/{self.max_acquire_retries}")
        raise IOError(f"{self.max_acquire_retries} frame incompleti consecutivi "
                      f"(ultimo status: {last_status})")

    # -- feature helpers --

    def _try_set(self, cam, name, value):
@@ -74,6 +102,15 @@ class TecCam:
        except Exception:
            pass

    def _set_throughput_limit(self, cam):
        """Limita il rate di trasmissione GVSP della camera (bytes/s)."""
        try:
            cam.get_feature_by_name('DeviceLinkThroughputLimitMode').set('On')
            cam.get_feature_by_name('DeviceLinkThroughputLimit').set(self.maxrate)
        except Exception:
            if not self._try_set(cam, 'StreamBytesPerSecond', self.maxrate):
                print("  [warn] throughput limit non impostabile su questa camera")

    def _set_exposure(self, cam, exptime_s):
        self._try_set(cam, 'ExposureAuto', 'Off')
        us = exptime_s * 1e6
@@ -154,9 +191,11 @@ def main():
    parser.add_argument('--fits',   action='store_true',      help='Salva anche in FITS')
    parser.add_argument('--vmin',   type=float, default=None, help='Min per PNG')
    parser.add_argument('--vmax',   type=float, default=None, help='Max per PNG')
    parser.add_argument('--maxrate',type=int,   default=8_000_000,
                         help='Limite banda camera in byte/s (default 8000000, adatto a link a 100Mb/s)')
    args = parser.parse_args()

    cam = TecCam(args.ip)
    cam = TecCam(args.ip, maxrate=args.maxrate)
    cam.acquire(args.exptime, gain=args.gain, binning=args.binning)
    cam.save_png(vmin=args.vmin, vmax=args.vmax)
    if args.fits: