diff --git a/README.md b/README.md index 3c8931a..3cc6b9a 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,12 @@ gst-launch-1.0 idsueyesrc config-file=ini/whole-presacler64_autoexp-binningx2.in #### Send Single Line via UDP Extract and transmit one line from camera (daytime, 200fps): ```pwsh -gst-launch-1.0 idsueyesrc config-file=ini/100fps-10exp-2456x4pix-500top-cw-extragain.ini exposure=10 framerate=750 ! ` +gst-launch-1.0 idsueyesrc config-file=ini/2456x4pix-500top-cw-extragain.ini exposure=10 framerate=750 ! ` videocrop bottom=3 ! ` queue ! ` udpsink host=10.81.2.183 port=5000 ``` -now moving to +now moving to automatic exposure (see [gstintervalometer](gst\intervalometer\gstintervalometer.c)) ```pwsh uv run .\scripts\launch-ids.py ` --config .\ini\2456x4pix-500top-cw.ini ` @@ -44,6 +44,16 @@ uv run .\scripts\launch-ids.py ` --host 10.81.2.183 ``` +```pwsh + gst-launch-1.0 idsueyesrc config-file=ini/2456x4pix-500top-cw-extragain.ini exposure=0.85 framerate=750 gain=0 name=cam device-id=1 ! ` + intervalometer enabled=true camera-element=cam ` + ramp-rate=vslow ` + update-interval=1000 ` + gain-ma52 ` + log-file=timelapse.csv ! ` + videocrop bottom=3 ! queue ! udpsink host=10.81.2.183 port=5000 +``` + #### Receive and Display ```pwsh uv run .\scripts\recv_raw_rolling.py --display-fps 60 @@ -77,7 +87,7 @@ gst-launch-1.0 -v ` ``` moving to ```pwsh -uv run .\scripts\launch-ids.py --config .\ini\100fps-10exp-2456x4pix-500top-cw-extragain.ini --device-id 1 --framerate 750 --exposure 10 --host 10.81.2.183 --gain 52 +uv run .\scripts\launch-ids.py --config .\ini\2456x4pix-500top-cw-extragain.ini --device-id 1 --framerate 750 --exposure 10 --host 10.81.2.183 --gain 52 ``` ## WIP - Frame filtering based on column analysis diff --git a/build.ps1 b/build.ps1 index 35b5907..9108b70 100644 --- a/build.ps1 +++ b/build.ps1 @@ -99,9 +99,10 @@ if ($BuildType -eq 'IDSuEyeOnly') { $pluginPaths = @( @{ Name = "libgstidsueye.dll"; RelPath = "sys\idsueye\Release\libgstidsueye.dll" }, @{ Name = "libgstrollingsum.dll"; RelPath = "gst\rollingsum\Release\libgstrollingsum.dll" }, - @{ Name = "libgstintervalometer.dll"; RelPath = "gst\intervalometer\Release\libgstintervalometer.dll" } + @{ Name = "libgstintervalometer.dll"; RelPath = "gst\intervalometer\Release\libgstintervalometer.dll" }, + @{ Name = "libgstlinescan.dll"; RelPath = "gst\linescan\Release\libgstlinescan.dll" } ) - Write-Host "Building: All plugins (IDS uEye, Rolling Sum, Intervalometer)" -ForegroundColor Yellow + Write-Host "Building: All plugins (IDS uEye, Rolling Sum, Intervalometer, Linescan)" -ForegroundColor Yellow } Write-Host "" @@ -294,6 +295,7 @@ try { Write-Host " gst-inspect-1.0 idsueyesrc" -ForegroundColor White Write-Host " gst-inspect-1.0 rollingsum" -ForegroundColor White Write-Host " gst-inspect-1.0 intervalometer" -ForegroundColor White + Write-Host " gst-inspect-1.0 linescan" -ForegroundColor White Write-Host "" Write-Host "Example pipelines:" -ForegroundColor Yellow Write-Host " # Rolling sum detection:" -ForegroundColor Gray @@ -301,6 +303,9 @@ try { Write-Host "" Write-Host " # Auto-exposure time-lapse:" -ForegroundColor Gray Write-Host " gst-launch-1.0 idsueyesrc name=cam ! intervalometer enabled=true camera-element=cam exposure-min=0.85 exposure-max=1.24 gain-min=0 gain-max=52 ! autovideosink" -ForegroundColor White + Write-Host "" + Write-Host " # Line scan camera (horizontal):" -ForegroundColor Gray + Write-Host " gst-launch-1.0 idsueyesrc ! linescan direction=horizontal line-index=100 output-size=800 ! videoconvert ! autovideosink" -ForegroundColor White } Write-Host "" diff --git a/gst/CMakeLists.txt b/gst/CMakeLists.txt index 53f58be..b22603c 100644 --- a/gst/CMakeLists.txt +++ b/gst/CMakeLists.txt @@ -5,6 +5,7 @@ endif (OPENCV_FOUND) add_subdirectory (bayerutils) add_subdirectory (extractcolor) add_subdirectory (intervalometer) +add_subdirectory (linescan) if (ENABLE_KLV) add_subdirectory (klv) diff --git a/gst/linescan/CMakeLists.txt b/gst/linescan/CMakeLists.txt new file mode 100644 index 0000000..0a8632f --- /dev/null +++ b/gst/linescan/CMakeLists.txt @@ -0,0 +1,28 @@ +set (SOURCES + gstlinescan.c + ) + +set (HEADERS + gstlinescan.h) + +include_directories (AFTER + ${ORC_INCLUDE_DIR}) + +set (libname gstlinescan) + +add_library (${libname} MODULE + ${SOURCES} + ${HEADERS}) + +target_link_libraries (${libname} + ${ORC_LIBRARIES} + ${GLIB2_LIBRARIES} + ${GOBJECT_LIBRARIES} + ${GSTREAMER_LIBRARY} + ${GSTREAMER_BASE_LIBRARY} + ${GSTREAMER_VIDEO_LIBRARY}) + +if (WIN32) + install (FILES $ DESTINATION ${PDB_INSTALL_DIR} COMPONENT pdb OPTIONAL) +endif () +install(TARGETS ${libname} LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR}) \ No newline at end of file diff --git a/gst/linescan/README.md b/gst/linescan/README.md new file mode 100644 index 0000000..568c4cf --- /dev/null +++ b/gst/linescan/README.md @@ -0,0 +1,125 @@ +# GStreamer Linescan Plugin + +## Overview + +The `linescan` plugin simulates a line scan camera by extracting a single row or column of pixels from each input frame and stacking them to create a line scan image over time. This is particularly useful for analyzing fast-moving objects captured with high frame rate cameras (typically 200-750 fps). + +## Features + +- **Horizontal mode**: Extracts a single row from each frame and stacks them vertically +- **Vertical mode**: Extracts a single column from each frame and stacks them horizontally +- **Configurable line selection**: Choose which row/column to extract, or use the middle automatically +- **Configurable output size**: Set the number of lines to accumulate before wrapping around +- **Supports multiple pixel formats**: GRAY8, GRAY16_LE, GRAY16_BE, RGB, BGR, BGRA, RGBx + +## Properties + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `direction` | enum | horizontal | Direction to extract line: `horizontal` (row) or `vertical` (column) | +| `line-index` | int | -1 | Index of row/column to extract (-1 for middle of image) | +| `output-size` | int | 800 | Number of lines to accumulate (width for horizontal mode, height for vertical mode) | + +## Usage Examples + +### Basic Horizontal Line Scan (Extract Row 100) +```bash +gst-launch-1.0 videotestsrc ! \ + linescan direction=horizontal line-index=100 output-size=800 ! \ + videoconvert ! autovideosink +``` + +### Vertical Line Scan (Extract Middle Column) +```bash +gst-launch-1.0 videotestsrc ! \ + linescan direction=vertical line-index=-1 output-size=600 ! \ + videoconvert ! autovideosink +``` + +### High-Speed Camera Line Scan +```bash +gst-launch-1.0 idsueyesrc framerate=500 ! \ + videocrop bottom=3 ! \ + linescan direction=horizontal line-index=50 output-size=1000 ! \ + videoconvert ! autovideosink +``` + +### Save Line Scan to File +```bash +gst-launch-1.0 idsueyesrc config-file=config.ini framerate=200 ! \ + linescan direction=horizontal output-size=2000 ! \ + videoconvert ! \ + pngenc ! filesink location=linescan.png +``` + +## How It Works + +### Horizontal Mode (default) +1. Extracts one horizontal row from each input frame +2. The row is determined by `line-index` (or middle if -1) +3. Each extracted row is stacked vertically to build the output image +4. Output dimensions: `output-size` (width) × `input_height` (height) +5. When the buffer fills up (after `input_height` frames), it wraps around and starts overwriting from the top + +### Vertical Mode +1. Extracts one vertical column from each input frame +2. The column is determined by `line-index` (or middle if -1) +3. Each extracted column is stacked horizontally to build the output image +4. Output dimensions: `input_width` (width) × `output-size` (height) +5. When the buffer fills up (after `input_width` frames), it wraps around and starts overwriting from the left + +## Typical Use Cases + +1. **Conveyor Belt Inspection**: Capture a continuous image of objects moving on a conveyor belt +2. **High-Speed Object Analysis**: Analyze fast-moving objects frame-by-frame at high framerates +3. **Barcode/Text Reading**: Extract a horizontal line across moving barcodes or text +4. **Web Inspection**: Continuous inspection of paper, textile, or other web materials +5. **Sports Analysis**: Track trajectory or movement patterns of fast-moving objects + +## Pipeline Tips + +### For Best Results +- Use a high framerate camera (200-750 fps recommended) +- Ensure consistent object motion +- Adjust `line-index` to capture the region of interest +- Set `output-size` based on expected duration or object size +- Use `videocrop` before linescan if needed to reduce processing + +### Common Pipeline Patterns +```bash +# Pattern 1: Crop + Linescan + Display +camera ! videocrop ! linescan ! videoconvert ! autovideosink + +# Pattern 2: Linescan + Encode + Save +camera ! linescan ! videoconvert ! x264enc ! mp4mux ! filesink + +# Pattern 3: Linescan + Network Stream +camera ! linescan ! videoconvert ! jpegenc ! multipartmux ! tcpserversink +``` + +## Performance Considerations + +- The plugin maintains an internal buffer equal to the full output image size +- Memory usage = `output_width × output_height × bytes_per_pixel` +- Processing overhead is minimal (single memcpy per frame) +- No frame buffering - processes each frame immediately + +## Troubleshooting + +### "Could not link" errors +- Ensure videocrop or other upstream elements provide fixed caps +- Try adding `videoconvert` before linescan if needed + +### Line index out of range +- Check that `line-index` is less than input height (horizontal) or width (vertical) +- Use `-1` to automatically select the middle + +### Wrapping/Rolling effect +- This is normal when the buffer fills up +- Adjust `output-size` to match your capture duration needs + +## See Also + +- [intervalometer](../intervalometer/README.md) - Auto-exposure control +- [rollingsum](../rollingsum/README.md) - Moving window sum detection +- [GStreamer Documentation](https://gstreamer.freedesktop.org/documentation/) \ No newline at end of file diff --git a/gst/linescan/gstlinescan.c b/gst/linescan/gstlinescan.c new file mode 100644 index 0000000..a9713fc --- /dev/null +++ b/gst/linescan/gstlinescan.c @@ -0,0 +1,636 @@ +/* GStreamer + * Copyright (C) 2024 FIXME + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-linescan + * + * Line scan camera simulator that extracts one row or column of pixels + * from each input frame and stacks them to create a line scan image. + * + * Useful for analyzing fast-moving objects with high frame rate cameras + * (typically 200-750 fps). The user sets the output width, and the height + * is determined by the input resolution. + * + * + * Example launch line + * |[ + * gst-launch-1.0 videotestsrc ! linescan direction=horizontal line-index=100 output-size=800 ! videoconvert ! autovideosink + * ]| + * Extracts row 100 from each frame and creates an 800-pixel wide output + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstlinescan.h" +#include + +GST_DEBUG_CATEGORY_STATIC (gst_linescan_debug); +#define GST_CAT_DEFAULT gst_linescan_debug + +/* Properties */ +enum +{ + PROP_0, + PROP_DIRECTION, + PROP_LINE_INDEX, + PROP_OUTPUT_SIZE +}; + +#define DEFAULT_PROP_DIRECTION GST_LINESCAN_DIRECTION_HORIZONTAL +#define DEFAULT_PROP_LINE_INDEX -1 /* -1 means middle of image */ +#define DEFAULT_PROP_OUTPUT_SIZE 800 + +/* GStreamer boilerplate */ +#define gst_linescan_parent_class parent_class +G_DEFINE_TYPE (GstLinescan, gst_linescan, GST_TYPE_BASE_TRANSFORM); + +/* Define the enum type for direction */ +#define GST_TYPE_LINESCAN_DIRECTION (gst_linescan_direction_get_type ()) +static GType +gst_linescan_direction_get_type (void) +{ + static GType direction_type = 0; + static const GEnumValue direction_values[] = { + {GST_LINESCAN_DIRECTION_HORIZONTAL, "Extract horizontal row, stack vertically", "horizontal"}, + {GST_LINESCAN_DIRECTION_VERTICAL, "Extract vertical column, stack horizontally", "vertical"}, + {0, NULL, NULL} + }; + + if (!direction_type) { + direction_type = g_enum_register_static ("GstLinescanDirection", direction_values); + } + return direction_type; +} + +static GstStaticPadTemplate gst_linescan_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ GRAY8, GRAY16_LE, GRAY16_BE, RGB, BGR, BGRA, RGBx }")) + ); + +static GstStaticPadTemplate gst_linescan_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ GRAY8, GRAY16_LE, GRAY16_BE, RGB, BGR, BGRA, RGBx }")) + ); + +/* GObject vmethod declarations */ +static void gst_linescan_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_linescan_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_linescan_finalize (GObject * object); + +/* GstBaseTransform vmethod declarations */ +static gboolean gst_linescan_start (GstBaseTransform * trans); +static gboolean gst_linescan_stop (GstBaseTransform * trans); +static gboolean gst_linescan_set_caps (GstBaseTransform * trans, + GstCaps * incaps, GstCaps * outcaps); +static GstCaps * gst_linescan_transform_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * filter); +static GstCaps * gst_linescan_fixate_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * othercaps); +static gboolean gst_linescan_transform_size (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, gsize size, + GstCaps * othercaps, gsize * othersize); +static GstFlowReturn gst_linescan_transform (GstBaseTransform * trans, + GstBuffer * inbuf, GstBuffer * outbuf); + +/* Helper functions */ +static void gst_linescan_reset (GstLinescan * filter); +static gboolean gst_linescan_allocate_buffer (GstLinescan * filter); + +static void +gst_linescan_class_init (GstLinescanClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GstBaseTransformClass *gstbasetransform_class = + GST_BASE_TRANSFORM_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (gst_linescan_debug, "linescan", 0, + "Line Scan Camera Simulator"); + + /* Register GObject vmethods */ + gobject_class->set_property = gst_linescan_set_property; + gobject_class->get_property = gst_linescan_get_property; + gobject_class->finalize = gst_linescan_finalize; + + /* Install GObject properties */ + g_object_class_install_property (gobject_class, PROP_DIRECTION, + g_param_spec_enum ("direction", "Direction", + "Direction to extract line (horizontal=row, vertical=column)", + GST_TYPE_LINESCAN_DIRECTION, DEFAULT_PROP_DIRECTION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_LINE_INDEX, + g_param_spec_int ("line-index", "Line Index", + "Index of row/column to extract (-1 for middle)", -1, G_MAXINT, + DEFAULT_PROP_LINE_INDEX, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_OUTPUT_SIZE, + g_param_spec_int ("output-size", "Output Size", + "Number of lines to accumulate (width for horizontal, height for vertical)", + 1, G_MAXINT, DEFAULT_PROP_OUTPUT_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Set element metadata */ + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_linescan_sink_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_linescan_src_template)); + + gst_element_class_set_static_metadata (gstelement_class, + "Line Scan Camera", "Filter/Effect/Video", + "Extracts single row/column from frames and builds line scan image", + "FIXME "); + + /* Register GstBaseTransform vmethods */ + gstbasetransform_class->start = GST_DEBUG_FUNCPTR (gst_linescan_start); + gstbasetransform_class->stop = GST_DEBUG_FUNCPTR (gst_linescan_stop); + gstbasetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_linescan_set_caps); + gstbasetransform_class->transform_caps = GST_DEBUG_FUNCPTR (gst_linescan_transform_caps); + gstbasetransform_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_linescan_fixate_caps); + gstbasetransform_class->transform_size = GST_DEBUG_FUNCPTR (gst_linescan_transform_size); + gstbasetransform_class->transform = GST_DEBUG_FUNCPTR (gst_linescan_transform); +} + +static void +gst_linescan_init (GstLinescan * filter) +{ + /* Initialize properties */ + filter->direction = DEFAULT_PROP_DIRECTION; + filter->line_index = DEFAULT_PROP_LINE_INDEX; + filter->output_size = DEFAULT_PROP_OUTPUT_SIZE; + + /* Initialize internal state */ + filter->line_buffer = NULL; + filter->buffer_position = 0; + filter->line_size = 0; + filter->actual_line_index = 0; + filter->frame_count = 0; + filter->video_info_valid = FALSE; + filter->output_caps_set = FALSE; + + /* This is not an in-place transform */ + gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), FALSE); +} + +static void +gst_linescan_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstLinescan *filter = GST_LINESCAN (object); + + switch (prop_id) { + case PROP_DIRECTION: + filter->direction = g_value_get_enum (value); + gst_linescan_reset (filter); + break; + case PROP_LINE_INDEX: + filter->line_index = g_value_get_int (value); + break; + case PROP_OUTPUT_SIZE: + filter->output_size = g_value_get_int (value); + gst_linescan_reset (filter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_linescan_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstLinescan *filter = GST_LINESCAN (object); + + switch (prop_id) { + case PROP_DIRECTION: + g_value_set_enum (value, filter->direction); + break; + case PROP_LINE_INDEX: + g_value_set_int (value, filter->line_index); + break; + case PROP_OUTPUT_SIZE: + g_value_set_int (value, filter->output_size); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_linescan_finalize (GObject * object) +{ + GstLinescan *filter = GST_LINESCAN (object); + + if (filter->line_buffer) { + g_free (filter->line_buffer); + filter->line_buffer = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_linescan_start (GstBaseTransform * trans) +{ + GstLinescan *filter = GST_LINESCAN (trans); + + GST_DEBUG_OBJECT (filter, "start"); + + filter->frame_count = 0; + filter->buffer_position = 0; + + return TRUE; +} + +static gboolean +gst_linescan_stop (GstBaseTransform * trans) +{ + GstLinescan *filter = GST_LINESCAN (trans); + + GST_DEBUG_OBJECT (filter, "stop"); + + if (filter->line_buffer) { + g_free (filter->line_buffer); + filter->line_buffer = NULL; + } + + filter->video_info_valid = FALSE; + filter->output_caps_set = FALSE; + + return TRUE; +} + +static GstCaps * +gst_linescan_transform_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps) +{ + GstLinescan *filter = GST_LINESCAN (trans); + GstCaps *ret, *tmp; + GstStructure *structure; + gint i; + + GST_DEBUG_OBJECT (filter, "transform_caps (direction: %d) caps: %" GST_PTR_FORMAT, direction, caps); + + ret = gst_caps_new_empty (); + + for (i = 0; i < gst_caps_get_size (caps); i++) { + structure = gst_caps_get_structure (caps, i); + structure = gst_structure_copy (structure); + + if (direction == GST_PAD_SINK) { + /* Transform sink caps to src caps (input -> output) */ + if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) { + /* Horizontal: extract row (width=input_width), stack vertically (height=output_size) */ + /* Width stays same as input, height becomes output_size */ + gst_structure_set (structure, "height", G_TYPE_INT, filter->output_size, NULL); + } else { + /* Vertical: extract column (height=input_height), stack horizontally (width=output_size) */ + /* Height stays same as input, width becomes output_size */ + gst_structure_set (structure, "width", G_TYPE_INT, filter->output_size, NULL); + } + } else { + /* Transform src caps to sink caps (output -> input) */ + /* We need to be permissive here - input can be any size */ + if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) { + /* For horizontal: output height is fixed, so input height can vary */ + gst_structure_remove_field (structure, "height"); + gst_structure_set (structure, "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); + } else { + /* For vertical: output width is fixed, so input width can vary */ + gst_structure_remove_field (structure, "width"); + gst_structure_set (structure, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL); + } + } + + gst_caps_append_structure (ret, structure); + } + + /* Apply filter if provided */ + if (filter_caps) { + tmp = gst_caps_intersect_full (ret, filter_caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (ret); + ret = tmp; + } + + GST_DEBUG_OBJECT (filter, "transformed caps: %" GST_PTR_FORMAT, ret); + + return ret; +} + +static GstCaps * +gst_linescan_fixate_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * othercaps) +{ + GstLinescan *filter = GST_LINESCAN (trans); + GstStructure *structure; + GstCaps *result; + + GST_DEBUG_OBJECT (filter, "fixate_caps (direction: %d) caps: %" GST_PTR_FORMAT + " othercaps: %" GST_PTR_FORMAT, direction, caps, othercaps); + + result = gst_caps_make_writable (othercaps); + structure = gst_caps_get_structure (result, 0); + + if (direction == GST_PAD_SINK) { + /* Fixating output caps based on input */ + GstVideoInfo info; + + if (gst_video_info_from_caps (&info, caps)) { + if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) { + /* Horizontal: extract row, stack vertically */ + /* Width stays same as input, height becomes output_size */ + gst_structure_fixate_field_nearest_int (structure, "width", + GST_VIDEO_INFO_WIDTH (&info)); + gst_structure_fixate_field_nearest_int (structure, "height", filter->output_size); + } else { + /* Vertical: extract column, stack horizontally */ + /* Width becomes output_size, height stays same as input */ + gst_structure_fixate_field_nearest_int (structure, "width", filter->output_size); + gst_structure_fixate_field_nearest_int (structure, "height", + GST_VIDEO_INFO_HEIGHT (&info)); + } + } + } else { + /* Fixating input caps based on output - less constrained */ + /* Just fixate to something reasonable */ + gst_structure_fixate_field_nearest_int (structure, "width", 640); + gst_structure_fixate_field_nearest_int (structure, "height", 480); + } + + /* Fixate other fields using parent implementation */ + result = gst_caps_fixate (result); + + GST_DEBUG_OBJECT (filter, "fixated caps: %" GST_PTR_FORMAT, result); + + return result; +} + +static gboolean +gst_linescan_transform_size (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, gsize size, + GstCaps * othercaps, gsize * othersize) +{ + GstLinescan *filter = GST_LINESCAN (trans); + GstVideoInfo info; + + if (!gst_video_info_from_caps (&info, othercaps)) { + GST_ERROR_OBJECT (filter, "Failed to parse othercaps"); + return FALSE; + } + + *othersize = GST_VIDEO_INFO_SIZE (&info); + + GST_DEBUG_OBJECT (filter, "transform_size: %zu -> %zu", size, *othersize); + + return TRUE; +} + +static gboolean +gst_linescan_set_caps (GstBaseTransform * trans, GstCaps * incaps, + GstCaps * outcaps) +{ + GstLinescan *filter = GST_LINESCAN (trans); + + if (!gst_video_info_from_caps (&filter->video_info_in, incaps)) { + GST_ERROR_OBJECT (filter, "Failed to parse input caps"); + return FALSE; + } + + if (!gst_video_info_from_caps (&filter->video_info_out, outcaps)) { + GST_ERROR_OBJECT (filter, "Failed to parse output caps"); + return FALSE; + } + + filter->video_info_valid = TRUE; + filter->output_caps_set = TRUE; + + /* Calculate actual line index if set to -1 (middle) */ + if (filter->line_index < 0) { + if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) { + filter->actual_line_index = GST_VIDEO_INFO_HEIGHT (&filter->video_info_in) / 2; + } else { + filter->actual_line_index = GST_VIDEO_INFO_WIDTH (&filter->video_info_in) / 2; + } + } else { + filter->actual_line_index = filter->line_index; + } + + /* Allocate line buffer */ + if (!gst_linescan_allocate_buffer (filter)) { + GST_ERROR_OBJECT (filter, "Failed to allocate line buffer"); + return FALSE; + } + + GST_INFO_OBJECT (filter, "Set caps - Input: %dx%d, Output: %dx%d, Direction: %s, Line: %d", + GST_VIDEO_INFO_WIDTH (&filter->video_info_in), + GST_VIDEO_INFO_HEIGHT (&filter->video_info_in), + GST_VIDEO_INFO_WIDTH (&filter->video_info_out), + GST_VIDEO_INFO_HEIGHT (&filter->video_info_out), + (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) ? "HORIZONTAL" : "VERTICAL", + filter->actual_line_index); + + return TRUE; +} + +static gboolean +gst_linescan_allocate_buffer (GstLinescan * filter) +{ + gint output_width, output_height; + gsize pixel_stride; + + if (!filter->video_info_valid) { + return FALSE; + } + + /* Free old buffer if exists */ + if (filter->line_buffer) { + g_free (filter->line_buffer); + filter->line_buffer = NULL; + } + + output_width = GST_VIDEO_INFO_WIDTH (&filter->video_info_out); + output_height = GST_VIDEO_INFO_HEIGHT (&filter->video_info_out); + pixel_stride = GST_VIDEO_INFO_COMP_PSTRIDE (&filter->video_info_out, 0); + + /* Calculate line size based on direction */ + if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) { + /* Extracting horizontal row: line is output_width pixels wide */ + filter->line_size = output_width * pixel_stride; + } else { + /* Extracting vertical column: line is 1 pixel wide */ + filter->line_size = pixel_stride; + } + + /* Allocate buffer for entire output image */ + filter->line_buffer = g_malloc0 (GST_VIDEO_INFO_SIZE (&filter->video_info_out)); + if (!filter->line_buffer) { + GST_ERROR_OBJECT (filter, "Failed to allocate line buffer"); + return FALSE; + } + + filter->buffer_position = 0; + + GST_DEBUG_OBJECT (filter, "Allocated line buffer: %zu bytes, line size: %zu", + GST_VIDEO_INFO_SIZE (&filter->video_info_out), filter->line_size); + + return TRUE; +} + +static GstFlowReturn +gst_linescan_transform (GstBaseTransform * trans, GstBuffer * inbuf, + GstBuffer * outbuf) +{ + GstLinescan *filter = GST_LINESCAN (trans); + GstMapInfo map_in, map_out; + guint8 *src_line, *dest_line; + gint in_width, in_height, in_stride; + gint out_width, out_height, out_stride; + gint pixel_stride; + gint x, y; + + if (!filter->video_info_valid || !filter->line_buffer) { + GST_ERROR_OBJECT (filter, "Not properly initialized"); + return GST_FLOW_ERROR; + } + + if (!gst_buffer_map (inbuf, &map_in, GST_MAP_READ)) { + GST_ERROR_OBJECT (filter, "Failed to map input buffer"); + return GST_FLOW_ERROR; + } + + if (!gst_buffer_map (outbuf, &map_out, GST_MAP_WRITE)) { + GST_ERROR_OBJECT (filter, "Failed to map output buffer"); + gst_buffer_unmap (inbuf, &map_in); + return GST_FLOW_ERROR; + } + + in_width = GST_VIDEO_INFO_WIDTH (&filter->video_info_in); + in_height = GST_VIDEO_INFO_HEIGHT (&filter->video_info_in); + in_stride = GST_VIDEO_INFO_PLANE_STRIDE (&filter->video_info_in, 0); + + out_width = GST_VIDEO_INFO_WIDTH (&filter->video_info_out); + out_height = GST_VIDEO_INFO_HEIGHT (&filter->video_info_out); + out_stride = GST_VIDEO_INFO_PLANE_STRIDE (&filter->video_info_out, 0); + + pixel_stride = GST_VIDEO_INFO_COMP_PSTRIDE (&filter->video_info_in, 0); + + if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) { + /* Extract horizontal row */ + if (filter->actual_line_index >= in_height) { + GST_WARNING_OBJECT (filter, "Line index %d exceeds input height %d", + filter->actual_line_index, in_height); + gst_buffer_unmap (inbuf, &map_in); + gst_buffer_unmap (outbuf, &map_out); + return GST_FLOW_ERROR; + } + + /* Get pointer to the row we want to extract */ + src_line = map_in.data + (filter->actual_line_index * in_stride); + + /* Copy the row to the current position in our buffer */ + dest_line = filter->line_buffer + (filter->buffer_position * out_stride); + memcpy (dest_line, src_line, filter->line_size); + + } else { + /* Extract vertical column */ + if (filter->actual_line_index >= in_width) { + GST_WARNING_OBJECT (filter, "Line index %d exceeds input width %d", + filter->actual_line_index, in_width); + gst_buffer_unmap (inbuf, &map_in); + gst_buffer_unmap (outbuf, &map_out); + return GST_FLOW_ERROR; + } + + /* Extract column pixel by pixel and place horizontally in output */ + for (y = 0; y < in_height && y < out_height; y++) { + src_line = map_in.data + (y * in_stride) + (filter->actual_line_index * pixel_stride); + dest_line = filter->line_buffer + (y * out_stride) + (filter->buffer_position * pixel_stride); + memcpy (dest_line, src_line, pixel_stride); + } + } + + /* Increment buffer position */ + filter->buffer_position++; + + /* Wrap around when we reach the output size */ + if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) { + if (filter->buffer_position >= out_height) { + filter->buffer_position = 0; + } + } else { + if (filter->buffer_position >= out_width) { + filter->buffer_position = 0; + } + } + + /* Copy accumulated buffer to output */ + memcpy (map_out.data, filter->line_buffer, map_out.size); + + gst_buffer_unmap (inbuf, &map_in); + gst_buffer_unmap (outbuf, &map_out); + + filter->frame_count++; + + GST_LOG_OBJECT (filter, "Processed frame %lu, buffer position: %d", + (unsigned long) filter->frame_count, filter->buffer_position); + + return GST_FLOW_OK; +} + +static void +gst_linescan_reset (GstLinescan * filter) +{ + if (filter->line_buffer) { + g_free (filter->line_buffer); + filter->line_buffer = NULL; + } + + filter->buffer_position = 0; + filter->frame_count = 0; + + if (filter->video_info_valid) { + gst_linescan_allocate_buffer (filter); + } +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "linescan", GST_RANK_NONE, + GST_TYPE_LINESCAN); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + linescan, + "Line scan camera simulator that extracts rows/columns from frames", + plugin_init, GST_PACKAGE_VERSION, GST_PACKAGE_LICENSE, GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) \ No newline at end of file diff --git a/gst/linescan/gstlinescan.h b/gst/linescan/gstlinescan.h new file mode 100644 index 0000000..5a6e668 --- /dev/null +++ b/gst/linescan/gstlinescan.h @@ -0,0 +1,87 @@ +/* GStreamer + * Copyright (C) 2024 FIXME + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_LINESCAN_H__ +#define __GST_LINESCAN_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_LINESCAN \ + (gst_linescan_get_type()) +#define GST_LINESCAN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_LINESCAN,GstLinescan)) +#define GST_LINESCAN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_LINESCAN,GstLinescanClass)) +#define GST_IS_LINESCAN(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_LINESCAN)) +#define GST_IS_LINESCAN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_LINESCAN)) + +typedef struct _GstLinescan GstLinescan; +typedef struct _GstLinescanClass GstLinescanClass; + +typedef enum { + GST_LINESCAN_DIRECTION_HORIZONTAL = 0, /* Extract row from each frame, stack vertically (width=input, height=output_size) */ + GST_LINESCAN_DIRECTION_VERTICAL /* Extract column from each frame, stack horizontally (width=output_size, height=input) */ +} GstLinescanDirection; + +/** + * GstLinescan: + * @element: the parent element. + * + * Line scan plugin that extracts a single row or column from each frame + * and builds them into an output image over time. + */ +struct _GstLinescan +{ + GstBaseTransform element; + + /* Properties */ + GstLinescanDirection direction; /* HORIZONTAL or VERTICAL */ + gint line_index; /* Which row/column to extract (-1 = middle) */ + gint output_size; /* Number of lines to accumulate (width for vertical, height for horizontal) */ + + /* Video info */ + GstVideoInfo video_info_in; + GstVideoInfo video_info_out; + gboolean video_info_valid; + + /* Internal state */ + guint8 *line_buffer; /* Buffer to accumulate lines */ + gint buffer_position; /* Current position in buffer (how many lines accumulated) */ + gsize line_size; /* Size of one line in bytes */ + gint actual_line_index; /* Computed line index to extract */ + guint64 frame_count; /* Number of frames processed */ + + gboolean output_caps_set; /* Whether output caps have been negotiated */ +}; + +struct _GstLinescanClass +{ + GstBaseTransformClass parent_class; +}; + +GType gst_linescan_get_type(void); + +G_END_DECLS + +#endif /* __GST_LINESCAN_H__ */ \ No newline at end of file diff --git a/ini/roi-night.ini b/ini/roi-night.ini new file mode 100644 index 0000000..65bb792 --- /dev/null +++ b/ini/roi-night.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=524 +Start Y=450 +Start X absolute=1 +Start Y absolute=1 +Width=256 +Height=1008 +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=474 +Extended pixelclock range=0 +Framerate=169.724771 +Exposure=5.851568 +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=32 +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=5.851568 +Brightness Aoi Left=524 +Brightness Aoi Top=450 +Brightness Aoi Width=256 +Brightness Aoi Height=1008 +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=524 +Auto WB Aoi Top=450 +Auto WB Aoi Width=256 +Auto WB Aoi Height=1008 +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