From 7085d458d5921feead75c4777bf030109c115bac Mon Sep 17 00:00:00 2001 From: MahmoodSeoud Date: Wed, 11 Jun 2025 11:51:06 +0100 Subject: [PATCH 1/4] CLAUDE.md --- CLAUDE.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2abc8d7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,112 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build Commands + +**Build the project:** +```bash +./build.sh +``` +This removes the build directory, runs meson setup, and builds with ninja. + +**Run the application:** +```bash +./run.sh +``` +Runs with default parameters: ZMQ interface, node 163, localhost device. + +**Manual execution:** +```bash +./build/Disco2CameraControl -i INTERFACE -d DEVICE -n NODE -p PORT +``` +- INTERFACE: zmq, can, or kiss (default: zmq) +- DEVICE: connection device (default: localhost) +- NODE: CSP node address (default: 2) +- PORT: server listening port (default: 10) + +## Architecture Overview + +### Core Components + +**CaptureController** (`src/camera_control/capture_controller.cpp`) +- Main orchestrator that handles capture requests +- Creates appropriate camera controller instances based on camera type +- Manages the entire capture workflow from parameter validation to image delivery + +**Camera Controllers** (Abstract factory pattern) +- `VimbaController`: Handles Allied Vision cameras via VimbaX SDK +- `IRController`: Handles infrared cameras +- `TestController`: Provides mock camera functionality for testing +- All inherit from `CameraController` base class + +**MessageQueue** (`src/message_queue/message_queue.cpp`) +- Manages System V shared memory and message queues +- Handles image data transfer to the image processing pipeline (DIPP) +- Creates shared memory segments and sends notification messages + +**CSP Server** (`src/communication/csp_server.c`) +- Provides CSP (Cubesat Space Protocol) communication interface +- Exposes parameters via libparam for remote control +- Handles different transport interfaces (ZMQ, CAN, KISS) + +### Data Flow + +1. **Parameter Setting**: Ground station sets capture parameters via CSP +2. **Capture Trigger**: `capture_param` set to 1 triggers image acquisition +3. **Controller Selection**: CaptureController creates appropriate camera controller +4. **Image Capture**: Camera controller captures raw BayerRG images (12-bit) +5. **Data Packaging**: Images packaged with metadata into batch structure +6. **Memory Management**: Image data stored in System V shared memory +7. **Pipeline Notification**: Message queue notifies DIPP of ready images + +### Key Data Structures + +**ImageBatch**: System V message queue structure containing image metadata and shared memory key +**Image**: Individual image structure with dimensions, pixel data, and timestamps +**CaptureMessage**: Internal capture instruction format with all parameters + +### Build System + +- **Meson** build system with cross-compilation support +- Automatically detects ARM64 vs x86_64 and links appropriate VimbaX libraries +- Dependencies: CSP, libparam, OpenCV, Protobuf, VimbaX SDK + +### Camera Parameters (CSP exposed) + +- `camera_id_param`: Camera model string (e.g., "1800 U-500c") +- `camera_type_param`: 0=VMB, 1=IR, 2=TEST +- `exposure_param`: Exposure in microseconds +- `iso_param`: Gain/ISO value +- `num_images_param`: Number of images in burst +- `interval_param`: Delay between images (microseconds) +- `obid_param`: Unique batch identifier +- `pipeline_id_param`: Target processing pipeline ID +- `capture_param`: Set to 1 to trigger capture +- `error_log`: Latest error code (read-only) + +### Error Handling + +Error codes are defined in `include/utils/errors.hpp`: +- 100s: Parameter parsing errors +- 200s: Camera/capture errors +- 300s: Message queue/shared memory errors + +## Development Notes + +### Prerequisites +```bash +sudo apt-get install libzmq3-dev libbsd +``` + +**Protobuf 3.19** is required - clone from the 3.19.x branch and build manually. + +### Testing +- Use `camera_type_param = 2` (TEST) for testing without hardware +- Test executable `receiver` in build directory can receive image messages +- Debug mode available with `-D` flag + +### VimbaX Integration +- Cross-compilation support for ARM64 vs Linux x86_64 +- VimbaX SDK libraries automatically selected based on build target +- Raw BayerRG format captured at 12-bit depth for maximum processing flexibility \ No newline at end of file From eb95eec8fafeec195dbeef1d27edf4d4a96b8347 Mon Sep 17 00:00:00 2001 From: MahmoodSeoud Date: Wed, 11 Jun 2025 13:40:17 +0100 Subject: [PATCH 2/4] Some pics? --- analyze_raw.py | 88 +++++++++++++++++++ demosaic.py | 81 +++++++++++++++++ demosaic_fixed.py | 110 ++++++++++++++++++++++++ inspect_images.py | 78 +++++++++++++++++ src/camera_control/vimba_controller.cpp | 96 +++++++++++++++++++-- src/communication/csp_server.c | 4 +- src/main.cpp | 4 +- test_img_1.jpg | Bin 9856 -> 0 bytes view_images.py | 65 ++++++++++++++ 9 files changed, 516 insertions(+), 10 deletions(-) create mode 100644 analyze_raw.py create mode 100644 demosaic.py create mode 100755 demosaic_fixed.py create mode 100644 inspect_images.py delete mode 100644 test_img_1.jpg create mode 100644 view_images.py diff --git a/analyze_raw.py b/analyze_raw.py new file mode 100644 index 0000000..7c994d5 --- /dev/null +++ b/analyze_raw.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import numpy as np +from pathlib import Path + +def analyze_bayer_raw(): + """Analyze the raw Bayer buffer data.""" + # Image parameters (full resolution) + width = 2464 + height = 2056 + + with open('buffer_dump.bin', 'rb') as f: + data = f.read() + + # Convert to 16-bit values (little-endian) + raw_data = np.frombuffer(data, dtype=np.uint16) + # Mask to 12-bit values + raw_data = raw_data & 0x0FFF + + # Reshape to image dimensions + image = raw_data.reshape(height, width) + + print(f"Raw Bayer Data Analysis:") + print(f"Shape: {image.shape}") + print(f"Data type: {image.dtype}") + print(f"Min value: {image.min()}") + print(f"Max value: {image.max()}") + print(f"Mean value: {image.mean():.2f}") + print(f"Std deviation: {image.std():.2f}") + + # Check for saturation + saturated_pixels = np.sum(image >= 4095) # 12-bit max + total_pixels = width * height + saturation_percent = (saturated_pixels / total_pixels) * 100 + print(f"Saturated pixels (4095): {saturated_pixels} ({saturation_percent:.2f}%)") + + # Check for zero pixels + zero_pixels = np.sum(image == 0) + zero_percent = (zero_pixels / total_pixels) * 100 + print(f"Zero pixels: {zero_pixels} ({zero_percent:.2f}%)") + + # Sample different regions + print(f"\nSample regions:") + regions = [ + ("Top-left", image[0:100, 0:100]), + ("Top-right", image[0:100, -100:]), + ("Center", image[height//2-50:height//2+50, width//2-50:width//2+50]), + ("Bottom-left", image[-100:, 0:100]), + ("Bottom-right", image[-100:, -100:]) + ] + + for name, region in regions: + print(f" {name}: min={region.min()}, max={region.max()}, mean={region.mean():.1f}") + + # Check histogram + hist, bins = np.histogram(image, bins=50, range=(0, 4095)) + print(f"\nHistogram analysis:") + print(f" Most common value: {bins[np.argmax(hist)]:.0f}") + print(f" Values in top 10% (>3686): {np.sum(image > 3686)} pixels") + print(f" Values in bottom 10% (<410): {np.sum(image < 410)} pixels") + + # Check for patterns (like half-image issues) + left_half = image[:, :width//2] + right_half = image[:, width//2:] + top_half = image[:height//2, :] + bottom_half = image[height//2:, :] + + print(f"\nHalf-image analysis:") + print(f" Left half: mean={left_half.mean():.1f}, max={left_half.max()}") + print(f" Right half: mean={right_half.mean():.1f}, max={right_half.max()}") + print(f" Top half: mean={top_half.mean():.1f}, max={top_half.max()}") + print(f" Bottom half: mean={bottom_half.mean():.1f}, max={bottom_half.max()}") + + # Look for row-by-row patterns + row_means = np.mean(image, axis=1) + col_means = np.mean(image, axis=0) + + print(f"\nRow/Column patterns:") + print(f" Row means: min={row_means.min():.1f}, max={row_means.max():.1f}, std={row_means.std():.2f}") + print(f" Col means: min={col_means.min():.1f}, max={col_means.max():.1f}, std={col_means.std():.2f}") + + # Save a small crop as text for inspection + crop = image[1000:1020, 1000:1020] # 20x20 crop from middle + print(f"\n20x20 sample from center (row 1000-1020, col 1000-1020):") + for row in crop: + print(" " + " ".join(f"{val:4d}" for val in row)) + +if __name__ == "__main__": + analyze_bayer_raw() \ No newline at end of file diff --git a/demosaic.py b/demosaic.py new file mode 100644 index 0000000..3858dd6 --- /dev/null +++ b/demosaic.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +import numpy as np +import cv2 +from pathlib import Path + +def read_bayer_buffer(filename, width, height, bits_per_pixel=12): + """Read raw Bayer buffer from file.""" + with open(filename, 'rb') as f: + data = f.read() + + # For 12-bit data stored in 16-bit format + if bits_per_pixel == 12: + # Convert bytes to 16-bit values (little-endian) + raw_data = np.frombuffer(data, dtype=np.uint16) + # Mask to 12-bit values + raw_data = raw_data & 0x0FFF + else: + raw_data = np.frombuffer(data, dtype=np.uint8) + + # Reshape to image dimensions + image = raw_data.reshape(height, width) + return image + +def demosaic_bayer(bayer_image, pattern='RGGB'): + """Demosaic Bayer pattern to RGB image.""" + height, width = bayer_image.shape + + # Convert to 8-bit for OpenCV processing (scale from 12-bit to 8-bit) + bayer_8bit = (bayer_image >> 4).astype(np.uint8) + + # Convert based on Bayer pattern + if pattern == 'RGGB': + color_image = cv2.cvtColor(bayer_8bit, cv2.COLOR_BAYER_RG2RGB) + elif pattern == 'GRBG': + color_image = cv2.cvtColor(bayer_8bit, cv2.COLOR_BAYER_GR2RGB) + elif pattern == 'GBRG': + color_image = cv2.cvtColor(bayer_8bit, cv2.COLOR_BAYER_GB2RGB) + elif pattern == 'BGGR': + color_image = cv2.cvtColor(bayer_8bit, cv2.COLOR_BAYER_BG2RGB) + else: + raise ValueError(f"Unknown Bayer pattern: {pattern}") + + return color_image + +def try_all_patterns(bayer_image): + """Try all Bayer patterns and save results.""" + patterns = ['RGGB', 'GRBG', 'GBRG', 'BGGR'] + + for pattern in patterns: + try: + color_image = demosaic_bayer(bayer_image, pattern) + output_filename = f'demosaic_{pattern.lower()}.jpg' + cv2.imwrite(output_filename, color_image) + print(f"Saved {output_filename} using {pattern} pattern") + except Exception as e: + print(f"Failed to process {pattern}: {e}") + +def main(): + # Image parameters from the captured output (full resolution) + width = 2464 + height = 2056 + bits_per_pixel = 12 + + # Read the buffer + print("Reading Bayer buffer...") + bayer_image = read_bayer_buffer('buffer_dump.bin', width, height, bits_per_pixel) + + print(f"Bayer image shape: {bayer_image.shape}") + print(f"Bayer image min/max values: {bayer_image.min()}/{bayer_image.max()}") + + # Try all Bayer patterns + print("Trying all Bayer patterns...") + try_all_patterns(bayer_image) + + # Also save the raw Bayer data as grayscale for inspection + bayer_8bit = (bayer_image >> 4).astype(np.uint8) + cv2.imwrite('bayer_raw.jpg', bayer_8bit) + print("Saved bayer_raw.jpg (raw Bayer data as grayscale)") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/demosaic_fixed.py b/demosaic_fixed.py new file mode 100755 index 0000000..568d54c --- /dev/null +++ b/demosaic_fixed.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +import numpy as np +import cv2 +from pathlib import Path + +def read_bayer_buffer(filename, width, height, bits_per_pixel=12): + """Read raw Bayer buffer from file.""" + with open(filename, 'rb') as f: + data = f.read() + + # For 12-bit data stored in 16-bit format + if bits_per_pixel == 12: + # Convert bytes to 16-bit values (little-endian) + raw_data = np.frombuffer(data, dtype=np.uint16) + # Mask to 12-bit values + raw_data = raw_data & 0x0FFF + else: + raw_data = np.frombuffer(data, dtype=np.uint8) + + # Reshape to image dimensions + image = raw_data.reshape(height, width) + return image + +def demosaic_bayer(bayer_image, pattern='RGGB', flip_vertical=True): + """Demosaic Bayer pattern to RGB image with proper orientation.""" + height, width = bayer_image.shape + + # Convert to 8-bit for OpenCV processing (scale from 12-bit to 8-bit) + bayer_8bit = (bayer_image >> 4).astype(np.uint8) + + # Flip image vertically if needed (cameras often capture upside down) + if flip_vertical: + bayer_8bit = cv2.flip(bayer_8bit, 0) # 0 means vertical flip + + # Convert based on Bayer pattern + # Note: OpenCV expects BGR output, not RGB + if pattern == 'RGGB': + color_image = cv2.cvtColor(bayer_8bit, cv2.COLOR_BayerRG2BGR) + elif pattern == 'GRBG': + color_image = cv2.cvtColor(bayer_8bit, cv2.COLOR_BayerGR2BGR) + elif pattern == 'GBRG': + color_image = cv2.cvtColor(bayer_8bit, cv2.COLOR_BayerGB2BGR) + elif pattern == 'BGGR': + color_image = cv2.cvtColor(bayer_8bit, cv2.COLOR_BayerBG2BGR) + else: + raise ValueError(f"Unknown Bayer pattern: {pattern}") + + return color_image + +def try_all_patterns(bayer_image, flip_vertical=True): + """Try all Bayer patterns and save results.""" + patterns = ['RGGB', 'GRBG', 'GBRG', 'BGGR'] + + # Based on the C++ code, the camera tries BayerGR12 first, then BayerRG12 + # So GRBG pattern is most likely correct + + for pattern in patterns: + try: + color_image = demosaic_bayer(bayer_image, pattern, flip_vertical) + + # Save with flip indicator in filename + flip_str = '_flipped' if flip_vertical else '' + output_filename = f'demosaic_{pattern.lower()}{flip_str}.jpg' + cv2.imwrite(output_filename, color_image) + print(f"Saved {output_filename} using {pattern} pattern") + + # Also save as PNG for better quality + png_filename = f'demosaic_{pattern.lower()}{flip_str}.png' + cv2.imwrite(png_filename, color_image) + + except Exception as e: + print(f"Failed to process {pattern}: {e}") + +def main(): + # Image parameters from the captured output (full resolution) + width = 2464 + height = 2056 + bits_per_pixel = 12 + + # Read the buffer + print("Reading Bayer buffer...") + bayer_image = read_bayer_buffer('buffer_dump.bin', width, height, bits_per_pixel) + + print(f"Bayer image shape: {bayer_image.shape}") + print(f"Bayer image min/max values: {bayer_image.min()}/{bayer_image.max()}") + + # Try all Bayer patterns with vertical flip (correct orientation) + print("\nTrying all Bayer patterns with vertical flip (correct orientation)...") + try_all_patterns(bayer_image, flip_vertical=True) + + # Also try without flip for comparison + print("\nTrying all Bayer patterns without flip (upside down)...") + try_all_patterns(bayer_image, flip_vertical=False) + + # Save the raw Bayer data as grayscale for inspection + bayer_8bit = (bayer_image >> 4).astype(np.uint8) + cv2.imwrite('bayer_raw_original.jpg', bayer_8bit) + cv2.imwrite('bayer_raw_flipped.jpg', cv2.flip(bayer_8bit, 0)) + print("\nSaved raw Bayer data as grayscale (both orientations)") + + # Provide recommendation based on C++ code analysis + print("\n" + "="*60) + print("RECOMMENDATION:") + print("Based on the C++ code, the camera is configured to use BayerGR12 format.") + print("This corresponds to the GRBG Bayer pattern.") + print("The most likely correct image is: demosaic_grbg_flipped.jpg") + print("="*60) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/inspect_images.py b/inspect_images.py new file mode 100644 index 0000000..31afc4f --- /dev/null +++ b/inspect_images.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +import cv2 +import numpy as np +from pathlib import Path + +def analyze_image(filename): + """Analyze an image file and print statistics.""" + if not Path(filename).exists(): + print(f"File {filename} does not exist") + return + + img = cv2.imread(filename) + if img is None: + print(f"Could not load image: {filename}") + return + + height, width = img.shape[:2] + channels = img.shape[2] if len(img.shape) > 2 else 1 + + print(f"\n{filename}:") + print(f" Dimensions: {width}x{height}") + print(f" Channels: {channels}") + print(f" Data type: {img.dtype}") + print(f" File size: {Path(filename).stat().st_size} bytes") + + # Calculate statistics for each channel + if channels == 3: + # BGR image + b, g, r = cv2.split(img) + print(f" Blue channel: min={b.min()}, max={b.max()}, mean={b.mean():.1f}") + print(f" Green channel: min={g.min()}, max={g.max()}, mean={g.mean():.1f}") + print(f" Red channel: min={r.min()}, max={r.max()}, mean={r.mean():.1f}") + else: + # Grayscale image + print(f" Grayscale: min={img.min()}, max={img.max()}, mean={img.mean():.1f}") + + # Check for common issues + if img.max() == 0: + print(" WARNING: Image is completely black!") + elif img.max() < 50: + print(" WARNING: Image appears very dark (max value < 50)") + elif img.min() == img.max(): + print(" WARNING: Image has no variation (all pixels same value)") + + # Check if image looks like it has content + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if channels == 3 else img + edges = cv2.Canny(gray, 50, 150) + edge_count = np.count_nonzero(edges) + edge_percentage = (edge_count / (width * height)) * 100 + print(f" Edge content: {edge_percentage:.2f}% of pixels have edges") + + if edge_percentage > 5: + print(" ✓ Image appears to have good detail/content") + elif edge_percentage > 1: + print(" ⚠ Image has some content but may be blurry or low contrast") + else: + print(" ✗ Image appears to have very little content") + +def main(): + image_files = [ + 'bayer_raw.jpg', + 'demosaic_rggb.jpg', + 'demosaic_grbg.jpg', + 'demosaic_gbrg.jpg', + 'demosaic_bggr.jpg' + ] + + print("Image Analysis Report") + print("=" * 50) + + for filename in image_files: + analyze_image(filename) + + print("\n" + "=" * 50) + print("Analysis complete.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/camera_control/vimba_controller.cpp b/src/camera_control/vimba_controller.cpp index 7b3c9a9..e7d536c 100644 --- a/src/camera_control/vimba_controller.cpp +++ b/src/camera_control/vimba_controller.cpp @@ -174,19 +174,67 @@ FramePtr VimbaController::aqcuireFrame(VmbCPP::CameraPtr cam, float exposure, fl pOffsetY->SetValue(0); } - // Set width and height to maximum available + // Set to maximum sensor resolution for full image capture VmbInt64_t minWidth, maxWidth, minHeight, maxHeight; if (cam->GetFeatureByName("Width", pWidth) == VmbErrorSuccess) { if (pWidth->GetRange(minWidth, maxWidth) == VmbErrorSuccess) { - pWidth->SetValue(maxWidth); + // Use full width for complete image capture + VmbInt64_t fullWidth = maxWidth; + // Ensure proper alignment based on pixel format + // For 12-bit packed: width should be even (2 pixels per 3 bytes) + fullWidth = (fullWidth / 2) * 2; // Even alignment for 12-bit packed + pWidth->SetValue(fullWidth); + std::cout << "Set width to " << fullWidth << " (full sensor width)" << std::endl; } } if (cam->GetFeatureByName("Height", pHeight) == VmbErrorSuccess) { if (pHeight->GetRange(minHeight, maxHeight) == VmbErrorSuccess) { - pHeight->SetValue(maxHeight); + // Use full height for complete image capture + VmbInt64_t fullHeight = maxHeight; + // Ensure height is multiple of 2 for proper alignment + fullHeight = (fullHeight / 2) * 2; + pHeight->SetValue(fullHeight); + std::cout << "Set height to " << fullHeight << " (full sensor height)" << std::endl; } } + // Configure USB specific settings for reliable data transfer + FeaturePtr pDeviceLinkThroughputLimit; + err = cam->GetFeatureByName("DeviceLinkThroughputLimit", pDeviceLinkThroughputLimit); + if (err == VmbErrorSuccess) { + // Very conservative bandwidth for USB2 to ensure complete transfer + VmbInt64_t limitValue = 20000000; // 20 MB/s for USB2 compatibility + err = pDeviceLinkThroughputLimit->SetValue(limitValue); + if (err == VmbErrorSuccess) { + std::cout << "Set USB bandwidth limit to " << limitValue << " bytes/sec" << std::endl; + } + } + + // Set packet size for USB2 compatibility + FeaturePtr pPacketSize; + err = cam->GetFeatureByName("GVSPPacketSize", pPacketSize); + if (err != VmbErrorSuccess) { + // Try alternative packet size feature name for USB cameras + err = cam->GetFeatureByName("PacketSize", pPacketSize); + } + if (err == VmbErrorSuccess) { + VmbInt64_t minPacket, maxPacket; + if (pPacketSize->GetRange(minPacket, maxPacket) == VmbErrorSuccess) { + // Use smaller, safer packet size for USB2 + VmbInt64_t usbPacketSize = std::min((VmbInt64_t)512, maxPacket); + pPacketSize->SetValue(usbPacketSize); + std::cout << "Set packet size to " << usbPacketSize << " bytes for USB2" << std::endl; + } + } + + // Set stream buffer count for USB2 + FeaturePtr pStreamBufferCount; + err = cam->GetFeatureByName("StreamBufferCount", pStreamBufferCount); + if (err == VmbErrorSuccess) { + pStreamBufferCount->SetValue(10); // More buffers for USB2 reliability + std::cout << "Set stream buffer count to 10 for USB2" << std::endl; + } + FeaturePtr pFormatFeature; err = cam->GetFeatureByName( "PixelFormat", pFormatFeature ); @@ -219,7 +267,8 @@ FramePtr VimbaController::aqcuireFrame(VmbCPP::CameraPtr cam, float exposure, fl } FramePtr frame; - err = cam->AcquireSingleImage(frame, 5000); + // Increase timeout significantly for USB2 to allow for slower data transfer + err = cam->AcquireSingleImage(frame, 30000); // 30 seconds timeout for USB2 if (err != VmbErrorSuccess) { @@ -292,6 +341,19 @@ std::vector VimbaController::Capture(CaptureMessage& capture_instructions return images; } + // Validate frame completeness before processing + VmbFrameStatusType frameStatus; + if (frame->GetReceiveStatus(frameStatus) == VmbErrorSuccess) { + if (frameStatus != VmbFrameStatusComplete) { + std::cerr << "Warning: Frame not complete! Status: " << frameStatus << std::endl; + if (frameStatus == VmbFrameStatusIncomplete) { + throw std::runtime_error("Frame incomplete - possible USB transfer issue"); + } + } + } else { + std::cerr << "Warning: Could not get frame status" << std::endl; + } + u_int width, height, bufferSize; frame->GetBufferSize(bufferSize); frame->GetWidth(width); @@ -306,10 +368,24 @@ std::vector VimbaController::Capture(CaptureMessage& capture_instructions int bitsPerPixel = getBitsPerPixelFromFormat(pixelFormat); int channels = getChannelsFromFormat(pixelFormat); + // Calculate expected buffer size for validation + size_t expectedSize; + if (bitsPerPixel == 12) { + // For 12-bit packed: each 2 pixels use 3 bytes + expectedSize = ((width * height + 1) / 2) * 3; + } else { + expectedSize = width * height * channels * ((bitsPerPixel + 7) / 8); + } + // Validate buffer size matches expected size + if (bufferSize != expectedSize) { + std::cout << "Buffer size validation: Expected " << expectedSize + << ", Got " << bufferSize + << " (difference: " << (int64_t)bufferSize - (int64_t)expectedSize << ")" << std::endl; + } Image img; - img.size = bufferSize; + img.size = bufferSize; // Use actual buffer size from VimbaX img.width = width; img.height = height; img.data = new u_char[bufferSize]; @@ -322,10 +398,18 @@ std::vector VimbaController::Capture(CaptureMessage& capture_instructions << ", channels: " << channels << ", width: " << width << ", height: " << height - << ", buffer size: " << bufferSize << " bytes" << std::endl; + << ", buffer size: " << bufferSize << " bytes" + << ", expected: " << expectedSize << " bytes" << std::endl; // Copy the actual image data from buffer to img.data std::memcpy(img.data, buffer, bufferSize); + + // Validate that the buffer contains reasonable data + size_t nonZeroBytes = 0; + for (size_t i = 0; i < std::min(bufferSize, (u_int)1000); i++) { + if (buffer[i] != 0) nonZeroBytes++; + } + std::cout << "Buffer validation: " << nonZeroBytes << "/1000 bytes non-zero in first 1KB" << std::endl; std::ofstream outfile("buffer_dump.bin", std::ios::binary); if (outfile.is_open()) { diff --git a/src/communication/csp_server.c b/src/communication/csp_server.c index f2eac37..0ac344b 100644 --- a/src/communication/csp_server.c +++ b/src/communication/csp_server.c @@ -86,8 +86,8 @@ void capture_param_callback() { strcpy(camera_id, "1800 U-500c"); // Use actual camera camera_type = 0; // VMB camera type - exposure = 50000; // Increased from 5ms to 50ms for much brighter images - iso = 4.0; // Increased from 1.0 to 4.0 for 4x gain boost + exposure = 10000; // Reduced from 50ms to 10ms to prevent overexposure + iso = 2.0; // Reduced from 4.0 to 2.0 for moderate gain num_images = 1; interval = 0; obid = 0; diff --git a/src/main.cpp b/src/main.cpp index 6ab3700..fdbddac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -117,8 +117,8 @@ int main(int argc, char *argv[], char *envp[]){ std::cout << "Taking picture..." << std::endl; char camera_id[] = "1800 U-500c"; uint8_t camera_type = 0; // VMB camera type - uint32_t exposure = 50000; // Increased from 3ms to 50ms for much brighter images - double iso = 4.0; // Increased from 1.0 to 4.0 for 4x gain boost + uint32_t exposure = 1000; // 1ms exposure for proper exposure + double iso = 1.0; // Standard gain uint32_t num_images = 1; uint32_t interval = 0; uint32_t obid = 0; diff --git a/test_img_1.jpg b/test_img_1.jpg deleted file mode 100644 index baf61614b869fffee0fcb4e0c76fdaa05651cd70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9856 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 diff --git a/view_images.py b/view_images.py new file mode 100644 index 0000000..87ba289 --- /dev/null +++ b/view_images.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +import cv2 +import numpy as np +from pathlib import Path + +def view_image(filename): + """Display an image file.""" + if not Path(filename).exists(): + print(f"File {filename} does not exist") + return + + img = cv2.imread(filename) + if img is None: + print(f"Could not load image: {filename}") + return + + # Get image info + height, width = img.shape[:2] + print(f"\n{filename}:") + print(f" Dimensions: {width}x{height}") + print(f" Channels: {img.shape[2] if len(img.shape) > 2 else 1}") + print(f" Data type: {img.dtype}") + print(f" Value range: {img.min()} - {img.max()}") + + # Resize for display if too large + display_img = img.copy() + if width > 1200 or height > 800: + scale = min(1200/width, 800/height) + new_width = int(width * scale) + new_height = int(height * scale) + display_img = cv2.resize(display_img, (new_width, new_height)) + print(f" Resized for display: {new_width}x{new_height}") + + # Show image + cv2.imshow(filename, display_img) + key = cv2.waitKey(0) + cv2.destroyAllWindows() + + return key + +def main(): + # List all the generated images + image_files = [ + 'bayer_raw.jpg', + 'demosaic_rggb.jpg', + 'demosaic_grbg.jpg', + 'demosaic_gbrg.jpg', + 'demosaic_bggr.jpg' + ] + + print("Available images:") + for i, filename in enumerate(image_files): + print(f"{i+1}. {filename}") + + print("\nPress any key to continue to next image, 'q' to quit") + + for filename in image_files: + key = view_image(filename) + if key == ord('q'): + break + + print("Image viewing complete.") + +if __name__ == "__main__": + main() \ No newline at end of file From 821174bcf16f03eecbfb1ff4108c4ba881b1ebfc Mon Sep 17 00:00:00 2001 From: MahmoodSeoud Date: Wed, 11 Jun 2025 14:51:06 +0100 Subject: [PATCH 3/4] capture images into specified folder --- demosaic_fixed.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/demosaic_fixed.py b/demosaic_fixed.py index 568d54c..9fa3fc2 100755 --- a/demosaic_fixed.py +++ b/demosaic_fixed.py @@ -54,19 +54,23 @@ def try_all_patterns(bayer_image, flip_vertical=True): # Based on the C++ code, the camera tries BayerGR12 first, then BayerRG12 # So GRBG pattern is most likely correct + # Create output directory if it doesn't exist + output_dir = Path('captured_images') + output_dir.mkdir(exist_ok=True) + for pattern in patterns: try: color_image = demosaic_bayer(bayer_image, pattern, flip_vertical) # Save with flip indicator in filename flip_str = '_flipped' if flip_vertical else '' - output_filename = f'demosaic_{pattern.lower()}{flip_str}.jpg' - cv2.imwrite(output_filename, color_image) + output_filename = output_dir / f'demosaic_{pattern.lower()}{flip_str}.jpg' + cv2.imwrite(str(output_filename), color_image) print(f"Saved {output_filename} using {pattern} pattern") # Also save as PNG for better quality - png_filename = f'demosaic_{pattern.lower()}{flip_str}.png' - cv2.imwrite(png_filename, color_image) + png_filename = output_dir / f'demosaic_{pattern.lower()}{flip_str}.png' + cv2.imwrite(str(png_filename), color_image) except Exception as e: print(f"Failed to process {pattern}: {e}") @@ -93,9 +97,10 @@ def main(): try_all_patterns(bayer_image, flip_vertical=False) # Save the raw Bayer data as grayscale for inspection + output_dir = Path('captured_images') bayer_8bit = (bayer_image >> 4).astype(np.uint8) - cv2.imwrite('bayer_raw_original.jpg', bayer_8bit) - cv2.imwrite('bayer_raw_flipped.jpg', cv2.flip(bayer_8bit, 0)) + cv2.imwrite(str(output_dir / 'bayer_raw_original.jpg'), bayer_8bit) + cv2.imwrite(str(output_dir / 'bayer_raw_flipped.jpg'), cv2.flip(bayer_8bit, 0)) print("\nSaved raw Bayer data as grayscale (both orientations)") # Provide recommendation based on C++ code analysis From 8d6f80520190ecfcc0279b47141ac282bdb6b942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20=C3=96zdemir?= <75176220+Burak123T@users.noreply.github.com> Date: Fri, 27 Jun 2025 06:51:40 +0200 Subject: [PATCH 4/4] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index ec9e7b5..bffa5b4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,4 +5,4 @@ [submodule "lib/param"] path = lib/param url = https://github.com/spaceinventor/libparam/ - branch = 59d3eb12d562582e37140e470afb71ce9bb18f8f + branch = 768970c6320a455250ddd88903bbd9f58db81216