diff --git a/README.md b/README.md index 2aa08e1..dd865af 100644 --- a/README.md +++ b/README.md @@ -49,30 +49,30 @@ gst-launch-1.0 idsueyesrc config-file=ini/whole-presacler64_autoexp-binningx2.in ``` ## Network Streaming -see more at network_guide.md -### Sending Line Scan Data Over UDP +### Quick Start - Single Line Transmission (2456x1) -#### Real Data Pipeline -Send camera data as raw UDP stream (note: 5ms exposure is too fast): +#### Send Single Line via UDP +Extract and transmit one line from camera (daytime, 200fps): ```powershell -gst-launch-1.0 idsueyesrc config-file=ini/200fps-2456x4pix-cw.ini exposure=5 framerate=300 ` +gst-launch-1.0 idsueyesrc config-file=ini/200fps-2456x4pix-cw.ini exposure=5 framerate=200 ` + ! videocrop bottom=3 ` ! queue ` ! udpsink host=127.0.0.1 port=5000 ``` -#### Python/OpenCV Receiver -Receive and process raw column data: +#### Receive and Display ```pwsh -uv run scripts/recv_raw_column.py +uv run .\scripts\recv_raw_rolling.py --display-fps 60 ``` -Or with rolling analysis: -```pwsh -uv run .\scripts\recv_raw_rolling.py -``` +**What's happening:** +- Camera captures 2456x4 pixels at row 500 of the sensor +- `videocrop bottom=3` extracts only the top line (2456x1) +- 7368 bytes transmitted per frame (2456 × 1 × 3 BGR channels) +- Receiver displays as a rolling vertical scan -See [`scripts/recv_raw_column.py`](scripts/recv_raw_column.py) for the Python implementation with debug options. +See [network_guide.md](network_guide.md) for detailed configuration options, nighttime settings, and recording. ### Demo/Test Data Streaming diff --git a/ini/100fps-10exp-2456x4pix-500top-cw-extragain.ini b/ini/100fps-10exp-2456x4pix-500top-cw-extragain.ini new file mode 100644 index 0000000..b3cebc8 --- /dev/null +++ b/ini/100fps-10exp-2456x4pix-500top-cw-extragain.ini @@ -0,0 +1,222 @@ +[Versions] +ueye_api_64.dll=4.93.1730 +ueye_usb_64.sys=4.93.1314 +ueye_boot_64.sys=4.93.1314 + + +[Sensor] +Sensor=UI308xCP-C +Sensor bit depth=0 +Sensor source gain=24 +FPN correction mode=0 +Black reference mode=0 +Sensor digital gain=0 + + +[Image size] +Start X=0 +Start Y=500 +Start X absolute=1 +Start Y absolute=1 +Width=2456 +Height=4 +Binning=0 +Subsampling=0 + + +[Scaler] +Mode=0 +Factor=0.000000 + + +[Multi AOI] +Enabled=0 +Mode=0 +x1=0 +x2=0 +x3=0 +x4=0 +y1=0 +y2=0 +y3=0 +y4=0 + + +[Shutter] +Mode=0 +Linescan number=0 + + +[Log Mode] +Mode=3 +Manual value=0 +Manual gain=0 + + +[Timing] +Pixelclock=237 +Extended pixelclock range=0 +Framerate=99.968929 +Exposure=9.910081 +Long exposure=0 +Dual exposure ratio=0 + + +[Selected Converter] +IS_SET_CM_RGB32=2 +IS_SET_CM_RGB24=2 +IS_SET_CM_RGB16=2 +IS_SET_CM_RGB15=2 +IS_SET_CM_Y8=2 +IS_SET_CM_RGB8=2 +IS_SET_CM_BAYER=8 +IS_SET_CM_UYVY=2 +IS_SET_CM_UYVY_MONO=2 +IS_SET_CM_UYVY_BAYER=2 +IS_CM_CBYCRY_PACKED=0 +IS_SET_CM_RGBY=8 +IS_SET_CM_RGB30=2 +IS_SET_CM_Y12=2 +IS_SET_CM_BAYER12=8 +IS_SET_CM_Y16=2 +IS_SET_CM_BAYER16=8 +IS_CM_BGR12_UNPACKED=2 +IS_CM_BGRA12_UNPACKED=2 +IS_CM_JPEG=0 +IS_CM_SENSOR_RAW10=8 +IS_CM_MONO10=2 +IS_CM_BGR10_UNPACKED=2 +IS_CM_RGBA8_PACKED=2 +IS_CM_RGB8_PACKED=2 +IS_CM_RGBY8_PACKED=8 +IS_CM_RGB10V2_PACKED=8 +IS_CM_RGB12_UNPACKED=2 +IS_CM_RGBA12_UNPACKED=2 +IS_CM_RGB10_UNPACKED=2 +IS_CM_RGB8_PLANAR=2 + + +[Parameters] +Colormode=1 +Gamma=1.200000 +Hardware Gamma=0 +Blacklevel Mode=0 +Blacklevel Offset=4 +Hotpixel Mode=2 +Hotpixel Threshold=0 +Sensor Hotpixel=0 +Adaptive hotpixel correction enable=0 +Adaptive hotpixel correction mode=0 +Adaptive hotpixel correction sensitivity=3 +GlobalShutter=0 +AllowRawWithLut=0 + + +[Gain] +Master=52 +Red=19 +Green=0 +Blue=33 +GainBoost=1 + + +[Processing] +EdgeEnhancementFactor=0 +RopEffect=0 +Whitebalance=0 +Whitebalance Red=1.000000 +Whitebalance Green=1.000000 +Whitebalance Blue=1.000000 +Color correction=4 +Color_correction_factor=1.000000 +Color_correction_satU=100 +Color_correction_satV=100 +Bayer Conversion=1 +JpegCompression=0 +NoiseMode=0 +ImageEffect=0 +LscModel=0 +WideDynamicRange=0 + + +[Auto features] +Auto Framerate control=0 +Brightness exposure control=0 +Brightness gain control=0 +Auto Framerate Sensor control=0 +Brightness exposure Sensor control=0 +Brightness gain Sensor control=0 +Brightness exposure Sensor control photometry=0 +Brightness gain Sensor control photometry=0 +Brightness control once=0 +Brightness reference=128 +Brightness speed=50 +Brightness max gain=100 +Brightness max exposure=2.511838 +Brightness Aoi Left=0 +Brightness Aoi Top=500 +Brightness Aoi Width=2456 +Brightness Aoi Height=4 +Brightness Hysteresis=2 +AutoImageControlMode=2 +AutoImageControlPeakWhiteChannel=0 +AutoImageControlExposureMinimum=0.000000 +AutoImageControlPeakWhiteChannelMode=0 +AutoImageControlPeakWhiteGranularity=0 +Auto WB control=0 +Auto WB type=2 +Auto WB RGB color model=1 +Auto WB RGB color temperature=5000 +Auto WB offsetR=0 +Auto WB offsetB=0 +Auto WB gainMin=0 +Auto WB gainMax=100 +Auto WB speed=50 +Auto WB Aoi Left=0 +Auto WB Aoi Top=500 +Auto WB Aoi Width=2456 +Auto WB Aoi Height=4 +Auto WB Once=0 +Auto WB Hysteresis=2 +Brightness Skip Frames Trigger Mode=4 +Brightness Skip Frames Freerun Mode=4 +Auto WB Skip Frames Trigger Mode=4 +Auto WB Skip Frames Freerun Mode=4 + + +[Trigger and Flash] +Trigger mode=0 +Trigger timeout=200 +Trigger delay=0 +Trigger debounce mode=0 +Trigger debounce delay time=1 +Trigger burst size=1 +Trigger prescaler frame=1 +Trigger prescaler line=1 +Trigger input=1 +Flash strobe=0 +Flash delay=0 +Flash duration=0 +Flash auto freerun=0 +PWM mode=0 +PWM frequency=20000000 +PWM dutycycle=20000000 +GPIO state=3 +GPIO direction=0 +GPIO1 Config=1 +GPIO2 Config=1 + + +[Vertical AOI Merge Mode] +Mode=0 +Position=0 +Additional Position=0 +Height=2 + + +[Level Controlled Trigger Mode] +Mode=0 + + +[Memory] +Camera memory mode=1 diff --git a/ini/200fps-2456x4pix-cw.ini b/ini/200fps-2456x4pix-cw.ini index ff7ffea..e8fe2f5 100644 --- a/ini/200fps-2456x4pix-cw.ini +++ b/ini/200fps-2456x4pix-cw.ini @@ -15,9 +15,9 @@ Sensor digital gain=0 [Image size] Start X=0 -Start Y=0 +Start Y=500 Start X absolute=0 -Start Y absolute=0 +Start Y absolute=500 Width=2456 Height=4 Binning=0 diff --git a/network_guide.md b/network_guide.md index 9cfaf5c..9269057 100644 --- a/network_guide.md +++ b/network_guide.md @@ -1,26 +1,65 @@ -# how to send a line +# How to Send a Single Line (2456x1) -real data +## Real Data - Single Line Transmission + +The camera captures 2456x4 pixels, but we extract and transmit only **one line (2456x1)** using `videocrop`. + +### Daytime Configuration (200fps) ```powershell -gst-launch-1.0 idsueyesrc config-file=ini/200fps-2456x4pix-cw.ini exposure=5 framerate=300 ` - ! queue ` +gst-launch-1.0 idsueyesrc config-file=ini/200fps-2456x4pix-cw.ini exposure=5 framerate=200 ` + ! videocrop bottom=3 ` + ! queue ` ! udpsink host=127.0.0.1 port=5000 ``` -note: 5ms is bit too fast for us +### Nighttime Configuration (100fps, extra gain) +```powershell +gst-launch-1.0 idsueyesrc config-file=ini/100fps-10exp-2456x4pix-500top-cw-extragain.ini exposure=10 framerate=100 ` + ! videocrop bottom=3 ` + ! queue ` + ! udpsink host=127.0.0.1 port=5000 +``` + +**Key Parameters:** +- `videocrop bottom=3` - Extracts only the top line (removes bottom 3 rows from 2456x4 image) +- Input: 2456x4 BGR from camera +- Output: 2456x1 BGR line transmitted via UDP +- Frame size: 7368 bytes (2456 × 1 × 3 channels) + +**Alternative:** To extract the bottom line instead, use `videocrop top=3` ### Python/OpenCV Receiver ```pwsh -uv run scripts/recv_raw_column.py -``` -or rolling like -```pwsh +# Basic rolling display uv run .\scripts\recv_raw_rolling.py ``` -See [`scripts/recv_raw_column.py`](scripts/recv_raw_column.py) for the Python implementation with debug options. + +```pwsh +# With display throttling and recording +uv run .\scripts\recv_raw_rolling.py --display-fps 60 --save-mjpeg .\results\output_60fps.avi +``` + +```pwsh +# Max performance (no display, stats only) +uv run .\scripts\recv_raw_rolling.py --no-display +``` + +See [`scripts/recv_raw_rolling.py`](scripts/recv_raw_rolling.py) for the Python implementation with debug options. -# demo data +## Configuration Notes + +Both INI files are configured with: +- Start Y = 500 (captures from row 500 of the sensor) +- Height = 4 pixels +- Width = 2456 pixels +- This optimizes for the center region of the sensor + +**Note:** `exposure=5` (5ms) may be too fast for some applications. Adjust based on your requirements. + +--- + +# Demo Data (Testing) ## Sender (crop to first column, send raw over UDP) ```pwsh gst-launch-1.0 -v ` diff --git a/scripts/recv_raw_rolling.py b/scripts/recv_raw_rolling.py index 29f62e5..c8c56c9 100644 --- a/scripts/recv_raw_rolling.py +++ b/scripts/recv_raw_rolling.py @@ -40,25 +40,25 @@ if ENABLE_DISPLAY: # Debug flag - set to True to see frame reception details DEBUG = False -# Line drop detection parameters -EXPECTED_FPS = 200 # Expected frame rate (from 200fps ini file) -EXPECTED_INTERVAL_MS = 1000.0 / EXPECTED_FPS # 5ms for 200fps -DROP_THRESHOLD_MS = EXPECTED_INTERVAL_MS * 2.5 # Alert if gap > 2.5x expected (12.5ms) +# Frame statistics parameters STATS_WINDOW_SIZE = 100 # Track stats over last N frames STATUS_INTERVAL = 100 # Print status every N frames +DROP_THRESHOLD_MULTIPLIER = 2.5 # Alert if gap > 2.5x rolling average +MIN_SAMPLES_FOR_DROP_DETECTION = 10 # Need at least N samples to detect drops # OPTIMIZED: Using NumPy indexing instead of cv2.rotate() for better performance # Extracting first row and reversing it is equivalent to ROTATE_90_COUNTERCLOCKWISE + first column # Stream parameters (match your GStreamer sender) -COLUMN_WIDTH = 4 # Width from 200fps-2456x4pix-cw.ini -COLUMN_HEIGHT = 2456 # Height from 200fps-2456x4pix-cw.ini +# Modified to receive single line: 2456x1 instead of 4x2456 +COLUMN_WIDTH = 2456 # One line width +COLUMN_HEIGHT = 1 # One line height CHANNELS = 3 -FRAME_SIZE = COLUMN_WIDTH * COLUMN_HEIGHT * CHANNELS # bytes (29472) +FRAME_SIZE = COLUMN_WIDTH * COLUMN_HEIGHT * CHANNELS # bytes (7368) # Display parameters DISPLAY_WIDTH = 800 # Width of rolling display in pixels -DISPLAY_HEIGHT = COLUMN_HEIGHT +DISPLAY_HEIGHT = COLUMN_WIDTH # 2456 pixels tall (the line width becomes display height) UDP_IP = "0.0.0.0" UDP_PORT = 5000 @@ -68,7 +68,7 @@ sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 16777216) # 16MB buffer sock.bind((UDP_IP, UDP_PORT)) -print(f"Receiving raw {COLUMN_WIDTH}x{COLUMN_HEIGHT} RGB columns on UDP port {UDP_PORT}") +print(f"Receiving raw {COLUMN_WIDTH}x{COLUMN_HEIGHT} BGR line on UDP port {UDP_PORT}") if ENABLE_DISPLAY: if args.display_fps > 0: print(f"Display: ENABLED - Rolling display ({DISPLAY_WIDTH}x{DISPLAY_HEIGHT}) @ {args.display_fps} Hz (throttled)") @@ -125,15 +125,18 @@ while True: if first_frame_time is None: first_frame_time = current_time - # Line drop detection + # Frame interval tracking and drop detection if last_frame_time is not None: interval_ms = (current_time - last_frame_time) * 1000 frame_intervals.append(interval_ms) - # Detect line drop - if interval_ms > DROP_THRESHOLD_MS: - total_drops += 1 - drops_since_last_status += 1 + # Detect drops based on rolling average (only after we have enough samples) + if len(frame_intervals) >= MIN_SAMPLES_FOR_DROP_DETECTION: + avg_interval = np.mean(frame_intervals) + drop_threshold = avg_interval * DROP_THRESHOLD_MULTIPLIER + if interval_ms > drop_threshold: + total_drops += 1 + drops_since_last_status += 1 last_frame_time = current_time frame_count += 1 @@ -156,12 +159,12 @@ while True: if ENABLE_DISPLAY: # Parse the incoming data - ALWAYS process every frame - frame = np.frombuffer(data, dtype=np.uint8).reshape((COLUMN_WIDTH, COLUMN_HEIGHT, CHANNELS)) + # Receiving 2456x1 line directly - reshape as a vertical column + # Input is 2456 pixels wide x 1 pixel tall, we want it as 2456 tall x 1 wide + frame = np.frombuffer(data, dtype=np.uint8).reshape((COLUMN_HEIGHT, COLUMN_WIDTH, CHANNELS)) - # OPTIMIZED: Extract first row and transpose to column (equivalent to rotating and taking first column) - # This avoids expensive cv2.rotate() - uses NumPy indexing instead - # For ROTATE_90_COUNTERCLOCKWISE: first column of rotated = first row reversed - column = frame[0, ::-1, :].reshape(COLUMN_HEIGHT, 1, CHANNELS) + # Transpose to make it vertical: (1, 2456, 3) -> (2456, 1, 3) + column = frame.transpose(1, 0, 2) # Insert the single column into the rolling buffer at the current position # This happens for EVERY received frame @@ -192,7 +195,7 @@ while True: break else: # No display mode - just validate the data can be reshaped - frame = np.frombuffer(data, dtype=np.uint8).reshape((COLUMN_WIDTH, COLUMN_HEIGHT, CHANNELS)) + frame = np.frombuffer(data, dtype=np.uint8).reshape((COLUMN_HEIGHT, COLUMN_WIDTH, CHANNELS)) if ENABLE_DISPLAY: if video_writer is not None: