Compare commits
8 Commits
4856248bc5
...
3349050849
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3349050849 | ||
|
|
8c650dde33 | ||
|
|
e16d36128b | ||
|
|
fa3dbdef38 | ||
|
|
25f32bf8e9 | ||
|
|
581d0ce7ae | ||
|
|
76626278ca | ||
|
|
64e4803df3 |
@ -49,6 +49,7 @@ 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
|
||||
|
||||
|
||||
42
scripts/build_go_receiver.ps1
Normal file
42
scripts/build_go_receiver.ps1
Normal file
@ -0,0 +1,42 @@
|
||||
# Build script for Go UDP receiver on Windows
|
||||
# Run this script to compile the high-performance Go receiver
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "Building Go UDP Receiver..." -ForegroundColor Cyan
|
||||
|
||||
# Check if Go is installed
|
||||
try {
|
||||
$goVersion = go version
|
||||
Write-Host "✓ Found Go: $goVersion" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "✗ Go is not installed or not in PATH" -ForegroundColor Red
|
||||
Write-Host " Download from: https://go.dev/dl/" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Navigate to scripts/go directory
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$goDir = Join-Path $scriptDir "go"
|
||||
Set-Location $goDir
|
||||
|
||||
# Clean dependencies
|
||||
Write-Host "`nCleaning dependencies..." -ForegroundColor Cyan
|
||||
go mod tidy
|
||||
|
||||
# Build the executable
|
||||
Write-Host "`nBuilding executable..." -ForegroundColor Cyan
|
||||
go build -o recv_raw_rolling.exe main.go
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "`n✓ Build successful!" -ForegroundColor Green
|
||||
Write-Host " Executable: scripts\go\recv_raw_rolling.exe" -ForegroundColor Green
|
||||
Write-Host "`nTo run:" -ForegroundColor Yellow
|
||||
Write-Host " cd scripts\go" -ForegroundColor White
|
||||
Write-Host " .\recv_raw_rolling.exe" -ForegroundColor White
|
||||
Write-Host "`nPress ESC to quit the application" -ForegroundColor Gray
|
||||
} else {
|
||||
Write-Host "`n✗ Build failed!" -ForegroundColor Red
|
||||
Write-Host " See scripts\go\README.md for troubleshooting" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
149
scripts/go/README.md
Normal file
149
scripts/go/README.md
Normal file
@ -0,0 +1,149 @@
|
||||
# Go UDP Receiver - High-Performance UDP Testing Tool
|
||||
|
||||
A high-performance Go implementation for testing UDP reception performance. This version focuses purely on UDP performance without any GUI/display components, making it ideal for benchmarking network throughput and detecting dropped frames.
|
||||
|
||||
## Features
|
||||
|
||||
- **Pure UDP Performance**: No GUI overhead, focuses on network performance
|
||||
- **High Throughput**: Go's native performance handles 200+ fps easily
|
||||
- **Low Latency**: Dedicated goroutine for UDP reception
|
||||
- **Drop Detection**: Tracks and reports dropped frames based on timing
|
||||
- **Bandwidth Monitoring**: Real-time bandwidth usage statistics
|
||||
- **Clean Output**: Console-based statistics ideal for logging and analysis
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Go 1.21+**: Download from https://go.dev/dl/
|
||||
- **No other dependencies**: Pure Go implementation using only standard library
|
||||
|
||||
## Building
|
||||
|
||||
### Windows (PowerShell)
|
||||
|
||||
```powershell
|
||||
# From project root
|
||||
.\scripts\build_go_receiver.ps1
|
||||
|
||||
# Or manually from scripts/go directory
|
||||
cd scripts\go
|
||||
go build -o recv_raw_rolling.exe main.go
|
||||
```
|
||||
|
||||
### Linux/macOS
|
||||
|
||||
```bash
|
||||
# From project root
|
||||
./scripts/build_go_receiver.sh
|
||||
|
||||
# Or manually from scripts/go directory
|
||||
cd scripts/go
|
||||
go build -o recv_raw_rolling main.go
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
# Windows
|
||||
cd scripts\go
|
||||
.\recv_raw_rolling.exe
|
||||
|
||||
# Linux/macOS
|
||||
cd scripts/go
|
||||
./recv_raw_rolling
|
||||
```
|
||||
|
||||
Press `Ctrl+C` to stop and display final statistics.
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit the constants in [`main.go`](main.go:13) to match your setup:
|
||||
|
||||
- `COLUMN_WIDTH`: Width of incoming data (default: 4)
|
||||
- `COLUMN_HEIGHT`: Height of incoming data (default: 2456)
|
||||
- `CHANNELS`: Number of color channels (default: 3 for RGB)
|
||||
- `UDP_PORT`: UDP port to listen on (default: 5000)
|
||||
- `EXPECTED_FPS`: Expected frame rate for drop detection (default: 200)
|
||||
|
||||
## Output
|
||||
|
||||
The receiver displays statistics every 100 frames:
|
||||
|
||||
```
|
||||
Frame 100: Real FPS: 198.5 | Instant: 200.2 | BW: 34.56 MB/s
|
||||
Frame 200: Real FPS: 199.1 | Instant: 199.8 | BW: 34.62 MB/s
|
||||
Frame 300: Real FPS: 199.8 | Instant: 200.1 | BW: 34.59 MB/s | ⚠️ 2 drops
|
||||
```
|
||||
|
||||
On shutdown (Ctrl+C), it displays final statistics:
|
||||
|
||||
```
|
||||
=== Final Statistics ===
|
||||
Total Frames: 1234
|
||||
Total Time: 6.17 seconds
|
||||
Average FPS: 200.0
|
||||
Instant FPS: 199.8
|
||||
Total Drops: 5
|
||||
Total Data: 35.67 MB
|
||||
Bandwidth: 34.58 MB/s
|
||||
```
|
||||
|
||||
## Performance Comparison
|
||||
|
||||
| Implementation | Typical FPS | CPU Usage | Overhead |
|
||||
|---------------|-------------|-----------|----------|
|
||||
| Python (OpenCV) | ~100 fps | High | Display rendering |
|
||||
| **Go (Console)** | **200+ fps** | **Minimal** | **None** |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **UDP Performance Testing**: Measure maximum UDP throughput
|
||||
- **Network Diagnostics**: Detect packet loss and timing issues
|
||||
- **Benchmarking**: Compare network setups and configurations
|
||||
- **Automated Testing**: Console output suitable for CI/CD pipelines
|
||||
- **High-Speed Data Acquisition**: Validate 200+ fps data streams
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Concurrent UDP Reception**: Dedicated goroutine prevents frame drops
|
||||
- **16MB Receive Buffer**: Configured for high-throughput scenarios
|
||||
- **Zero Display Overhead**: Pure console output for maximum performance
|
||||
- **Graceful Shutdown**: Handles Ctrl+C and displays final statistics
|
||||
- **No External Dependencies**: Pure Go standard library
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Errors
|
||||
|
||||
Ensure you have Go 1.21+ installed:
|
||||
```bash
|
||||
go version
|
||||
```
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
If port 5000 is in use, edit `UDP_PORT` in [`main.go`](main.go:31) and rebuild.
|
||||
|
||||
### High Drop Rate
|
||||
|
||||
1. Check network configuration with [`scripts/udp_optimize.ps1`](../udp_optimize.ps1) (Windows)
|
||||
2. Verify sender is transmitting at correct rate
|
||||
3. Check for network congestion or interference
|
||||
4. Consider increasing receive buffer size in code
|
||||
|
||||
## Comparison with Python Version
|
||||
|
||||
The Python version ([`scripts/recv_raw_rolling.py`](../recv_raw_rolling.py)) includes visual display but has lower throughput:
|
||||
|
||||
- **Use Go version for**: Performance testing, benchmarking, headless servers
|
||||
- **Use Python version for**: Visual debugging, development, seeing actual data
|
||||
|
||||
## Changes from Previous Version
|
||||
|
||||
This version has been simplified to focus purely on UDP performance:
|
||||
|
||||
- ✅ Removed SDL2 dependency
|
||||
- ✅ Removed GUI/display components
|
||||
- ✅ Added comprehensive statistics
|
||||
- ✅ Improved drop detection
|
||||
- ✅ Added bandwidth monitoring
|
||||
- ✅ Zero external dependencies
|
||||
3
scripts/go/go.mod
Normal file
3
scripts/go/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module recv_raw_rolling
|
||||
|
||||
go 1.21
|
||||
0
scripts/go/go.sum
Normal file
0
scripts/go/go.sum
Normal file
203
scripts/go/main.go
Normal file
203
scripts/go/main.go
Normal file
@ -0,0 +1,203 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Stream parameters (match your GStreamer sender)
|
||||
COLUMN_WIDTH = 4
|
||||
COLUMN_HEIGHT = 2456
|
||||
CHANNELS = 3
|
||||
FRAME_SIZE = COLUMN_WIDTH * COLUMN_HEIGHT * CHANNELS // 29472 bytes
|
||||
|
||||
// Line drop detection parameters
|
||||
EXPECTED_FPS = 200
|
||||
EXPECTED_INTERVAL_MS = 1000.0 / EXPECTED_FPS // 5ms for 200fps
|
||||
DROP_THRESHOLD_MS = EXPECTED_INTERVAL_MS * 2.5
|
||||
STATS_WINDOW_SIZE = 100
|
||||
STATUS_INTERVAL = 100
|
||||
|
||||
UDP_PORT = 5000
|
||||
)
|
||||
|
||||
type FrameStats struct {
|
||||
intervals []float64
|
||||
totalDrops int
|
||||
dropsSince int
|
||||
frameCount int
|
||||
firstFrameTime time.Time
|
||||
lastFrameTime time.Time
|
||||
totalBytes int64
|
||||
}
|
||||
|
||||
func newFrameStats() *FrameStats {
|
||||
return &FrameStats{
|
||||
intervals: make([]float64, 0, STATS_WINDOW_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FrameStats) addInterval(intervalMs float64) {
|
||||
if len(fs.intervals) >= STATS_WINDOW_SIZE {
|
||||
fs.intervals = fs.intervals[1:]
|
||||
}
|
||||
fs.intervals = append(fs.intervals, intervalMs)
|
||||
}
|
||||
|
||||
func (fs *FrameStats) avgInterval() float64 {
|
||||
if len(fs.intervals) == 0 {
|
||||
return 0
|
||||
}
|
||||
sum := 0.0
|
||||
for _, v := range fs.intervals {
|
||||
sum += v
|
||||
}
|
||||
return sum / float64(len(fs.intervals))
|
||||
}
|
||||
|
||||
func (fs *FrameStats) printSummary() {
|
||||
if fs.frameCount == 0 {
|
||||
return
|
||||
}
|
||||
elapsed := time.Since(fs.firstFrameTime).Seconds()
|
||||
avgFPS := float64(fs.frameCount) / elapsed
|
||||
avgInterval := fs.avgInterval()
|
||||
instantFPS := 0.0
|
||||
if avgInterval > 0 {
|
||||
instantFPS = 1000.0 / avgInterval
|
||||
}
|
||||
|
||||
bandwidth := float64(fs.totalBytes) / elapsed / (1024 * 1024) // MB/s
|
||||
|
||||
fmt.Println("\n=== Final Statistics ===")
|
||||
fmt.Printf("Total Frames: %d\n", fs.frameCount)
|
||||
fmt.Printf("Total Time: %.2f seconds\n", elapsed)
|
||||
fmt.Printf("Average FPS: %.2f\n", avgFPS)
|
||||
fmt.Printf("Instant FPS: %.2f\n", instantFPS)
|
||||
fmt.Printf("Total Drops: %d\n", fs.totalDrops)
|
||||
fmt.Printf("Total Data: %.2f MB\n", float64(fs.totalBytes)/(1024*1024))
|
||||
fmt.Printf("Bandwidth: %.2f MB/s\n", bandwidth)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Setup UDP socket
|
||||
addr := net.UDPAddr{
|
||||
Port: UDP_PORT,
|
||||
IP: net.ParseIP("0.0.0.0"),
|
||||
}
|
||||
conn, err := net.ListenUDP("udp", &addr)
|
||||
if err != nil {
|
||||
log.Fatal("UDP listen failed:", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Set receive buffer size to 16MB
|
||||
if err := conn.SetReadBuffer(16 * 1024 * 1024); err != nil {
|
||||
log.Printf("Warning: Failed to set recv buffer: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("High-Performance UDP Receiver for Raw %dx%d RGB columns\n", COLUMN_WIDTH, COLUMN_HEIGHT)
|
||||
fmt.Printf("Listening on UDP port %d\n", UDP_PORT)
|
||||
fmt.Printf("Expected frame size: %d bytes\n", FRAME_SIZE)
|
||||
fmt.Printf("Press Ctrl+C to stop\n\n")
|
||||
|
||||
stats := newFrameStats()
|
||||
buffer := make([]byte, 65536)
|
||||
|
||||
// Handle graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Channel for shutdown
|
||||
done := make(chan bool)
|
||||
|
||||
// Start UDP receiver goroutine
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
n, _, err := conn.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
log.Printf("UDP read error: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if n != FRAME_SIZE {
|
||||
// Wrong packet size, skip
|
||||
continue
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
|
||||
// Initialize timing on first frame
|
||||
if stats.firstFrameTime.IsZero() {
|
||||
stats.firstFrameTime = currentTime
|
||||
fmt.Println("Started receiving frames...")
|
||||
}
|
||||
|
||||
// Line drop detection
|
||||
if !stats.lastFrameTime.IsZero() {
|
||||
intervalMs := float64(currentTime.Sub(stats.lastFrameTime).Microseconds()) / 1000.0
|
||||
stats.addInterval(intervalMs)
|
||||
|
||||
if intervalMs > DROP_THRESHOLD_MS {
|
||||
stats.totalDrops++
|
||||
stats.dropsSince++
|
||||
}
|
||||
}
|
||||
|
||||
stats.lastFrameTime = currentTime
|
||||
stats.frameCount++
|
||||
stats.totalBytes += int64(n)
|
||||
|
||||
// Print status
|
||||
if stats.frameCount%STATUS_INTERVAL == 0 {
|
||||
elapsed := currentTime.Sub(stats.firstFrameTime).Seconds()
|
||||
realFPS := float64(stats.frameCount) / elapsed
|
||||
avgInterval := stats.avgInterval()
|
||||
instantFPS := 0.0
|
||||
if avgInterval > 0 {
|
||||
instantFPS = 1000.0 / avgInterval
|
||||
}
|
||||
|
||||
bandwidth := float64(stats.totalBytes) / elapsed / (1024 * 1024) // MB/s
|
||||
|
||||
status := fmt.Sprintf("Frame %6d: Real FPS: %6.1f | Instant: %6.1f | BW: %6.2f MB/s",
|
||||
stats.frameCount, realFPS, instantFPS, bandwidth)
|
||||
if stats.dropsSince > 0 {
|
||||
status += fmt.Sprintf(" | ⚠️ %d drops", stats.dropsSince)
|
||||
stats.dropsSince = 0
|
||||
} else if stats.totalDrops > 0 {
|
||||
status += fmt.Sprintf(" | Total drops: %d", stats.totalDrops)
|
||||
}
|
||||
fmt.Println(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for shutdown signal
|
||||
<-sigChan
|
||||
fmt.Println("\nShutdown signal received...")
|
||||
close(done)
|
||||
|
||||
// Give a moment for cleanup
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Print final statistics
|
||||
stats.printSummary()
|
||||
fmt.Println("Goodbye!")
|
||||
}
|
||||
@ -6,17 +6,49 @@
|
||||
# "numpy",
|
||||
# ]
|
||||
# ///
|
||||
#
|
||||
# NOTE: For higher performance (?), see the Go implementation:
|
||||
# scripts/go/main.go -
|
||||
# Build: Run scripts/build_go_receiver.ps1 (Windows) or scripts/build_go_receiver.sh (Linux/macOS)
|
||||
# See scripts/go/README.md for setup instructions
|
||||
#
|
||||
# Usage:
|
||||
# python recv_raw_rolling.py # With OpenCV display (default)
|
||||
# python recv_raw_rolling.py --no-display # Stats only, no display (max performance)
|
||||
|
||||
import socket
|
||||
import numpy as np
|
||||
import cv2
|
||||
import time
|
||||
import argparse
|
||||
from collections import deque
|
||||
|
||||
# Parse command-line arguments
|
||||
parser = argparse.ArgumentParser(description='Receive raw column stream via UDP')
|
||||
parser.add_argument('--no-display', action='store_true',
|
||||
help='Disable OpenCV display for maximum performance (stats only)')
|
||||
parser.add_argument('--display-fps', type=int, default=0,
|
||||
help='Limit display refresh rate (0=every frame, 60=60fps, etc). Reduces cv2.imshow() overhead while receiving all frames')
|
||||
parser.add_argument('--save-mjpeg', type=str, default=None,
|
||||
help='Save rolling display to MJPEG video file (e.g., output.avi). Uses display-fps if set, otherwise 30 fps')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Import OpenCV only if display is enabled
|
||||
ENABLE_DISPLAY = not args.no_display
|
||||
if ENABLE_DISPLAY:
|
||||
import cv2
|
||||
|
||||
# Debug flag - set to True to see frame reception details
|
||||
DEBUG = False
|
||||
|
||||
# Rotation mode - set to rotate incoming data before display
|
||||
# Options: None, cv2.ROTATE_90_CLOCKWISE, cv2.ROTATE_90_COUNTERCLOCKWISE, cv2.ROTATE_180
|
||||
ROTATION = cv2.ROTATE_90_COUNTERCLOCKWISE # Rotate rows to columns
|
||||
# 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)
|
||||
STATS_WINDOW_SIZE = 100 # Track stats over last N frames
|
||||
STATUS_INTERVAL = 100 # Print status every N frames
|
||||
|
||||
# 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
|
||||
@ -32,21 +64,56 @@ UDP_IP = "0.0.0.0"
|
||||
UDP_PORT = 5000
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
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"Display width: {DISPLAY_WIDTH} pixels (rolling)")
|
||||
if ENABLE_DISPLAY:
|
||||
if args.display_fps > 0:
|
||||
print(f"Display: ENABLED - Rolling display ({DISPLAY_WIDTH}x{DISPLAY_HEIGHT}) @ {args.display_fps} Hz (throttled)")
|
||||
else:
|
||||
print(f"Display: ENABLED - Rolling display ({DISPLAY_WIDTH}x{DISPLAY_HEIGHT}) @ full rate")
|
||||
else:
|
||||
print(f"Display: DISABLED - Stats only mode (max performance)")
|
||||
if DEBUG:
|
||||
print(f"Expected frame size: {FRAME_SIZE} bytes")
|
||||
|
||||
cv2.namedWindow("Rolling Column Stream", cv2.WINDOW_NORMAL)
|
||||
# Initialize display if enabled
|
||||
if ENABLE_DISPLAY:
|
||||
cv2.namedWindow("Rolling Column Stream", cv2.WINDOW_NORMAL)
|
||||
rolling_buffer = np.zeros((DISPLAY_HEIGHT, DISPLAY_WIDTH, CHANNELS), dtype=np.uint8)
|
||||
current_column = 0
|
||||
|
||||
# Display throttling support
|
||||
if args.display_fps > 0:
|
||||
display_interval = 1.0 / args.display_fps # seconds between display updates
|
||||
last_display_time = 0
|
||||
else:
|
||||
display_interval = 0 # Update every frame
|
||||
last_display_time = 0
|
||||
|
||||
# MJPEG video writer setup
|
||||
video_writer = None
|
||||
if args.save_mjpeg:
|
||||
# Use display-fps if set, otherwise default to 30 fps for video
|
||||
video_fps = args.display_fps if args.display_fps > 0 else 30
|
||||
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
|
||||
video_writer = cv2.VideoWriter(args.save_mjpeg, fourcc, video_fps,
|
||||
(DISPLAY_WIDTH, DISPLAY_HEIGHT))
|
||||
print(f"Recording to: {args.save_mjpeg} @ {video_fps} fps")
|
||||
|
||||
# Initialize the rolling buffer
|
||||
rolling_buffer = np.zeros((DISPLAY_HEIGHT, DISPLAY_WIDTH, CHANNELS), dtype=np.uint8)
|
||||
current_column = 0
|
||||
frame_count = 0
|
||||
|
||||
# Line drop detection state
|
||||
last_frame_time = None
|
||||
first_frame_time = None
|
||||
frame_intervals = deque(maxlen=STATS_WINDOW_SIZE)
|
||||
total_drops = 0
|
||||
drops_since_last_status = 0
|
||||
|
||||
while True:
|
||||
current_time = time.time()
|
||||
data, addr = sock.recvfrom(65536)
|
||||
|
||||
if len(data) != FRAME_SIZE:
|
||||
@ -54,33 +121,81 @@ while True:
|
||||
print(f"Received {len(data)} bytes (expected {FRAME_SIZE}), skipping...")
|
||||
continue
|
||||
|
||||
if DEBUG:
|
||||
frame_count += 1
|
||||
if frame_count % 30 == 0:
|
||||
print(f"Received {frame_count} frames, current column: {current_column}")
|
||||
|
||||
# Parse the incoming data
|
||||
frame = np.frombuffer(data, dtype=np.uint8).reshape((COLUMN_WIDTH, COLUMN_HEIGHT, CHANNELS))
|
||||
# Initialize timing on first frame
|
||||
if first_frame_time is None:
|
||||
first_frame_time = current_time
|
||||
|
||||
# Apply rotation if configured
|
||||
if ROTATION is not None:
|
||||
rotated = cv2.rotate(frame, ROTATION)
|
||||
# Line 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
|
||||
|
||||
last_frame_time = current_time
|
||||
frame_count += 1
|
||||
|
||||
# Print status every STATUS_INTERVAL frames
|
||||
if frame_count % STATUS_INTERVAL == 0:
|
||||
elapsed_time = current_time - first_frame_time
|
||||
real_fps = frame_count / elapsed_time if elapsed_time > 0 else 0
|
||||
avg_interval = np.mean(frame_intervals) if len(frame_intervals) > 0 else 0
|
||||
instant_fps = 1000.0 / avg_interval if avg_interval > 0 else 0
|
||||
|
||||
status = f"Frame {frame_count}: Real FPS: {real_fps:.1f} | Instant: {instant_fps:.1f}"
|
||||
if drops_since_last_status > 0:
|
||||
status += f" | ⚠️ {drops_since_last_status} drops detected"
|
||||
drops_since_last_status = 0
|
||||
else:
|
||||
status += f" | Total drops: {total_drops}"
|
||||
|
||||
print(status)
|
||||
|
||||
if ENABLE_DISPLAY:
|
||||
# Parse the incoming data - ALWAYS process every frame
|
||||
frame = np.frombuffer(data, dtype=np.uint8).reshape((COLUMN_WIDTH, COLUMN_HEIGHT, 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)
|
||||
|
||||
# Insert the single column into the rolling buffer at the current position
|
||||
# This happens for EVERY received frame
|
||||
rolling_buffer[:, current_column:current_column+1, :] = column
|
||||
|
||||
# Move to the next column position, wrapping around when reaching the end
|
||||
current_column = (current_column + 1) % DISPLAY_WIDTH
|
||||
|
||||
# Display throttling: only refresh display at specified rate
|
||||
# This reduces cv2.imshow() / cv2.waitKey() overhead while keeping all data
|
||||
should_display = True
|
||||
if args.display_fps > 0:
|
||||
if current_time - last_display_time >= display_interval:
|
||||
last_display_time = current_time
|
||||
should_display = True
|
||||
else:
|
||||
should_display = False
|
||||
|
||||
if should_display:
|
||||
# Display the rolling buffer (clean, no overlays)
|
||||
cv2.imshow("Rolling Column Stream", rolling_buffer)
|
||||
|
||||
# Write frame to video if recording
|
||||
if video_writer is not None:
|
||||
video_writer.write(rolling_buffer)
|
||||
|
||||
if cv2.waitKey(1) == 27: # ESC to quit
|
||||
break
|
||||
else:
|
||||
rotated = frame
|
||||
|
||||
# Extract only the first column for smoother rolling (1 pixel/frame)
|
||||
column = rotated[:, 0:1, :]
|
||||
# No display mode - just validate the data can be reshaped
|
||||
frame = np.frombuffer(data, dtype=np.uint8).reshape((COLUMN_WIDTH, COLUMN_HEIGHT, CHANNELS))
|
||||
|
||||
# Insert the single column into the rolling buffer at the current position
|
||||
rolling_buffer[:, current_column:current_column+1, :] = column
|
||||
|
||||
# Move to the next column position, wrapping around when reaching the end
|
||||
current_column = (current_column + 1) % DISPLAY_WIDTH
|
||||
|
||||
# Display the rolling buffer
|
||||
cv2.imshow("Rolling Column Stream", rolling_buffer)
|
||||
|
||||
if cv2.waitKey(1) == 27: # ESC to quit
|
||||
break
|
||||
|
||||
cv2.destroyAllWindows()
|
||||
if ENABLE_DISPLAY:
|
||||
if video_writer is not None:
|
||||
video_writer.release()
|
||||
print(f"Video saved: {args.save_mjpeg}")
|
||||
cv2.destroyAllWindows()
|
||||
232
scripts/udp_optimize.ps1
Normal file
232
scripts/udp_optimize.ps1
Normal file
@ -0,0 +1,232 @@
|
||||
# UDP Performance Optimization Script for Windows
|
||||
# Run as Administrator
|
||||
#
|
||||
# Usage:
|
||||
# .\udp_optimize.ps1 query - Show current settings
|
||||
# .\udp_optimize.ps1 apply - Apply optimizations (backs up to udp_backup.reg)
|
||||
# .\udp_optimize.ps1 revert - Restore from backup
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("query", "apply", "revert")]
|
||||
[string]$Action
|
||||
)
|
||||
|
||||
# Check if running as Administrator
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
if (-not $isAdmin) {
|
||||
Write-Error "This script must be run as Administrator!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$backupFile = "udp_backup.reg"
|
||||
|
||||
# Define registry settings
|
||||
$regSettings = @(
|
||||
@{
|
||||
Path = "HKLM:\SYSTEM\CurrentControlSet\Services\AFD\Parameters"
|
||||
Name = "DefaultReceiveWindow"
|
||||
Type = "DWORD"
|
||||
Value = 16777216 # 16MB
|
||||
Description = "UDP Receive Buffer Size"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SYSTEM\CurrentControlSet\Services\AFD\Parameters"
|
||||
Name = "LargeBufferSize"
|
||||
Type = "DWORD"
|
||||
Value = 4096
|
||||
Description = "Large Buffer Size"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SYSTEM\CurrentControlSet\Services\AFD\Parameters"
|
||||
Name = "MediumBufferSize"
|
||||
Type = "DWORD"
|
||||
Value = 1504
|
||||
Description = "Medium Buffer Size"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters"
|
||||
Name = "TcpWindowSize"
|
||||
Type = "DWORD"
|
||||
Value = 65535
|
||||
Description = "TCP Window Size"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters"
|
||||
Name = "MaxConnectionsPerServer"
|
||||
Type = "DWORD"
|
||||
Value = 16
|
||||
Description = "Max Connections Per Server"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters"
|
||||
Name = "MaxFreeTcbs"
|
||||
Type = "DWORD"
|
||||
Value = 16000
|
||||
Description = "Max Free TCBs"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters"
|
||||
Name = "DefaultTTL"
|
||||
Type = "DWORD"
|
||||
Value = 64
|
||||
Description = "Default TTL"
|
||||
}
|
||||
)
|
||||
|
||||
function Query-Settings {
|
||||
Write-Host "`n=== Current UDP/TCP Performance Settings ===" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
foreach ($setting in $regSettings) {
|
||||
$path = $setting.Path
|
||||
$name = $setting.Name
|
||||
|
||||
# Ensure path exists
|
||||
if (-not (Test-Path $path)) {
|
||||
Write-Host "[$name] Path does not exist: $path" -ForegroundColor Yellow
|
||||
Write-Host " Description: $($setting.Description)" -ForegroundColor Gray
|
||||
Write-Host " Current: NOT SET" -ForegroundColor Yellow
|
||||
Write-Host " Recommended: $($setting.Value)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
$currentValue = Get-ItemProperty -Path $path -Name $name -ErrorAction Stop
|
||||
Write-Host "[$name] $($setting.Description)" -ForegroundColor White
|
||||
Write-Host " Path: $path" -ForegroundColor Gray
|
||||
Write-Host " Current: $($currentValue.$name)" -ForegroundColor Yellow
|
||||
Write-Host " Recommended: $($setting.Value)" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host "[$name] $($setting.Description)" -ForegroundColor White
|
||||
Write-Host " Path: $path" -ForegroundColor Gray
|
||||
Write-Host " Current: NOT SET" -ForegroundColor Yellow
|
||||
Write-Host " Recommended: $($setting.Value)" -ForegroundColor Green
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
|
||||
function Backup-Settings {
|
||||
Write-Host "Creating backup..." -ForegroundColor Cyan
|
||||
|
||||
$backupContent = "Windows Registry Editor Version 5.00`r`n`r`n"
|
||||
|
||||
foreach ($setting in $regSettings) {
|
||||
$path = $setting.Path
|
||||
$name = $setting.Name
|
||||
|
||||
if (Test-Path $path) {
|
||||
try {
|
||||
$currentValue = Get-ItemProperty -Path $path -Name $name -ErrorAction Stop
|
||||
$regPath = $path -replace "HKLM:\\", "HKEY_LOCAL_MACHINE\"
|
||||
$backupContent += "[$regPath]`r`n"
|
||||
$backupContent += "`"$name`"=dword:$("{0:x8}" -f $currentValue.$name)`r`n`r`n"
|
||||
}
|
||||
catch {
|
||||
# Value doesn't exist, note it in backup
|
||||
$regPath = $path -replace "HKLM:\\", "HKEY_LOCAL_MACHINE\"
|
||||
$backupContent += "; [$regPath]`r`n"
|
||||
$backupContent += "; `"$name`" was not set`r`n`r`n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$backupContent | Out-File -FilePath $backupFile -Encoding ASCII
|
||||
Write-Host "Backup saved to: $backupFile" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Apply-Settings {
|
||||
Write-Host "`n=== Applying UDP/TCP Optimizations ===" -ForegroundColor Cyan
|
||||
|
||||
# Create backup first
|
||||
Backup-Settings
|
||||
Write-Host ""
|
||||
|
||||
$changedCount = 0
|
||||
|
||||
foreach ($setting in $regSettings) {
|
||||
$path = $setting.Path
|
||||
$name = $setting.Name
|
||||
$value = $setting.Value
|
||||
|
||||
# Ensure registry path exists
|
||||
if (-not (Test-Path $path)) {
|
||||
Write-Host "Creating registry path: $path" -ForegroundColor Yellow
|
||||
New-Item -Path $path -Force | Out-Null
|
||||
}
|
||||
|
||||
try {
|
||||
# Get current value
|
||||
$currentValue = $null
|
||||
try {
|
||||
$current = Get-ItemProperty -Path $path -Name $name -ErrorAction Stop
|
||||
$currentValue = $current.$name
|
||||
}
|
||||
catch {
|
||||
$currentValue = $null
|
||||
}
|
||||
|
||||
if ($currentValue -eq $value) {
|
||||
Write-Host "[UNCHANGED] $name = $value" -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
Set-ItemProperty -Path $path -Name $name -Value $value -Type $setting.Type
|
||||
Write-Host "[APPLIED] $name = $value (was: $currentValue)" -ForegroundColor Green
|
||||
$changedCount++
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "[ERROR] Failed to set $name : $_" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n$changedCount settings changed." -ForegroundColor Cyan
|
||||
|
||||
if ($changedCount -gt 0) {
|
||||
Write-Host "`n⚠️ IMPORTANT: Restart your computer for changes to take effect!" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
function Revert-Settings {
|
||||
if (-not (Test-Path $backupFile)) {
|
||||
Write-Error "Backup file not found: $backupFile"
|
||||
Write-Host "Run 'apply' first to create a backup, or restore manually." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "`n=== Reverting to Backed Up Settings ===" -ForegroundColor Cyan
|
||||
Write-Host "Importing: $backupFile" -ForegroundColor Yellow
|
||||
|
||||
try {
|
||||
$result = reg import $backupFile 2>&1
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "Settings restored successfully!" -ForegroundColor Green
|
||||
Write-Host "`n⚠️ IMPORTANT: Restart your computer for changes to take effect!" -ForegroundColor Yellow
|
||||
}
|
||||
else {
|
||||
Write-Error "Failed to import registry file: $result"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Error importing registry: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Main execution
|
||||
switch ($Action) {
|
||||
"query" {
|
||||
Query-Settings
|
||||
}
|
||||
"apply" {
|
||||
Apply-Settings
|
||||
}
|
||||
"revert" {
|
||||
Revert-Settings
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`nDone!" -ForegroundColor Cyan
|
||||
@ -497,6 +497,12 @@ gst_idsueyesrc_start (GstBaseSrc * bsrc)
|
||||
SENSORINFO sInfo;
|
||||
ret = is_GetCameraInfo (src->hCam, &cInfo);
|
||||
ret = is_GetSensorInfo (src->hCam, &sInfo);
|
||||
|
||||
/* Log sensor information for debugging AOI issues */
|
||||
if (ret == IS_SUCCESS) {
|
||||
GST_DEBUG_OBJECT (src, "Sensor: %s, max size: %dx%d",
|
||||
sInfo.strSensorName, sInfo.nMaxWidth, sInfo.nMaxHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen (src->config_file)) {
|
||||
@ -715,10 +721,20 @@ gst_idsueyesrc_create (GstPushSrc * psrc, GstBuffer ** buf)
|
||||
GST_LOG_OBJECT (src, "create");
|
||||
|
||||
if (!src->is_started) {
|
||||
/* Query and re-validate AOI configuration before starting capture.
|
||||
* This is required when using AOI with Y offsets. */
|
||||
IS_RECT rectAOI;
|
||||
ret = is_AOI (src->hCam, IS_AOI_IMAGE_GET_AOI, (void *) &rectAOI,
|
||||
sizeof (rectAOI));
|
||||
if (ret == IS_SUCCESS) {
|
||||
ret = is_AOI (src->hCam, IS_AOI_IMAGE_SET_AOI, (void *) &rectAOI,
|
||||
sizeof (rectAOI));
|
||||
}
|
||||
|
||||
ret = is_CaptureVideo (src->hCam, IS_DONT_WAIT);
|
||||
if (ret != IS_SUCCESS) {
|
||||
GST_ELEMENT_ERROR (src, STREAM, WRONG_TYPE,
|
||||
("Failed to start video capture"), (NULL));
|
||||
("Failed to start video capture: %s", gst_idsueyesrc_get_error_string (src, ret)), (NULL));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user