Loading noctua/devices/mako.py +14 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ class Mako(BaseDevice): """ Constructor """ super().__init__(url) self.id = url self.vmb = VmbSystem.get_instance() Loading Loading @@ -90,6 +91,10 @@ class Webcam(Mako): """ def __init__(self, url): """ Constructor """ super().__init__(url) self._last_frame = None self._lock = threading.Lock() Loading @@ -99,6 +104,7 @@ class Webcam(Mako): def _frame_handler(self, cam, stream, frame): """Callback to handle frames during streaming.""" if frame.get_status() == FrameStatus.Complete: with self._lock: self._last_frame = frame.as_numpy_ndarray().copy() Loading @@ -108,12 +114,14 @@ class Webcam(Mako): @property def streaming(self): """Get the current streaming status.""" return self._streaming @streaming.setter def streaming(self, b): """Start or stop camera streaming.""" if b == self._streaming: return cam = self._check_connection() Loading Loading @@ -141,6 +149,7 @@ class Webcam(Mako): @property def image(self): """Get the current frame as a raw binary PNG.""" data = self.matrix if data is not None: return array_to_png(data, vmin=self._range[0], vmax=self._range[1]) Loading @@ -149,6 +158,7 @@ class Webcam(Mako): def save_image(self, filename="temp.png"): """Save the current frame as a PNG file.""" png_data = self.image if png_data: with open(filename, 'wb') as f: Loading @@ -158,6 +168,7 @@ class Webcam(Mako): def save_fits(self, filename="temp.fits"): """Save the current frame as a FITS file.""" raw = self.matrix if raw is not None: # Squeeze or reshape to 2D if it's a mono image with a channel dim Loading @@ -174,6 +185,7 @@ class Webcam(Mako): @property def autoexpose(self): """Get exposure auto status and options as a tuple.""" # Accessing the feature object directly to use as_tuple() cam = self._check_connection() return cam.ExposureAuto.as_tuple() Loading @@ -182,6 +194,7 @@ class Webcam(Mako): @autoexpose.setter def autoexpose(self, value): """Set exposure auto status ('Continuous', 'Off', 'Once').""" self.put("ExposureAuto", value) Loading Loading @@ -210,6 +223,7 @@ class Webcam(Mako): # """ # Open an OpenCV window in a separate thread for local monitoring. # """ # import cv2 # if not self._streaming: Loading noctua/utils/image.py +11 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ def array_to_png(data, vmin=0, vmax=255): """ Convert a numpy array to a pure Python PNG binary buffer. """ rescaled = np.clip(data, vmin, vmax) rescaled = ((rescaled - vmin) / (vmax - vmin) * 255).astype(np.uint8) Loading Loading @@ -66,6 +67,7 @@ class Streamer: fps : int, optional Frames per second. Default is 2. """ self.port = port self.host = host self.image_provider = image_provider Loading @@ -76,12 +78,17 @@ class Streamer: def start(self): """Start the HTTP server in a background thread.""" if self.server: return parent = self class StreamHandler(BaseHTTPRequestHandler): """ HTTP Stream handler """ def do_GET(self): if self.path == '/': self.send_response(200) Loading Loading @@ -132,9 +139,11 @@ class Streamer: except (ConnectionResetError, BrokenPipeError): pass def log_message(self, format, *args): return def run_server(): try: parent.server = ThreadingHTTPServer((parent.host, parent.port), StreamHandler) Loading @@ -146,8 +155,10 @@ class Streamer: self._thread = threading.Thread(target=run_server, daemon=True) self._thread.start() def stop(self): """Stop the HTTP server and free the port.""" if self.server: threading.Thread(target=self.server.shutdown).start() self.server.server_close() Loading Loading
noctua/devices/mako.py +14 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ class Mako(BaseDevice): """ Constructor """ super().__init__(url) self.id = url self.vmb = VmbSystem.get_instance() Loading Loading @@ -90,6 +91,10 @@ class Webcam(Mako): """ def __init__(self, url): """ Constructor """ super().__init__(url) self._last_frame = None self._lock = threading.Lock() Loading @@ -99,6 +104,7 @@ class Webcam(Mako): def _frame_handler(self, cam, stream, frame): """Callback to handle frames during streaming.""" if frame.get_status() == FrameStatus.Complete: with self._lock: self._last_frame = frame.as_numpy_ndarray().copy() Loading @@ -108,12 +114,14 @@ class Webcam(Mako): @property def streaming(self): """Get the current streaming status.""" return self._streaming @streaming.setter def streaming(self, b): """Start or stop camera streaming.""" if b == self._streaming: return cam = self._check_connection() Loading Loading @@ -141,6 +149,7 @@ class Webcam(Mako): @property def image(self): """Get the current frame as a raw binary PNG.""" data = self.matrix if data is not None: return array_to_png(data, vmin=self._range[0], vmax=self._range[1]) Loading @@ -149,6 +158,7 @@ class Webcam(Mako): def save_image(self, filename="temp.png"): """Save the current frame as a PNG file.""" png_data = self.image if png_data: with open(filename, 'wb') as f: Loading @@ -158,6 +168,7 @@ class Webcam(Mako): def save_fits(self, filename="temp.fits"): """Save the current frame as a FITS file.""" raw = self.matrix if raw is not None: # Squeeze or reshape to 2D if it's a mono image with a channel dim Loading @@ -174,6 +185,7 @@ class Webcam(Mako): @property def autoexpose(self): """Get exposure auto status and options as a tuple.""" # Accessing the feature object directly to use as_tuple() cam = self._check_connection() return cam.ExposureAuto.as_tuple() Loading @@ -182,6 +194,7 @@ class Webcam(Mako): @autoexpose.setter def autoexpose(self, value): """Set exposure auto status ('Continuous', 'Off', 'Once').""" self.put("ExposureAuto", value) Loading Loading @@ -210,6 +223,7 @@ class Webcam(Mako): # """ # Open an OpenCV window in a separate thread for local monitoring. # """ # import cv2 # if not self._streaming: Loading
noctua/utils/image.py +11 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ def array_to_png(data, vmin=0, vmax=255): """ Convert a numpy array to a pure Python PNG binary buffer. """ rescaled = np.clip(data, vmin, vmax) rescaled = ((rescaled - vmin) / (vmax - vmin) * 255).astype(np.uint8) Loading Loading @@ -66,6 +67,7 @@ class Streamer: fps : int, optional Frames per second. Default is 2. """ self.port = port self.host = host self.image_provider = image_provider Loading @@ -76,12 +78,17 @@ class Streamer: def start(self): """Start the HTTP server in a background thread.""" if self.server: return parent = self class StreamHandler(BaseHTTPRequestHandler): """ HTTP Stream handler """ def do_GET(self): if self.path == '/': self.send_response(200) Loading Loading @@ -132,9 +139,11 @@ class Streamer: except (ConnectionResetError, BrokenPipeError): pass def log_message(self, format, *args): return def run_server(): try: parent.server = ThreadingHTTPServer((parent.host, parent.port), StreamHandler) Loading @@ -146,8 +155,10 @@ class Streamer: self._thread = threading.Thread(target=run_server, daemon=True) self._thread.start() def stop(self): """Stop the HTTP server and free the port.""" if self.server: threading.Thread(target=self.server.shutdown).start() self.server.server_close() Loading