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 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 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..9fa3fc2 --- /dev/null +++ b/demosaic_fixed.py @@ -0,0 +1,115 @@ +#!/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 + + # 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 = 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 = 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}") + +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 + output_dir = Path('captured_images') + bayer_8bit = (bayer_image >> 4).astype(np.uint8) + 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 + 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 baf6161..0000000 Binary files a/test_img_1.jpg and /dev/null differ 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