diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..aea8a5f --- /dev/null +++ b/.clang-format @@ -0,0 +1,18 @@ +BasedOnStyle: Google +IncludeBlocks: Regroup +IndentPPDirectives: AfterHash +SortIncludes: true +IncludeCategories: + - Regex: '^' + Priority: 3 + SortPriority: 0 + - Regex: '^<.*\.h>' + Priority: 2 + SortPriority: 0 + - Regex: '^<.*' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 + diff --git a/bindings/src/pygstpylon.cpp b/bindings/src/pygstpylon.cpp index 681beb0..fa653ab 100644 --- a/bindings/src/pygstpylon.cpp +++ b/bindings/src/pygstpylon.cpp @@ -31,7 +31,7 @@ */ #ifdef HAVE_CONFIG_H -#include "config.h" +# include "config.h" #endif #include "bindaccessfunctions.h" diff --git a/ext/pylon/gstpylon.cpp b/ext/pylon/gstpylon.cpp index 9440fd3..418b939 100644 --- a/ext/pylon/gstpylon.cpp +++ b/ext/pylon/gstpylon.cpp @@ -36,6 +36,7 @@ #include "gst/pylon/gstpyloncache.h" #include "gst/pylon/gstpylondebug.h" +#include "gst/pylon/gstpylonformatmapping.h" #include "gst/pylon/gstpylonincludes.h" #include "gst/pylon/gstpylonmetaprivate.h" #include "gst/pylon/gstpylonobject.h" @@ -46,11 +47,11 @@ #include -/* Pixel format definitions */ -typedef struct { - std::string pfnc_name; - std::string gst_name; -} PixelFormatMappingType; +/* retry open camera limits in case of collision with other + * process + */ +constexpr int FAILED_OPEN_RETRY_COUNT = 30; +constexpr int FAILED_OPEN_RETRY_WAIT_TIME_MS = 1000; /* Mapping of GstStructure with its corresponding formats */ typedef struct { @@ -122,19 +123,6 @@ struct _GstPylon { gint requested_device_index; }; -static const std::vector pixel_format_mapping_raw = { - {"Mono8", "GRAY8"}, {"RGB8Packed", "RGB"}, - {"BGR8Packed", "BGR"}, {"RGB8", "RGB"}, - {"BGR8", "BGR"}, {"YCbCr422_8", "YUY2"}, - {"YUV422_8_UYVY", "UYVY"}, {"YUV422_8", "YUY2"}, - {"YUV422Packed", "UYVY"}, {"YUV422_YUYV_Packed", "YUY2"}}; - -static const std::vector pixel_format_mapping_bayer = { - {"BayerBG8", "bggr"}, - {"BayerGR8", "grbg"}, - {"BayerRG8", "rggb"}, - {"BayerGB8", "gbrg"}}; - static const std::vector gst_structure_formats = { {"video/x-raw", pixel_format_mapping_raw}, {"video/x-bayer", pixel_format_mapping_bayer}}; @@ -264,10 +252,34 @@ GstPylon *gst_pylon_new(GstElement *gstpylonsrc, const gchar *device_user_name, device_info = device_list.at(device_index); - self->camera->Attach(factory.CreateDevice(device_info)); + /* retry loop to start camera + * handles the cornercase of multiprocess pipelines started + * concurrently + */ + for (auto retry_idx = 0; retry_idx <= FAILED_OPEN_RETRY_COUNT; + retry_idx++) { + try { + self->camera->Attach(factory.CreateDevice(device_info)); + break; + } catch (GenICam::GenericException &e) { + GST_INFO_OBJECT(gstpylonsrc, "Failed to Open %s (%s)\n", + device_info.GetSerialNumber().c_str(), + e.GetDescription()); + /* wait for before new open attempt */ + g_usleep(FAILED_OPEN_RETRY_WAIT_TIME_MS * 1000); + } + } self->camera->Open(); - /* Set the camera to a valid state */ + /* Set the camera to a valid state + * close left open transactions on the device + */ + self->camera->DeviceFeaturePersistenceEnd.TryExecute(); + self->camera->DeviceRegistersStreamingEnd.TryExecute(); + + /* Set the camera to a valid state + * load the poweron user set + */ if (self->camera->UserSetSelector.IsWritable()) { std::string default_set = "Auto"; gst_pylon_apply_set(self, default_set); @@ -781,9 +793,11 @@ gboolean gst_pylon_set_configuration(GstPylon *self, const GstCaps *conf, Pylon::CIntegerParameter width(nodemap, "Width"); width.SetValue(gst_width, Pylon::IntegerValueCorrection_None); + GST_INFO("Set Feature Width: %d", gst_width); Pylon::CIntegerParameter height(nodemap, "Height"); height.SetValue(gst_height, Pylon::IntegerValueCorrection_None); + GST_INFO("Set Feature Height: %d", gst_height); Pylon::CBooleanParameter framerate_enable(nodemap, "AcquisitionFrameRateEnable"); @@ -792,13 +806,14 @@ gboolean gst_pylon_set_configuration(GstPylon *self, const GstCaps *conf, framerate_enable.TrySetValue(true); gdouble div = 1.0 * gst_numerator / gst_denominator; - if (self->camera->GetSfncVersion() >= Pylon::Sfnc_2_0_0) { Pylon::CFloatParameter framerate(nodemap, "AcquisitionFrameRate"); framerate.TrySetValue(div, Pylon::FloatValueCorrection_None); + GST_INFO("Set Feature AcquisitionFrameRate: %f", div); } else { Pylon::CFloatParameter framerate(nodemap, "AcquisitionFrameRateAbs"); framerate.TrySetValue(div, Pylon::FloatValueCorrection_None); + GST_INFO("Set Feature AcquisitionFrameRateAbs: %f", div); } } catch (const Pylon::GenericException &e) { @@ -894,6 +909,21 @@ static gchar *gst_pylon_get_string_properties( Pylon::CBaslerUniversalInstantCamera camera(factory.CreateDevice(device), Pylon::Cleanup_Delete); camera.Open(); + + /* Set the camera to a valid state + * close left open transactions on the device + */ + camera.DeviceFeaturePersistenceEnd.TryExecute(); + camera.DeviceRegistersStreamingEnd.TryExecute(); + + /* Set the camera to a valid state + * load the factory default set + */ + if (camera.UserSetSelector.IsWritable()) { + camera.UserSetSelector.SetValue("Default"); + camera.UserSetLoad.Execute(); + } + get_device_string_properties(&camera, &camera_properties, DEFAULT_ALIGNMENT); camera.Close(); diff --git a/gst-libs/gst/pylon/gstpylon-prelude.h b/gst-libs/gst/pylon/gstpylon-prelude.h index 8f14674..5da085c 100644 --- a/gst-libs/gst/pylon/gstpylon-prelude.h +++ b/gst-libs/gst/pylon/gstpylon-prelude.h @@ -46,7 +46,7 @@ #ifndef EXT_PYLONSRC_API # ifdef BUILDING_EXT_PYLONSRC -# define EXT_PYLONSRC_API GST_API_EXPORT /* from config.h */ +# define EXT_PYLONSRC_API GST_PYLON_API_EXPORT /* from config.h */ # else # define EXT_PYLONSRC_API GST_API_IMPORT # endif diff --git a/gst-libs/gst/pylon/gstpyloncache.cpp b/gst-libs/gst/pylon/gstpyloncache.cpp index 7db2cf9..37564ff 100644 --- a/gst-libs/gst/pylon/gstpyloncache.cpp +++ b/gst-libs/gst/pylon/gstpyloncache.cpp @@ -68,11 +68,16 @@ static std::string gst_pylon_cache_create_filepath( GstPylonCache::GstPylonCache(const std::string &name) : filepath(gst_pylon_cache_create_filepath(name)), feature_cache_dict(g_key_file_new()), - is_empty(TRUE) {} + is_modified(FALSE) { + /* load initial cache file */ + if (!LoadCacheFile()) { + GST_LOG("No feature cache file found"); + } +} GstPylonCache::~GstPylonCache() { g_key_file_free(this->feature_cache_dict); } -gboolean GstPylonCache::IsCacheValid() { +gboolean GstPylonCache::LoadCacheFile() { gboolean ret = TRUE; /* Check if file exists */ @@ -88,13 +93,10 @@ gboolean GstPylonCache::IsCacheValid() { if (!ret) { return FALSE; } - - this->is_empty = FALSE; - return TRUE; } -gboolean GstPylonCache::IsEmpty() { return this->is_empty; } +gboolean GstPylonCache::HasNewSettings() { return is_modified; } void GstPylonCache::CreateCacheFile() { GError *file_err = NULL; @@ -128,12 +130,14 @@ void GstPylonCache::SetIntegerAttribute(const char *feature, const char *attribute, const gint64 val) { g_key_file_set_int64(this->feature_cache_dict, feature, attribute, val); + is_modified = true; } void GstPylonCache::SetDoubleAttribute(const char *feature, const char *attribute, const gdouble val) { g_key_file_set_double(this->feature_cache_dict, feature, attribute, val); + is_modified = true; } bool GstPylonCache::GetIntegerAttribute(const char *feature, diff --git a/gst-libs/gst/pylon/gstpyloncache.h b/gst-libs/gst/pylon/gstpyloncache.h index 0fa7f0a..0b57859 100644 --- a/gst-libs/gst/pylon/gstpyloncache.h +++ b/gst-libs/gst/pylon/gstpyloncache.h @@ -41,8 +41,7 @@ class GST_PLUGIN_EXPORT GstPylonCache { public: GstPylonCache(const std::string &name); ~GstPylonCache(); - gboolean IsCacheValid(); - gboolean IsEmpty(); + gboolean HasNewSettings(); void SetIntProps(const gchar *feature_name, const gint64 min, const gint64 max, const GParamFlags flags); @@ -54,6 +53,8 @@ class GST_PLUGIN_EXPORT GstPylonCache { bool GetDoubleProps(const gchar *feature_name, gdouble &min, gdouble &max, GParamFlags &flags); + /* Load from file system */ + gboolean LoadCacheFile(); /* Persist cache to filesystem */ void CreateCacheFile(); @@ -70,7 +71,7 @@ class GST_PLUGIN_EXPORT GstPylonCache { std::string filepath; GKeyFile *feature_cache_dict; - gboolean is_empty; + gboolean is_modified; }; #endif diff --git a/gst-libs/gst/pylon/gstpylondebug.c b/gst-libs/gst/pylon/gstpylondebug.cpp similarity index 83% rename from gst-libs/gst/pylon/gstpylondebug.c rename to gst-libs/gst/pylon/gstpylondebug.cpp index 51ebc3b..e2c3da3 100644 --- a/gst-libs/gst/pylon/gstpylondebug.c +++ b/gst-libs/gst/pylon/gstpylondebug.cpp @@ -31,20 +31,18 @@ */ #ifdef HAVE_CONFIG_H -#include "config.h" +# include "config.h" #endif #include "gstpylondebug.h" -GST_DEBUG_CATEGORY (gst_pylon_debug); +GST_DEBUG_CATEGORY(gst_pylon_debug); -void -gst_pylon_debug_init (void) -{ - if (g_once_init_enter (&gst_pylon_debug)) { - GST_DEBUG_CATEGORY (cat_done); - GST_DEBUG_CATEGORY_INIT (cat_done, "pylonsrc", 0, - "debug category for pylonsrc element"); - g_once_init_leave (&gst_pylon_debug, cat_done); +void gst_pylon_debug_init(void) { + if (g_once_init_enter(&gst_pylon_debug)) { + GST_DEBUG_CATEGORY(cat_done); + GST_DEBUG_CATEGORY_INIT(cat_done, "pylonsrc", 0, + "debug category for pylonsrc element"); + g_once_init_leave(&gst_pylon_debug, cat_done); } } diff --git a/gst-libs/gst/pylon/gstpylonfeaturewalker.cpp b/gst-libs/gst/pylon/gstpylonfeaturewalker.cpp index a7e0a6f..aa6fb50 100644 --- a/gst-libs/gst/pylon/gstpylonfeaturewalker.cpp +++ b/gst-libs/gst/pylon/gstpylonfeaturewalker.cpp @@ -36,7 +36,7 @@ #include "gstpylondebug.h" #include "gstpylonfeaturewalker.h" -#include "gstpylonintrospection.h" +#include "gstpylonparamfactory.h" #include @@ -46,16 +46,19 @@ #define MAX_INT_SELECTOR_ENTRIES 16 /* prototypes */ -static std::vector gst_pylon_get_enum_entries( +std::vector gst_pylon_get_enum_entries( GenApi::IEnumeration* enum_node); -static std::vector gst_pylon_get_int_entries( - GenApi::IInteger* int_node); -static std::vector gst_pylon_camera_handle_node( +std::vector gst_pylon_get_int_entries(GenApi::IInteger* int_node); +std::vector gst_pylon_camera_handle_node( GenApi::INode* node, GenApi::INodeMap& nodemap, const std::string& device_fullname, GstPylonCache& feature_cache); -static void gst_pylon_camera_install_specs( - const std::vector& specs_list, GObjectClass* oclass, - gint& nprop); +void gst_pylon_camera_install_specs(const std::vector& specs_list, + GObjectClass* oclass, gint& nprop); +std::vector gst_pylon_camera_handle_node( + GenApi::INode* node, GstPylonParamFactory& param_factory); +bool is_unsupported_feature(const std::string& feature_name); +bool is_unsupported_category(const std::string& category_name); +bool is_unsupported_selector(const std::string& feature_name); static const std::unordered_set propfilter_set = { "Width", @@ -64,53 +67,49 @@ static const std::unordered_set propfilter_set = { "AcquisitionFrameRateEnable", "AcquisitionFrameRate", "AcquisitionFrameRateAbs", - "ChunkData", "AcquisitionStart", "AcquisitionStop", "UserSetLoad", "UserSetSave", "TriggerSoftware", "DeviceReset", - "FileAccessControl", + "DeviceFeaturePersistenceStart", + "DeviceFeaturePersistenceEnd", "DeviceRegistersStreamingStart", "DeviceRegistersStreamingEnd", - "FileAccessControl" /* has to be implemented in access library */ - "EventControl", /* disable full event section until mapped to gst - events/msgs */ - "SequencerControl" /* sequencer control relies on cmd feature */ }; static const std::unordered_set categoryfilter_set = { "ChunkData", - "FileAccessControl" /* has to be implemented in access library */ - "EventControl", /* disable full event section until mapped to gst - events/msgs */ - "SequencerControl", /* sequencer control relies on cmd feature */ - "MultipleROI" /* workaround skip to avoid issues with ace2/dart2 + "FileAccessControl", /* has to be implemented in access library */ + "EventControl", /* disable full event section until mapped to gst + events/msgs */ + "SequencerControl", /* sequencer control relies on cmd feature */ + "MultipleROI", /* workaround skip to avoid issues with ace2/dart2 FIXME: this has to be fixed in feature walker */ }; /* filter for features that are not supported */ -static bool is_unsupported_feature(const std::string& feature_name) { +bool is_unsupported_feature(const std::string& feature_name) { return propfilter_set.find(feature_name) != propfilter_set.end(); } /* filter for categories that are not supported */ -static bool is_unsupported_category(const std::string& category_name) { +bool is_unsupported_category(const std::string& category_name) { return categoryfilter_set.find(category_name) != categoryfilter_set.end(); } /* filter for selector nodes */ -static std::unordered_set selectorfilter_set = { +std::unordered_set selectorfilter_set = { "DeviceLinkSelector", }; /* filter for selectors and categories that are supported */ -static bool is_unsupported_selector(const std::string& feature_name) { +bool is_unsupported_selector(const std::string& feature_name) { return selectorfilter_set.find(feature_name) != selectorfilter_set.end(); } -static std::vector gst_pylon_get_enum_entries( +std::vector gst_pylon_get_enum_entries( GenApi::IEnumeration* enum_node) { GenApi::NodeList_t enum_entries; std::vector entry_names; @@ -138,8 +137,7 @@ static std::vector gst_pylon_get_enum_entries( return entry_names; } -static std::vector gst_pylon_get_int_entries( - GenApi::IInteger* int_node) { +std::vector gst_pylon_get_int_entries(GenApi::IInteger* int_node) { std::vector entry_names; g_return_val_if_fail(int_node, entry_names); @@ -226,9 +224,8 @@ std::vector GstPylonFeatureWalker::process_selector_features( return enum_values; } -static std::vector gst_pylon_camera_handle_node( - GenApi::INode* node, GenApi::INodeMap& nodemap, - const std::string& device_fullname, GstPylonCache& feature_cache) { +std::vector gst_pylon_camera_handle_node( + GenApi::INode* node, GstPylonParamFactory& param_factory) { GenApi::INode* selector_node = NULL; gint64 selector_value = 0; std::vector specs_list; @@ -261,22 +258,20 @@ static std::vector gst_pylon_camera_handle_node( } } - specs_list.push_back(GstPylonParamFactory::make_param( - nodemap, node, selector_node, selector_value, device_fullname, - feature_cache)); + specs_list.push_back( + param_factory.make_param(node, selector_node, selector_value)); } catch (const Pylon::GenericException& e) { - GST_FIXME("Unable to fully install property '%s-%s' on device \"%s\": %s", + GST_DEBUG("Unable to fully install property '%s-%s' : %s", node->GetName().c_str(), enum_value.c_str(), - device_fullname.c_str(), e.GetDescription()); + e.GetDescription()); } } return specs_list; } -static void gst_pylon_camera_install_specs( - const std::vector& specs_list, GObjectClass* oclass, - gint& nprop) { +void gst_pylon_camera_install_specs(const std::vector& specs_list, + GObjectClass* oclass, gint& nprop) { g_return_if_fail(oclass); if (!specs_list.empty()) { @@ -300,7 +295,15 @@ void GstPylonFeatureWalker::install_properties( const std::string& device_fullname, GstPylonCache& feature_cache) { g_return_if_fail(oclass); - gboolean is_cache_valid = feature_cache.IsCacheValid(); + /* handle filter for debugging */ + const char* single_feature = NULL; + if (const char* env_p = std::getenv("PYLONSRC_SINGLE_FEATURE")) { + GST_DEBUG("LIMIT to use only feature %s\n", env_p); + single_feature = env_p; + } + + auto param_factory = + GstPylonParamFactory(nodemap, device_fullname, feature_cache); gint nprop = 1; GenApi::INode* root_node = nodemap.GetNode("Root"); @@ -315,29 +318,36 @@ void GstPylonFeatureWalker::install_properties( /* Only handle real features that are not in the filter set, are not * selectors and are available */ auto sel_node = dynamic_cast(node); - if (node->IsFeature() && (node->GetVisibility() != GenApi::Invisible) && + auto category_node = dynamic_cast(node); + if (!category_node && node->IsFeature() && + (node->GetVisibility() != GenApi::Invisible) && GenApi::IsImplemented(node) && !is_unsupported_feature(std::string(node->GetName())) && node->GetPrincipalInterfaceType() != GenApi::intfICategory && + node->GetPrincipalInterfaceType() != GenApi::intfICommand && + node->GetPrincipalInterfaceType() != GenApi::intfIRegister && sel_node && !sel_node->IsSelector()) { GenICam::gcstring value; GenICam::gcstring attrib; try { - std::vector specs_list = gst_pylon_camera_handle_node( - node, nodemap, device_fullname, feature_cache); - - gst_pylon_camera_install_specs(specs_list, oclass, nprop); - + if (!single_feature || + (single_feature && std::string(node->GetName().c_str()) == + std::string(single_feature))) { + GST_DEBUG("Install node %s", node->GetName().c_str()); + std::vector specs_list = + gst_pylon_camera_handle_node(node, param_factory); + + gst_pylon_camera_install_specs(specs_list, oclass, nprop); + } } catch (const Pylon::GenericException& e) { - GST_FIXME("Unable to install property \"%s\" on device \"%s\": %s", + GST_DEBUG("Unable to install property \"%s\" on device \"%s\": %s", node->GetName().c_str(), device_fullname.c_str(), e.GetDescription()); } } /* Walk down all categories */ - auto category_node = dynamic_cast(node); if (category_node && !is_unsupported_category(std::string(node->GetName()))) { GenApi::FeatureList_t features; @@ -348,7 +358,7 @@ void GstPylonFeatureWalker::install_properties( } } - if (!is_cache_valid) { + if (feature_cache.HasNewSettings()) { try { feature_cache.CreateCacheFile(); } catch (const Pylon::GenericException& e) { diff --git a/gst-libs/gst/pylon/gstpylonformatmapping.h b/gst-libs/gst/pylon/gstpylonformatmapping.h new file mode 100644 index 0000000..9f11c77 --- /dev/null +++ b/gst-libs/gst/pylon/gstpylonformatmapping.h @@ -0,0 +1,79 @@ +/* Copyright (C) 2023 Basler AG + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _GST_PYLON_FORMAT_MAPPING_ +#define _GST_PYLON_FORMAT_MAPPING_ + +#include +#include + +bool isSupportedPylonFormat(const std::string &format); + +/* Pixel format definitions */ +typedef struct { + std::string pfnc_name; + std::string gst_name; +} PixelFormatMappingType; + +const std::vector pixel_format_mapping_raw = { + {"Mono8", "GRAY8"}, {"RGB8Packed", "RGB"}, + {"BGR8Packed", "BGR"}, {"RGB8", "RGB"}, + {"BGR8", "BGR"}, {"YCbCr422_8", "YUY2"}, + {"YUV422_8_UYVY", "UYVY"}, {"YUV422_8", "YUY2"}, + {"YUV422Packed", "UYVY"}, {"YUV422_YUYV_Packed", "YUY2"}}; + +const std::vector pixel_format_mapping_bayer = { + {"BayerBG8", "bggr"}, + {"BayerGR8", "grbg"}, + {"BayerRG8", "rggb"}, + {"BayerGB8", "gbrg"}}; + +bool isSupportedPylonFormat(const std::string &format) { + bool res = false; + for (const auto &fd : pixel_format_mapping_raw) { + if (fd.pfnc_name == format) { + res = true; + break; + } + } + if (!res) { + for (const auto &fd : pixel_format_mapping_bayer) { + if (fd.pfnc_name == format) { + res = true; + break; + } + } + } + return res; +} + +#endif diff --git a/gst-libs/gst/pylon/gstpylonintrospection.cpp b/gst-libs/gst/pylon/gstpylonintrospection.cpp index 4e96e46..31e1591 100644 --- a/gst-libs/gst/pylon/gstpylonintrospection.cpp +++ b/gst-libs/gst/pylon/gstpylonintrospection.cpp @@ -35,6 +35,7 @@ #endif #include "gstpylondebug.h" +#include "gstpylonformatmapping.h" #include "gstpylonintrospection.h" #include "gstpylonobject.h" #include "gstpylonparamspecs.h" @@ -42,6 +43,7 @@ #include #include +#include #include #include #include @@ -68,88 +70,43 @@ class GstPylonTypeAction : public GstPylonActions { }; /* prototypes */ -static GParamSpec *gst_pylon_make_spec_int64(GenApi::INodeMap &nodemap, - GenApi::INode *node, - GstPylonCache &feature_cache); -static GParamSpec *gst_pylon_make_spec_selector_int64( - GenApi::INodeMap &nodemap, GenApi::INode *node, GenApi::INode *selector, - guint64 selector_value, GstPylonCache &feature_cache); -static GParamSpec *gst_pylon_make_spec_bool(GenApi::INodeMap &nodemap, - GenApi::INode *node); -static GParamSpec *gst_pylon_make_spec_selector_bool(GenApi::INodeMap &nodemap, - GenApi::INode *node, - GenApi::INode *selector, - guint64 selector_value); -static GParamSpec *gst_pylon_make_spec_double(GenApi::INodeMap &nodemap, - GenApi::INode *node, - GstPylonCache &feature_cache); -static GParamSpec *gst_pylon_make_spec_selector_double( - GenApi::INodeMap &nodemap, GenApi::INode *node, GenApi::INode *selector, - guint64 selector_value, GstPylonCache &feature_cache); -static GParamSpec *gst_pylon_make_spec_str(GenApi::INodeMap &nodemap, - GenApi::INode *node); -static GParamSpec *gst_pylon_make_spec_selector_str(GenApi::INodeMap &nodemap, - GenApi::INode *node, - GenApi::INode *selector, - guint64 selector_value); -static GType gst_pylon_make_enum_type(GenApi::INodeMap &nodemap, - GenApi::INode *node, - const std::string &device_fullname); -static GParamSpec *gst_pylon_make_spec_enum(GenApi::INodeMap &nodemap, - GenApi::INode *node, - const std::string &device_fullname); -static GParamSpec *gst_pylon_make_spec_selector_enum( - GenApi::INodeMap &nodemap, GenApi::INode *node, GenApi::INode *selector, - guint64 selector_value, const std::string &device_fullname); -static GenApi::INode *gst_pylon_find_limit_node(GenApi::INode *feature_node, - const GenICam::gcstring &limit); +GenApi::INode *gst_pylon_find_limit_node(GenApi::INode *feature_node, + const GenICam::gcstring &limit); static std::vector gst_pylon_find_parent_features( GenApi::INode *feature_node); -static void gst_pylon_add_all_property_values( +void gst_pylon_add_all_property_values( GenApi::INode *feature_node, std::string value, std::unordered_map &invalidators); -static std::vector gst_pylon_get_available_features( +std::vector gst_pylon_get_available_features( const std::set &feature_list); template -static std::vector> gst_pylon_cartesian_product( +std::vector> gst_pylon_cartesian_product( std::vector> &v); template -static T gst_pylon_check_for_feature_invalidators( +T gst_pylon_check_for_feature_invalidators( GenApi::INode *feature_node, GenApi::INode *limit_node, std::string limit, std::unordered_map &invalidators); template -static T gst_pylon_query_feature_limits(GenApi::INode *feature_node, - const std::string &limit); -static std::vector> -gst_pylon_create_set_value_actions( +T gst_pylon_query_feature_limits(GenApi::INode *feature_node, + const std::string &limit); +std::vector> gst_pylon_create_set_value_actions( const std::vector &node_list); template -static void gst_pylon_find_limits( - GenApi::INode *node, double &minimum_under_all_settings, - double &maximum_under_all_settings, - std::vector &invalidators_result); +void gst_pylon_find_limits(GenApi::INode *node, + double &minimum_under_all_settings, + double &maximum_under_all_settings, + std::vector &invalidators_result); template -static std::string gst_pylon_build_cache_value_string( - GParamFlags flags, T minimum_under_all_settings, - T maximum_under_all_settings); +std::string gst_pylon_build_cache_value_string(GParamFlags flags, + T minimum_under_all_settings, + T maximum_under_all_settings); -static void gst_pylon_query_feature_properties_double( - GenApi::INodeMap &nodemap, GenApi::INode *node, - GstPylonCache &feature_cache, GParamFlags &flags, - gdouble &minimum_under_all_settings, gdouble &maximum_under_all_settings, - GenApi::INode *selector = NULL, gint64 selector_value = 0); +gboolean gst_pylon_can_feature_later_be_writable(GenApi::INode *node); -static void gst_pylon_query_feature_properties_integer( - GenApi::INodeMap &nodemap, GenApi::INode *node, - GstPylonCache &feature_cache, GParamFlags &flags, - gint64 &minimum_under_all_settings, gint64 &maximum_under_all_settings, - GenApi::INode *selector = NULL, gint64 selector_value = 0); - -static gboolean gst_pylon_can_feature_later_be_writable(GenApi::INode *node); -static GParamFlags gst_pylon_query_access(GenApi::INodeMap &nodemap, - GenApi::INode *node); +std::vector gst_pylon_create_reset_value_actions( + const std::vector &node_list); -static gboolean gst_pylon_can_feature_later_be_writable(GenApi::INode *node) { +gboolean gst_pylon_can_feature_later_be_writable(GenApi::INode *node) { GenICam::gcstring value; GenICam::gcstring attribute; if (node->GetProperty("pIsLocked", value, attribute)) { @@ -166,8 +123,8 @@ static gboolean gst_pylon_can_feature_later_be_writable(GenApi::INode *node) { } } -static GParamFlags gst_pylon_query_access(GenApi::INodeMap &nodemap, - GenApi::INode *node) { +GParamFlags gst_pylon_query_access(GenApi::INodeMap &nodemap, + GenApi::INode *node) { gint flags = 0; g_return_val_if_fail(node, static_cast(flags)); @@ -211,8 +168,8 @@ static GParamFlags gst_pylon_query_access(GenApi::INodeMap &nodemap, return static_cast(flags); } -static GenApi::INode *gst_pylon_find_limit_node( - GenApi::INode *node, const GenICam::gcstring &limit) { +GenApi::INode *gst_pylon_find_limit_node(GenApi::INode *node, + const GenICam::gcstring &limit) { GenApi::INode *limit_node = NULL; GenICam::gcstring value; GenICam::gcstring attribute; @@ -224,10 +181,45 @@ static GenApi::INode *gst_pylon_find_limit_node( } else if (node->GetProperty("pValue", value, attribute)) { limit_node = gst_pylon_find_limit_node(node->GetNodeMap()->GetNode(value), limit); + } else if (node->GetProperty("pValueDefault", value, attribute)) { + /* FIXME: pValueDefault might not cover the limits of all selector entries + * properly + */ + limit_node = + gst_pylon_find_limit_node(node->GetNodeMap()->GetNode(value), limit); } return limit_node; } +/* identify the first category a node belongs to */ +static const std::string gst_pylon_find_node_category(GenApi::INode *node) { + std::string category = ""; + g_return_val_if_fail(node, category); + + GenApi::INode *curr_node = node; + + while (true) { + if (curr_node->IsFeature() && + curr_node->GetPrincipalInterfaceType() == GenApi::intfICategory) { + category = curr_node->GetName(); + break; + } else { + GenApi::NodeList_t parents; + curr_node->GetParents(parents); + if (parents.size() == 0) { + /* no parents and still no Category found, abort */ + break; + } else if (parents.size() >= 1) { + /* yes there are genicam nodes that belong to multiple categories ... + * but we ignore this here */ + curr_node = parents[0]; + } + } + } + + return category; +} + static std::vector gst_pylon_find_parent_features( GenApi::INode *node) { std::vector parent_features; @@ -249,7 +241,7 @@ static std::vector gst_pylon_find_parent_features( return parent_features; } -static void gst_pylon_add_all_property_values( +void gst_pylon_add_all_property_values( GenApi::INode *node, std::string value, std::unordered_map &invalidators) { std::string delimiter = "\t"; @@ -271,7 +263,7 @@ static void gst_pylon_add_all_property_values( } } -static std::vector gst_pylon_get_available_features( +std::vector gst_pylon_get_available_features( const std::set &feature_list) { std::vector available_features; for (const auto &feature : feature_list) { @@ -282,8 +274,19 @@ static std::vector gst_pylon_get_available_features( return available_features; } +static std::vector gst_pylon_get_valid_categories( + const std::vector &feature_list) { + std::vector valid_features; + for (const auto &feature : feature_list) { + if (gst_pylon_find_node_category(feature) != "MultipleROI") { + valid_features.push_back(feature); + } + } + return valid_features; +} + template -static std::vector> gst_pylon_cartesian_product( +std::vector> gst_pylon_cartesian_product( std::vector> &values) { std::vector> result; auto product = [](long long a, std::vector &b) { return a * b.size(); }; @@ -301,8 +304,8 @@ static std::vector> gst_pylon_cartesian_product( } template -static T gst_pylon_query_feature_limits(GenApi::INode *node, - const std::string &limit) { +T gst_pylon_query_feature_limits(GenApi::INode *node, + const std::string &limit) { g_return_val_if_fail(node, 0); P param(node); @@ -315,7 +318,7 @@ static T gst_pylon_query_feature_limits(GenApi::INode *node, } template -static T gst_pylon_check_for_feature_invalidators( +T gst_pylon_check_for_feature_invalidators( GenApi::INode *node, GenApi::INode *limit_node, std::string limit, std::unordered_map &invalidators) { T limit_under_all_settings = 0; @@ -336,8 +339,7 @@ static T gst_pylon_check_for_feature_invalidators( return limit_under_all_settings; } -static std::vector> -gst_pylon_create_set_value_actions( +std::vector> gst_pylon_create_set_value_actions( const std::vector &node_list) { std::vector> actions_list; @@ -379,13 +381,11 @@ gst_pylon_create_set_value_actions( GenApi::StringList_t settable_values; param.GetSettableValues(settable_values); for (const auto &value : settable_values) { - /* Skip unsupported packed mono and bayer formats */ + /* Skip only check plugin supported formats */ if (node->GetName() == "PixelFormat" && - (Pylon::IsMonoPacked(static_cast( - param.GetEntryByName(value)->GetValue())) || - Pylon::IsBayerPacked(static_cast( - param.GetEntryByName(value)->GetValue())))) + !isSupportedPylonFormat(value.c_str())) { continue; + } values.push_back( new GstPylonTypeAction( param, value)); @@ -393,9 +393,10 @@ gst_pylon_create_set_value_actions( break; } default: - std::string msg = "Action in unsupported for node of type " + - std::to_string(node->GetPrincipalInterfaceType()); - throw Pylon::GenericException(msg.c_str(), __FILE__, __LINE__); + std::string msg = + "No test for node " + std::string(node->GetName().c_str()); + GST_DEBUG("%s", msg.c_str()); + continue; } actions_list.push_back(values); } @@ -403,7 +404,7 @@ gst_pylon_create_set_value_actions( return actions_list; } -static std::vector gst_pylon_create_reset_value_actions( +std::vector gst_pylon_create_reset_value_actions( const std::vector &node_list) { std::vector actions_list; @@ -439,25 +440,49 @@ static std::vector gst_pylon_create_reset_value_actions( break; } default: - std::string msg = "Action in unsupported for node of type " + - std::to_string(node->GetPrincipalInterfaceType()); - throw Pylon::GenericException(msg.c_str(), __FILE__, __LINE__); + std::string msg = + "No test for node " + std::string(node->GetName().c_str()); + GST_DEBUG("%s", msg.c_str()); + continue; } } return actions_list; } +using namespace std ::literals; +class TimeLogger { + public: + TimeLogger(const std::string &message) + : t0(std::chrono::system_clock::now()), message(message) { + GST_DEBUG("Start Checking: %s ", message.c_str()); + }; + + ~TimeLogger() { + auto t1 = std::chrono::system_clock::now(); + GST_DEBUG("TIMELOGGER %s %ld msec -> ", message.c_str(), (t1 - t0) / 1ms); + for (auto &info : info_list) { + GST_DEBUG("%s ", info.c_str()); + } + } + + void add_info(const std::string &info) { info_list.push_back(info); } + + private: + const std::chrono::time_point t0; + std::string message; + std::vector info_list; +}; template -static void gst_pylon_find_limits(GenApi::INode *node, - T &minimum_under_all_settings, - T &maximum_under_all_settings) { +void gst_pylon_find_limits(GenApi::INode *node, T &minimum_under_all_settings, + T &maximum_under_all_settings) { std::unordered_map invalidators; maximum_under_all_settings = 0; minimum_under_all_settings = 0; - g_return_if_fail(node); + auto tl = TimeLogger(node->GetName().c_str()); + /* Find the maximum value of a feature under the influence of other elements * of the nodemap */ GenApi::INode *pmax_node = gst_pylon_find_limit_node(node, "pMax"); @@ -490,6 +515,97 @@ static void gst_pylon_find_limits(GenApi::INode *node, std::vector available_parent_inv = gst_pylon_get_available_features(parent_invalidators); + /* workarounds for ace2/dart2 exposuretime and short exposuretime + * and other nodes with large dependencies + * FIXME: refactor this into a filter class + */ + if (node->GetName() == "ExposureTime" && + available_parent_inv.end() != + std::find_if(available_parent_inv.begin(), available_parent_inv.end(), + [](const GenApi::INode *n) { + return n->GetName() == "BslExposureTimeMode"; + })) { + GST_DEBUG("Apply ExposureTime feature workaround"); + minimum_under_all_settings = 1.0; + maximum_under_all_settings = 1e+07; + return; + } else if (node->GetName() == "OffsetX") { + GST_DEBUG("Apply OffsetX feature workaround"); + Pylon::CIntegerParameter sensor_width( + node->GetNodeMap()->GetNode("SensorWidth")); + Pylon::CIntegerParameter width(node->GetNodeMap()->GetNode("Width")); + /* try to shortcut if sensor width is available */ + if (sensor_width.IsValid() && width.IsValid()) { + minimum_under_all_settings = 0; + maximum_under_all_settings = sensor_width.GetValue() - width.GetInc(); + return; + } + } else if (node->GetName() == "OffsetY") { + GST_DEBUG("Apply OffsetY feature workaround"); + Pylon::CIntegerParameter sensor_height( + node->GetNodeMap()->GetNode("SensorHeight")); + Pylon::CIntegerParameter height(node->GetNodeMap()->GetNode("Height")); + /* try to shortcut if sensor height is available */ + if (sensor_height.IsValid() && height.IsValid()) { + minimum_under_all_settings = 0; + maximum_under_all_settings = sensor_height.GetValue() - height.GetInc(); + return; + } + } else if (node->GetName() == "AutoFunctionROIOffsetX") { + GST_DEBUG("Apply AutoFunctionROIOffsetX feature workaround"); + Pylon::CIntegerParameter sensor_width( + node->GetNodeMap()->GetNode("SensorWidth")); + Pylon::CIntegerParameter width(node->GetNodeMap()->GetNode("Width")); + /* try to shortcut if sensor width is available */ + if (sensor_width.IsValid() && width.IsValid()) { + minimum_under_all_settings = 0; + maximum_under_all_settings = sensor_width.GetValue() - width.GetInc(); + return; + } + } else if (node->GetName() == "AutoFunctionROIOffsetY") { + GST_DEBUG("Apply AutoFunctionROIOffsetY feature workaround"); + Pylon::CIntegerParameter sensor_height( + node->GetNodeMap()->GetNode("SensorHeight")); + Pylon::CIntegerParameter height(node->GetNodeMap()->GetNode("Height")); + /* try to shortcut if sensor height is available */ + if (sensor_height.IsValid() && height.IsValid()) { + minimum_under_all_settings = 0; + maximum_under_all_settings = sensor_height.GetValue() - height.GetInc(); + return; + } + } else if (node->GetName() == "AutoFunctionROIWidth") { + GST_DEBUG("Apply AutoFunctionROIWidth feature workaround"); + Pylon::CIntegerParameter sensor_width( + node->GetNodeMap()->GetNode("SensorWidth")); + /* try to shortcut if sensor width is available */ + if (sensor_width.IsValid()) { + minimum_under_all_settings = 0; + maximum_under_all_settings = sensor_width.GetValue(); + return; + } + } else if (node->GetName() == "AutoFunctionROIHeight") { + GST_DEBUG("Apply AutoFunctionROIHeight feature workaround"); + Pylon::CIntegerParameter sensor_height( + node->GetNodeMap()->GetNode("SensorHeight")); + /* try to shortcut if sensor height is available */ + if (sensor_height.IsValid()) { + minimum_under_all_settings = 0; + maximum_under_all_settings = sensor_height.GetValue(); + return; + } + } else if (node->GetName() == "AcquisitionBurstFrameCount") { + minimum_under_all_settings = 1; + maximum_under_all_settings = 1023; + GST_DEBUG("Apply AcquisitionBurstFrameCount feature workaround"); + return; + } + + available_parent_inv = gst_pylon_get_valid_categories(available_parent_inv); + + for (auto &node : available_parent_inv) { + tl.add_info(node->GetName().c_str()); + } + /* Save current set of values */ std::vector reset_list = gst_pylon_create_reset_value_actions(available_parent_inv); @@ -498,19 +614,30 @@ static void gst_pylon_find_limits(GenApi::INode *node, std::vector> actions_list = gst_pylon_create_set_value_actions(available_parent_inv); + /* try to get support for optimized bulk feature settings */ + auto reg_streaming_start = Pylon::CCommandParameter( + node->GetNodeMap()->GetNode("DeviceRegistersStreamingStart")); + auto reg_streaming_end = Pylon::CCommandParameter( + node->GetNodeMap()->GetNode("DeviceRegistersStreamingEnd")); + /* Create list of all possible setting permutations and execute them all */ auto action_list_permutations = gst_pylon_cartesian_product(actions_list); std::vector min_values; std::vector max_values; for (const auto &actions : action_list_permutations) { + reg_streaming_start.TryExecute(); for (const auto &action : actions) { /* Some states might not be valid, so just skip them */ try { action->set_value(); - } catch (const Pylon::GenericException &) { + } catch (const GenICam::GenericException &e) { + GST_DEBUG("failed to set action"); continue; } } + + reg_streaming_end.TryExecute(); + /* Capture min and max values after all setting are applied*/ min_values.push_back(gst_pylon_query_feature_limits(node, "min")); max_values.push_back(gst_pylon_query_feature_limits(node, "max")); @@ -613,269 +740,3 @@ void gst_pylon_query_feature_properties_integer( g_free(feature_cache_name); } - -static GParamSpec *gst_pylon_make_spec_int64(GenApi::INodeMap &nodemap, - GenApi::INode *node, - GstPylonCache &feature_cache) { - g_return_val_if_fail(node, NULL); - - Pylon::CIntegerParameter param(node); - gint64 max_value = 0; - gint64 min_value = 0; - GParamFlags flags = G_PARAM_READABLE; - - gst_pylon_query_feature_properties_integer(nodemap, node, feature_cache, - flags, min_value, max_value); - - return g_param_spec_int64(node->GetName(), node->GetDisplayName(), - node->GetToolTip(), min_value, max_value, - param.GetValue(), flags); -} - -static GParamSpec *gst_pylon_make_spec_selector_int64( - GenApi::INodeMap &nodemap, GenApi::INode *node, GenApi::INode *selector, - guint64 selector_value, GstPylonCache &feature_cache) { - g_return_val_if_fail(node, NULL); - g_return_val_if_fail(selector, NULL); - - Pylon::CIntegerParameter param(node); - gint64 max_value = 0; - gint64 min_value = 0; - GParamFlags flags = G_PARAM_READABLE; - - gst_pylon_query_feature_properties_integer(nodemap, node, feature_cache, - flags, min_value, max_value, - selector, selector_value); - - return gst_pylon_param_spec_selector_int64( - nodemap, node->GetName(), selector->GetName(), selector_value, - node->GetDisplayName(), node->GetToolTip(), min_value, max_value, - param.GetValue(), flags); -} - -static GParamSpec *gst_pylon_make_spec_bool(GenApi::INodeMap &nodemap, - GenApi::INode *node) { - g_return_val_if_fail(node, NULL); - - Pylon::CBooleanParameter param(node); - - return g_param_spec_boolean(node->GetName(), node->GetDisplayName(), - node->GetToolTip(), param.GetValue(), - gst_pylon_query_access(nodemap, node)); -} - -static GParamSpec *gst_pylon_make_spec_selector_bool(GenApi::INodeMap &nodemap, - GenApi::INode *node, - GenApi::INode *selector, - guint64 selector_value) { - g_return_val_if_fail(node, NULL); - g_return_val_if_fail(selector, NULL); - - Pylon::CBooleanParameter param(node); - - return gst_pylon_param_spec_selector_boolean( - nodemap, node->GetName(), selector->GetName(), selector_value, - node->GetDisplayName(), node->GetToolTip(), param.GetValue(), - gst_pylon_query_access(nodemap, node)); -} - -static GParamSpec *gst_pylon_make_spec_double(GenApi::INodeMap &nodemap, - GenApi::INode *node, - GstPylonCache &feature_cache) { - g_return_val_if_fail(node, NULL); - - Pylon::CFloatParameter param(node); - gdouble max_value = 0; - gdouble min_value = 0; - GParamFlags flags = G_PARAM_READABLE; - - gst_pylon_query_feature_properties_double(nodemap, node, feature_cache, flags, - min_value, max_value); - - return g_param_spec_double(node->GetName(), node->GetDisplayName(), - node->GetToolTip(), min_value, max_value, - param.GetValue(), flags); -} - -static GParamSpec *gst_pylon_make_spec_selector_double( - GenApi::INodeMap &nodemap, GenApi::INode *node, GenApi::INode *selector, - guint64 selector_value, GstPylonCache &feature_cache) { - g_return_val_if_fail(node, NULL); - g_return_val_if_fail(selector, NULL); - - Pylon::CFloatParameter param(node); - gdouble max_value = 0; - gdouble min_value = 0; - GParamFlags flags = G_PARAM_READABLE; - - gst_pylon_query_feature_properties_double(nodemap, node, feature_cache, flags, - min_value, max_value, selector, - selector_value); - - return gst_pylon_param_spec_selector_double( - nodemap, node->GetName(), selector->GetName(), selector_value, - node->GetDisplayName(), node->GetToolTip(), min_value, max_value, - param.GetValue(), flags); -} - -static GParamSpec *gst_pylon_make_spec_str(GenApi::INodeMap &nodemap, - GenApi::INode *node) { - g_return_val_if_fail(node, NULL); - - Pylon::CStringParameter param(node); - - return g_param_spec_string(node->GetName(), node->GetDisplayName(), - node->GetToolTip(), param.GetValue(), - gst_pylon_query_access(nodemap, node)); -} - -static GParamSpec *gst_pylon_make_spec_selector_str(GenApi::INodeMap &nodemap, - GenApi::INode *node, - GenApi::INode *selector, - guint64 selector_value) { - g_return_val_if_fail(node, NULL); - g_return_val_if_fail(selector, NULL); - - Pylon::CStringParameter param(node); - - return gst_pylon_param_spec_selector_string( - nodemap, node->GetName(), selector->GetName(), selector_value, - node->GetDisplayName(), node->GetToolTip(), param.GetValue(), - gst_pylon_query_access(nodemap, node)); -} - -static GType gst_pylon_make_enum_type(GenApi::INodeMap &nodemap, - GenApi::INode *node, - const std::string &device_fullname) { - /* When registering enums to the GType system, their string pointers - must remain valid throughout the application lifespan. To achieve this - we are saving all found enums into a static hash table - */ - static std::unordered_map> persistent_values; - - g_return_val_if_fail(node, G_TYPE_INVALID); - - Pylon::CEnumParameter param(node); - - gchar *full_name = g_strdup_printf("%s_%s", device_fullname.c_str(), - node->GetName().c_str()); - std::string name = gst_pylon_param_spec_sanitize_name(full_name); - g_free(full_name); - - GType type = g_type_from_name(name.c_str()); - - if (!type) { - std::vector enumvalues; - GenApi::StringList_t values; - - param.GetSettableValues(values); - for (const auto &value_name : values) { - auto entry = param.GetEntryByName(value_name); - auto value = static_cast(entry->GetValue()); - auto tooltip = entry->GetNode()->GetToolTip(); - - /* We need a copy of the strings so that they are persistent - throughout the application lifespan */ - GEnumValue ev = {value, g_strdup(value_name.c_str()), - g_strdup(tooltip.c_str())}; - enumvalues.push_back(ev); - } - - GEnumValue sentinel = {0}; - enumvalues.push_back(sentinel); - - type = g_enum_register_static(name.c_str(), enumvalues.data()); - persistent_values.insert({type, std::move(enumvalues)}); - } - - return type; -} - -static GParamSpec *gst_pylon_make_spec_enum( - GenApi::INodeMap &nodemap, GenApi::INode *node, - const std::string &device_fullname) { - g_return_val_if_fail(node, NULL); - - Pylon::CEnumParameter param(node); - GType type = gst_pylon_make_enum_type(nodemap, node, device_fullname.c_str()); - - return g_param_spec_enum(node->GetName(), node->GetDisplayName(), - node->GetToolTip(), type, param.GetIntValue(), - gst_pylon_query_access(nodemap, node)); -} - -static GParamSpec *gst_pylon_make_spec_selector_enum( - GenApi::INodeMap &nodemap, GenApi::INode *node, GenApi::INode *selector, - guint64 selector_value, const std::string &device_fullname) { - g_return_val_if_fail(node, NULL); - g_return_val_if_fail(selector, NULL); - - Pylon::CEnumParameter param(node); - GType type = gst_pylon_make_enum_type(nodemap, node, device_fullname.c_str()); - - return gst_pylon_param_spec_selector_enum( - nodemap, node->GetName(), selector->GetName(), selector_value, - node->GetDisplayName(), node->GetToolTip(), type, param.GetIntValue(), - gst_pylon_query_access(nodemap, node)); -} - -GParamSpec *GstPylonParamFactory::make_param(GenApi::INodeMap &nodemap, - GenApi::INode *node, - GenApi::INode *selector, - guint64 selector_value, - const std::string &device_fullname, - GstPylonCache &feature_cache) { - g_return_val_if_fail(node, NULL); - - GParamSpec *spec = NULL; - GenApi::EInterfaceType iface = node->GetPrincipalInterfaceType(); - - switch (iface) { - case GenApi::intfIInteger: - if (!selector) { - spec = gst_pylon_make_spec_int64(nodemap, node, feature_cache); - } else { - spec = gst_pylon_make_spec_selector_int64( - nodemap, node, selector, selector_value, feature_cache); - } - break; - case GenApi::intfIBoolean: - if (!selector) { - spec = gst_pylon_make_spec_bool(nodemap, node); - } else { - spec = gst_pylon_make_spec_selector_bool(nodemap, node, selector, - selector_value); - } - break; - case GenApi::intfIFloat: - if (!selector) { - spec = gst_pylon_make_spec_double(nodemap, node, feature_cache); - } else { - spec = gst_pylon_make_spec_selector_double( - nodemap, node, selector, selector_value, feature_cache); - } - break; - case GenApi::intfIString: - if (!selector) { - spec = gst_pylon_make_spec_str(nodemap, node); - } else { - spec = gst_pylon_make_spec_selector_str(nodemap, node, selector, - selector_value); - } - break; - case GenApi::intfIEnumeration: - if (!selector) { - spec = gst_pylon_make_spec_enum(nodemap, node, device_fullname); - } else { - spec = gst_pylon_make_spec_selector_enum( - nodemap, node, selector, selector_value, device_fullname); - } - break; - default: - Pylon::String_t msg = - "Unsupported node of type " + GenApi::GetInterfaceName(node); - throw Pylon::GenericException(msg, __FILE__, __LINE__); - } - - return spec; -} diff --git a/gst-libs/gst/pylon/gstpylonintrospection.h b/gst-libs/gst/pylon/gstpylonintrospection.h index 5cc01a9..9fcc15f 100644 --- a/gst-libs/gst/pylon/gstpylonintrospection.h +++ b/gst-libs/gst/pylon/gstpylonintrospection.h @@ -37,12 +37,19 @@ #include #include -class GstPylonParamFactory { - public: - static GParamSpec *make_param(GenApi::INodeMap &nodemap, GenApi::INode *node, - GenApi::INode *selector, guint64 selector_value, - const std::string &device_fullname, - GstPylonCache &feature_cache); -}; +GParamFlags gst_pylon_query_access(GenApi::INodeMap &nodemap, + GenApi::INode *node); + +void gst_pylon_query_feature_properties_double( + GenApi::INodeMap &nodemap, GenApi::INode *node, + GstPylonCache &feature_cache, GParamFlags &flags, + gdouble &minimum_under_all_settings, gdouble &maximum_under_all_settings, + GenApi::INode *selector = NULL, gint64 selector_value = 0); + +void gst_pylon_query_feature_properties_integer( + GenApi::INodeMap &nodemap, GenApi::INode *node, + GstPylonCache &feature_cache, GParamFlags &flags, + gint64 &minimum_under_all_settings, gint64 &maximum_under_all_settings, + GenApi::INode *selector = NULL, gint64 selector_value = 0); #endif diff --git a/gst-libs/gst/pylon/gstpylonobject.cpp b/gst-libs/gst/pylon/gstpylonobject.cpp index 0de16aa..ce9574c 100644 --- a/gst-libs/gst/pylon/gstpylonobject.cpp +++ b/gst-libs/gst/pylon/gstpylonobject.cpp @@ -206,6 +206,7 @@ static void gst_pylon_object_set_pylon_feature(GstPylonObjectPrivate* priv, const gchar* name) { P param(*priv->nodemap, name); param.SetValue(get_value(value)); + GST_INFO("Set Feature %s: %s", name, param.ToString().c_str()); } template <> @@ -223,6 +224,8 @@ void gst_pylon_object_set_pylon_feature( Pylon::EIntegerValueCorrection::IntegerValueCorrection_Nearest); } else param.SetValue(get_value(value)); + + GST_INFO("Set Feature %s: %s", name, param.ToString().c_str()); } template <> @@ -240,6 +243,7 @@ void gst_pylon_object_set_pylon_feature( Pylon::EFloatValueCorrection::FloatValueCorrection_ClipToRange); } else param.SetValue(get_value(value)); + GST_INFO("Set Feature %s: %s", name, param.ToString().c_str()); } template <> @@ -248,6 +252,7 @@ void gst_pylon_object_set_pylon_feature( const gchar* name) { Pylon::CEnumParameter param(*priv->nodemap, name); param.SetIntValue(get_value(value)); + GST_INFO("Set Feature %s: %s", name, param.ToString().c_str()); } /* Get gst property from pylon feature */ @@ -257,6 +262,7 @@ static void gst_pylon_object_get_pylon_feature(GenApi::INodeMap& nodemap, const gchar* name) { P param(nodemap, name); set_value(value, param.GetValue()); + GST_DEBUG("Get Feature %s: %s", name, param.ToString().c_str()); } template <> @@ -265,6 +271,7 @@ void gst_pylon_object_get_pylon_feature( const gchar* name) { Pylon::CEnumParameter param(nodemap, name); set_value(value, param.GetIntValue()); + GST_DEBUG("Get Feature %s: %s", name, param.ToString().c_str()); } template <> @@ -273,6 +280,7 @@ void gst_pylon_object_get_pylon_feature( const gchar* name) { Pylon::CStringParameter param(nodemap, name); set_value(value, param.GetValue().c_str()); + GST_DEBUG("Get Feature %s: %s", name, param.ToString().c_str()); } void gst_pylon_object_set_pylon_selector(GenApi::INodeMap& nodemap, @@ -283,9 +291,15 @@ void gst_pylon_object_set_pylon_selector(GenApi::INodeMap& nodemap, switch (selector_type) { case GenApi::intfIEnumeration: Pylon::CEnumParameter(nodemap, selector_name).SetIntValue(selector_value); + GST_INFO( + "Set Selector-Feature %s: %s", selector_name, + Pylon::CEnumParameter(nodemap, selector_name).ToString().c_str()); break; case GenApi::intfIInteger: Pylon::CIntegerParameter(nodemap, selector_name).SetValue(selector_value); + GST_INFO( + "Set Selector-Feature %s: %s", selector_name, + Pylon::CIntegerParameter(nodemap, selector_name).ToString().c_str()); break; default: std::string error_msg = "Selector \"" + std::string(selector_name) + @@ -300,7 +314,9 @@ template static T gst_pylon_object_get_pylon_property(GenApi::INodeMap& nodemap, const gchar* name) { P param(nodemap, name); - return param.GetValue(); + T val = param.GetValue(); + GST_DEBUG("Get Feature %s: %s", name, param.ToString().c_str()); + return val; } template diff --git a/gst-libs/gst/pylon/gstpylonparamfactory.cpp b/gst-libs/gst/pylon/gstpylonparamfactory.cpp new file mode 100644 index 0000000..1fc2c0c --- /dev/null +++ b/gst-libs/gst/pylon/gstpylonparamfactory.cpp @@ -0,0 +1,286 @@ +/* Copyright (C) 2023 Basler AG + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gstpylonparamfactory.h" + +#include "gstpylonintrospection.h" +#include "gstpylonparamspecs.h" + +#include + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_int64( + GenApi::INode *node) { + g_return_val_if_fail(node, NULL); + + Pylon::CIntegerParameter param(node); + gint64 max_value = 0; + gint64 min_value = 0; + GParamFlags flags = G_PARAM_READABLE; + + gst_pylon_query_feature_properties_integer(nodemap, node, feature_cache, + flags, min_value, max_value); + + return g_param_spec_int64(node->GetName(), node->GetDisplayName(), + node->GetToolTip(), min_value, max_value, + param.GetValue(), flags); +} + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_selector_int64( + GenApi::INode *node, GenApi::INode *selector, guint64 selector_value) { + g_return_val_if_fail(node, NULL); + g_return_val_if_fail(selector, NULL); + + Pylon::CIntegerParameter param(node); + gint64 max_value = 0; + gint64 min_value = 0; + GParamFlags flags = G_PARAM_READABLE; + + gst_pylon_query_feature_properties_integer(nodemap, node, feature_cache, + flags, min_value, max_value, + selector, selector_value); + + return gst_pylon_param_spec_selector_int64( + nodemap, node->GetName(), selector->GetName(), selector_value, + node->GetDisplayName(), node->GetToolTip(), min_value, max_value, + param.GetValue(), flags); +} + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_bool( + GenApi::INode *node) { + g_return_val_if_fail(node, NULL); + + Pylon::CBooleanParameter param(node); + + return g_param_spec_boolean(node->GetName(), node->GetDisplayName(), + node->GetToolTip(), param.GetValue(), + gst_pylon_query_access(nodemap, node)); +} + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_selector_bool( + GenApi::INode *node, GenApi::INode *selector, guint64 selector_value) { + g_return_val_if_fail(node, NULL); + g_return_val_if_fail(selector, NULL); + + Pylon::CBooleanParameter param(node); + + return gst_pylon_param_spec_selector_boolean( + nodemap, node->GetName(), selector->GetName(), selector_value, + node->GetDisplayName(), node->GetToolTip(), param.GetValue(), + gst_pylon_query_access(nodemap, node)); +} + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_double( + GenApi::INode *node) { + g_return_val_if_fail(node, NULL); + + Pylon::CFloatParameter param(node); + gdouble max_value = 0; + gdouble min_value = 0; + GParamFlags flags = G_PARAM_READABLE; + + gst_pylon_query_feature_properties_double(nodemap, node, feature_cache, flags, + min_value, max_value); + + return g_param_spec_double(node->GetName(), node->GetDisplayName(), + node->GetToolTip(), min_value, max_value, + param.GetValue(), flags); +} + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_selector_double( + GenApi::INode *node, GenApi::INode *selector, guint64 selector_value) { + g_return_val_if_fail(node, NULL); + g_return_val_if_fail(selector, NULL); + + Pylon::CFloatParameter param(node); + gdouble max_value = 0; + gdouble min_value = 0; + GParamFlags flags = G_PARAM_READABLE; + + gst_pylon_query_feature_properties_double(nodemap, node, feature_cache, flags, + min_value, max_value, selector, + selector_value); + + return gst_pylon_param_spec_selector_double( + nodemap, node->GetName(), selector->GetName(), selector_value, + node->GetDisplayName(), node->GetToolTip(), min_value, max_value, + param.GetValue(), flags); +} + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_str(GenApi::INode *node) { + g_return_val_if_fail(node, NULL); + + Pylon::CStringParameter param(node); + + return g_param_spec_string(node->GetName(), node->GetDisplayName(), + node->GetToolTip(), param.GetValue(), + gst_pylon_query_access(nodemap, node)); +} + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_selector_str( + GenApi::INode *node, GenApi::INode *selector, guint64 selector_value) { + g_return_val_if_fail(node, NULL); + g_return_val_if_fail(selector, NULL); + + Pylon::CStringParameter param(node); + + return gst_pylon_param_spec_selector_string( + nodemap, node->GetName(), selector->GetName(), selector_value, + node->GetDisplayName(), node->GetToolTip(), param.GetValue(), + gst_pylon_query_access(nodemap, node)); +} + +GType GstPylonParamFactory::gst_pylon_make_enum_type(GenApi::INode *node) { + /* When registering enums to the GType system, their string pointers + must remain valid throughout the application lifespan. To achieve this + we are saving all found enums into a static hash table + */ + static std::unordered_map> persistent_values; + + g_return_val_if_fail(node, G_TYPE_INVALID); + + Pylon::CEnumParameter param(node); + + gchar *full_name = g_strdup_printf("%s_%s", device_fullname.c_str(), + node->GetName().c_str()); + std::string name = gst_pylon_param_spec_sanitize_name(full_name); + g_free(full_name); + + GType type = g_type_from_name(name.c_str()); + + if (!type) { + std::vector enumvalues; + GenApi::StringList_t values; + + param.GetSettableValues(values); + for (const auto &value_name : values) { + auto entry = param.GetEntryByName(value_name); + auto value = static_cast(entry->GetValue()); + auto tooltip = entry->GetNode()->GetToolTip(); + + /* We need a copy of the strings so that they are persistent + throughout the application lifespan */ + GEnumValue ev = {value, g_strdup(value_name.c_str()), + g_strdup(tooltip.c_str())}; + enumvalues.push_back(ev); + } + + GEnumValue sentinel = {0}; + enumvalues.push_back(sentinel); + + type = g_enum_register_static(name.c_str(), enumvalues.data()); + persistent_values.insert({type, std::move(enumvalues)}); + } + + return type; +} + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_enum( + GenApi::INode *node) { + g_return_val_if_fail(node, NULL); + + Pylon::CEnumParameter param(node); + GType type = gst_pylon_make_enum_type(node); + + return g_param_spec_enum(node->GetName(), node->GetDisplayName(), + node->GetToolTip(), type, param.GetIntValue(), + gst_pylon_query_access(nodemap, node)); +} + +GParamSpec *GstPylonParamFactory::gst_pylon_make_spec_selector_enum( + GenApi::INode *node, GenApi::INode *selector, guint64 selector_value) { + g_return_val_if_fail(node, NULL); + g_return_val_if_fail(selector, NULL); + + Pylon::CEnumParameter param(node); + GType type = gst_pylon_make_enum_type(node); + + return gst_pylon_param_spec_selector_enum( + nodemap, node->GetName(), selector->GetName(), selector_value, + node->GetDisplayName(), node->GetToolTip(), type, param.GetIntValue(), + gst_pylon_query_access(nodemap, node)); +} + +GParamSpec *GstPylonParamFactory::GstPylonParamFactory::make_param( + GenApi::INode *node, GenApi::INode *selector, guint64 selector_value) { + g_return_val_if_fail(node, NULL); + + GParamSpec *spec = NULL; + GenApi::EInterfaceType iface = node->GetPrincipalInterfaceType(); + + switch (iface) { + case GenApi::intfIInteger: + if (!selector) { + spec = gst_pylon_make_spec_int64(node); + } else { + spec = + gst_pylon_make_spec_selector_int64(node, selector, selector_value); + } + break; + case GenApi::intfIBoolean: + if (!selector) { + spec = gst_pylon_make_spec_bool(node); + } else { + spec = + gst_pylon_make_spec_selector_bool(node, selector, selector_value); + } + break; + case GenApi::intfIFloat: + if (!selector) { + spec = gst_pylon_make_spec_double(node); + } else { + spec = + gst_pylon_make_spec_selector_double(node, selector, selector_value); + } + break; + case GenApi::intfIString: + if (!selector) { + spec = gst_pylon_make_spec_str(node); + } else { + spec = gst_pylon_make_spec_selector_str(node, selector, selector_value); + } + break; + case GenApi::intfIEnumeration: + if (!selector) { + spec = gst_pylon_make_spec_enum(node); + } else { + spec = + gst_pylon_make_spec_selector_enum(node, selector, selector_value); + } + break; + default: + Pylon::String_t msg = + "Unsupported node of type " + GenApi::GetInterfaceName(node); + throw Pylon::GenericException(msg, __FILE__, __LINE__); + } + + return spec; +} diff --git a/gst-libs/gst/pylon/gstpylonparamfactory.h b/gst-libs/gst/pylon/gstpylonparamfactory.h new file mode 100644 index 0000000..e1749e6 --- /dev/null +++ b/gst-libs/gst/pylon/gstpylonparamfactory.h @@ -0,0 +1,80 @@ +/* Copyright (C) 2023 Basler AG + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSTPYLONPARAMFACTORY_H +#define GSTPYLONPARAMFACTORY_H + +#include +#include + +class GstPylonParamFactory { + public: + GstPylonParamFactory(GenApi::INodeMap &nodemap, + const std::string &device_fullname, + GstPylonCache &feature_cache) + : nodemap(nodemap), + device_fullname(device_fullname), + feature_cache(feature_cache){}; + + GParamSpec *make_param(GenApi::INode *node, GenApi::INode *selector, + guint64 selector_value); + + private: + GParamSpec *gst_pylon_make_spec_int64(GenApi::INode *node); + GParamSpec *gst_pylon_make_spec_selector_int64(GenApi::INode *node, + GenApi::INode *selector, + guint64 selector_value); + GParamSpec *gst_pylon_make_spec_bool(GenApi::INode *node); + GParamSpec *gst_pylon_make_spec_selector_bool(GenApi::INode *node, + GenApi::INode *selector, + guint64 selector_value); + GParamSpec *gst_pylon_make_spec_double(GenApi::INode *node); + GParamSpec *gst_pylon_make_spec_selector_double(GenApi::INode *node, + GenApi::INode *selector, + guint64 selector_value); + GParamSpec *gst_pylon_make_spec_str(GenApi::INode *node); + GParamSpec *gst_pylon_make_spec_selector_str(GenApi::INode *node, + GenApi::INode *selector, + guint64 selector_value); + GType gst_pylon_make_enum_type(GenApi::INode *node); + GParamSpec *gst_pylon_make_spec_enum(GenApi::INode *node); + GParamSpec *gst_pylon_make_spec_selector_enum(GenApi::INode *node, + GenApi::INode *selector, + guint64 selector_value); + + private: + GenApi::INodeMap &nodemap; + const std::string &device_fullname; + GstPylonCache &feature_cache; +}; + +#endif // GSTPYLONPARAMFACTORY_H diff --git a/gst-libs/gst/pylon/meson.build b/gst-libs/gst/pylon/meson.build index a944bbc..1b617e3 100644 --- a/gst-libs/gst/pylon/meson.build +++ b/gst-libs/gst/pylon/meson.build @@ -29,12 +29,13 @@ endif gstpylon_sources = [ 'gstpyloncache.cpp', - 'gstpylondebug.c', + 'gstpylondebug.cpp', 'gstpylonfeaturewalker.cpp', 'gstpylonintrospection.cpp', 'gstpylonmeta.cpp', 'gstpylonobject.cpp', - 'gstpylonparamspecs.cpp' + 'gstpylonparamspecs.cpp', + 'gstpylonparamfactory.cpp', ] gstpylon_headers = [ @@ -46,7 +47,7 @@ gstpylon_headers = [ 'gstpylonmeta.h', 'gstpylonmetaprivate.h', 'gstpylonobject.h', - 'gstpylonparamspecs.h' + 'gstpylonparamspecs.h', ] install_headers(gstpylon_headers, subdir : 'gstreamer-1.0/gst/pylon/') diff --git a/hooks/cpp-format b/hooks/cpp-format index 57740e4..35e4069 100755 --- a/hooks/cpp-format +++ b/hooks/cpp-format @@ -19,33 +19,5 @@ else CLANG_INDENT=clang-format fi -CLANG_INDENT_STYLE="{ - BasedOnStyle: Google, - IncludeBlocks: Regroup, - IndentPPDirectives: AfterHash, - IncludeCategories: [ - { - Regex: '^', - Priority: 3 - }, - { - Regex: '^<.*\\\.h>', - Priority: 2 - }, - { - Regex: '^<.*', - Priority: 3 - }, - { - Regex: '.*', - Priority: 1 - } - ] -}" - -CLANG_INDENT_PARAMETERS="--style=\"${CLANG_INDENT_STYLE}\" \ - --sort-includes \ - -i" - echo "--Formatting ${file}--" -eval ${CLANG_INDENT} ${CLANG_INDENT_PARAMETERS} ${file} +eval ${CLANG_INDENT} -i --style=file ${file} diff --git a/hooks/pre-commit.hook b/hooks/pre-commit.hook index 724b47c..688ecad 100755 --- a/hooks/pre-commit.hook +++ b/hooks/pre-commit.hook @@ -3,9 +3,7 @@ # Check that the code follows a consistent code style # -# Check for existence of indent, and error out if not present. -# On some *bsd systems the binary seems to be called gnunindent, -# so check for that first. +# Check for existence of clang-format, and error out if not present. echo "--Checking style--" for file in `git diff-index --cached --name-only HEAD --diff-filter=ACMR| grep -e "\.h$" -e "\.cpp$"` ; do @@ -13,25 +11,25 @@ for file in `git diff-index --cached --name-only HEAD --diff-filter=ACMR| grep - # revision in the index (and not the checked out version). nf=`git checkout-index --temp ${file} | cut -f 1` newfile_name=`basename $file` - touch "/tmp/$newfile_name" || exit 1 - newfile="/tmp/$newfile_name" - cp "${nf}" "${newfile}" + check_dir=$(mktemp -d) + newfile="${check_dir}/${newfile_name}" - INDENT="hooks/cpp-format" + cp "${nf}" "${newfile}" + cp ".clang-format" ${check_dir} - eval ${INDENT} $newfile 2>> /dev/null + eval hooks/cpp-format ${newfile} 2>> /dev/null diff -u -p "${nf}" "${newfile}" r=$? - rm "${newfile}" + rm -r "${check_dir}" rm "${nf}" if [ $r != 0 ] ; then echo "=================================================================================================" echo " Code style error in: $file " echo " " echo " Please fix before committing. Don't forget to run git add before trying to commit again. " -echo " If the whole file is to be committed, run as : " +echo " If the whole file is to be committed, run as (scripts may be found in hooks/): " echo " " echo " hooks/cpp-format $file; git add $file; git commit" echo " " @@ -40,3 +38,4 @@ echo "========================================================================== fi done echo "--Checking style pass--" + diff --git a/meson.build b/meson.build index 6db360b..5c420e1 100644 --- a/meson.build +++ b/meson.build @@ -13,30 +13,33 @@ gst_dep = dependency('gstreamer-1.0', version : gst_req) gst_version = gst_dep.version() message('Building against GStreamer ' + gst_version) +python3 = import('python').find_installation() +# generate project version from git info py_script = ''' import subprocess import sys, os meson_project_path = sys.argv[1] -git_dir = meson_project_path + "/.git" - -if not os.path.exists(git_dir): - sys.exit(1) +git_dir = os.path.join(meson_project_path, ".git") try: - cmd_result = subprocess.run(["git", "describe", "--tags"], capture_output=True) + cmd_result = subprocess.run( + ["git", "--git-dir=" + git_dir, "describe", "--tags", "--abbrev=7", "--dirty=+"], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + check=True, + ) vcs_tag = cmd_result.stdout.decode("utf-8")[1:-1] - print(vcs_tag) -except FileNotFoundError: +except (FileNotFoundError, subprocess.CalledProcessError): sys.exit(1) -''' +print(vcs_tag) +''' git_version = vcs_tag(command : ['python3', '-c', py_script, meson.project_source_root()], - input: 'version.h.in', output: 'version.h', replace_string: '@GIT_VERSION@', - fallback: meson.project_version() + '-local') - + input: 'version.h.in', output: 'version.h', replace_string: '@GIT_VERSION@', + fallback: meson.project_version() + '-local') version_arr = gst_version.split('.') gst_version_major = version_arr[0].to_int() @@ -168,7 +171,7 @@ else endif # Passing this through the command line would be too messy -cdata.set('GST_API_EXPORT', export_define) +cdata.set('GST_PYLON_API_EXPORT', export_define) warning_flags = [ '-Wmissing-declarations', @@ -257,7 +260,6 @@ endif presetdir = join_paths(get_option('datadir'), 'gstreamer-' + api_version, 'presets') -python3 = import('python').find_installation() pkgconfig = import('pkgconfig') plugins_pkgconfig_install_dir = join_paths(plugins_install_dir, 'pkgconfig') if get_option('default_library') == 'shared' diff --git a/tests/check/generic/states.c b/tests/check/generic/states.c index 0c0197c..a08c19a 100644 --- a/tests/check/generic/states.c +++ b/tests/check/generic/states.c @@ -38,196 +38,181 @@ static GList *elements = NULL; -static void -setup (void) -{ +static void setup(void) { GList *features, *f; GList *plugins, *p; gchar **ignorelist = NULL; const gchar *STATE_IGNORE_ELEMENTS = NULL; GstRegistry *def; - GST_DEBUG ("getting elements for package %s", PACKAGE); - STATE_IGNORE_ELEMENTS = g_getenv ("GST_STATE_IGNORE_ELEMENTS"); - if (!g_getenv ("GST_NO_STATE_IGNORE_ELEMENTS") && STATE_IGNORE_ELEMENTS) { - GST_DEBUG ("Will ignore element factories: '%s'", STATE_IGNORE_ELEMENTS); - ignorelist = g_strsplit (STATE_IGNORE_ELEMENTS, " ", 0); + GST_DEBUG("getting elements for package %s", PACKAGE); + STATE_IGNORE_ELEMENTS = g_getenv("GST_STATE_IGNORE_ELEMENTS"); + if (!g_getenv("GST_NO_STATE_IGNORE_ELEMENTS") && STATE_IGNORE_ELEMENTS) { + GST_DEBUG("Will ignore element factories: '%s'", STATE_IGNORE_ELEMENTS); + ignorelist = g_strsplit(STATE_IGNORE_ELEMENTS, " ", 0); } - def = gst_registry_get (); + def = gst_registry_get(); - plugins = gst_registry_get_plugin_list (def); + plugins = gst_registry_get_plugin_list(def); for (p = plugins; p; p = p->next) { GstPlugin *plugin = p->data; - if (strcmp (gst_plugin_get_source (plugin), PACKAGE) != 0) - continue; + if (strcmp(gst_plugin_get_source(plugin), PACKAGE) != 0) continue; - features = - gst_registry_get_feature_list_by_plugin (def, - gst_plugin_get_name (plugin)); + features = gst_registry_get_feature_list_by_plugin( + def, gst_plugin_get_name(plugin)); for (f = features; f; f = f->next) { GstPluginFeature *feature = f->data; - const gchar *name = gst_plugin_feature_get_name (feature); + const gchar *name = gst_plugin_feature_get_name(feature); gboolean ignore = FALSE; - if (!GST_IS_ELEMENT_FACTORY (feature)) - continue; + if (!GST_IS_ELEMENT_FACTORY(feature)) continue; if (ignorelist) { gchar **s; for (s = ignorelist; s && *s; ++s) { - if (g_str_has_prefix (name, *s)) { - GST_DEBUG ("ignoring element %s", name); + if (g_str_has_prefix(name, *s)) { + GST_DEBUG("ignoring element %s", name); ignore = TRUE; } } - if (ignore) - continue; + if (ignore) continue; } - GST_DEBUG ("adding element %s", name); - elements = g_list_prepend (elements, (gpointer) g_strdup (name)); + GST_DEBUG("adding element %s", name); + elements = g_list_prepend(elements, (gpointer)g_strdup(name)); } - gst_plugin_feature_list_free (features); + gst_plugin_feature_list_free(features); } - gst_plugin_list_free (plugins); - g_strfreev (ignorelist); + gst_plugin_list_free(plugins); + g_strfreev(ignorelist); } -static void -teardown (void) -{ +static void teardown(void) { GList *e; for (e = elements; e; e = e->next) { - g_free (e->data); + g_free(e->data); } - g_list_free (elements); + g_list_free(elements); elements = NULL; } - -GST_START_TEST (test_state_changes_up_and_down_seq) -{ +GST_START_TEST(test_state_changes_up_and_down_seq) { GstElement *element; GList *e; for (e = elements; e; e = e->next) { const gchar *name = e->data; - GST_INFO ("testing element %s", name); - element = gst_element_factory_make (name, name); - fail_if (element == NULL, "Could not make element from factory %s", name); + GST_INFO("testing element %s", name); + element = gst_element_factory_make(name, name); + fail_if(element == NULL, "Could not make element from factory %s", name); - if (GST_IS_PIPELINE (element)) { - GST_DEBUG ("element %s is a pipeline", name); + if (GST_IS_PIPELINE(element)) { + GST_DEBUG("element %s is a pipeline", name); } - gst_element_set_state (element, GST_STATE_READY); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_PLAYING); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_READY); - gst_element_set_state (element, GST_STATE_NULL); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_READY); - gst_element_set_state (element, GST_STATE_PLAYING); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_NULL); - gst_object_unref (GST_OBJECT (element)); + gst_element_set_state(element, GST_STATE_READY); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_PLAYING); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_READY); + gst_element_set_state(element, GST_STATE_NULL); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_READY); + gst_element_set_state(element, GST_STATE_PLAYING); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(element)); } } GST_END_TEST; -GST_START_TEST (test_state_changes_up_seq) -{ +GST_START_TEST(test_state_changes_up_seq) { GstElement *element; GList *e; for (e = elements; e; e = e->next) { const gchar *name = e->data; - GST_INFO ("testing element %s", name); - element = gst_element_factory_make (name, name); - fail_if (element == NULL, "Could not make element from factory %s", name); + GST_INFO("testing element %s", name); + element = gst_element_factory_make(name, name); + fail_if(element == NULL, "Could not make element from factory %s", name); - if (GST_IS_PIPELINE (element)) { - GST_DEBUG ("element %s is a pipeline", name); + if (GST_IS_PIPELINE(element)) { + GST_DEBUG("element %s is a pipeline", name); } - gst_element_set_state (element, GST_STATE_READY); + gst_element_set_state(element, GST_STATE_READY); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_READY); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_READY); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_PLAYING); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_READY); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_PLAYING); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_READY); - gst_element_set_state (element, GST_STATE_NULL); - gst_object_unref (GST_OBJECT (element)); + gst_element_set_state(element, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(element)); } } GST_END_TEST; -GST_START_TEST (test_state_changes_down_seq) -{ +GST_START_TEST(test_state_changes_down_seq) { GstElement *element; GList *e; for (e = elements; e; e = e->next) { const gchar *name = e->data; - GST_INFO ("testing element %s", name); - element = gst_element_factory_make (name, name); - fail_if (element == NULL, "Could not make element from factory %s", name); + GST_INFO("testing element %s", name); + element = gst_element_factory_make(name, name); + fail_if(element == NULL, "Could not make element from factory %s", name); - if (GST_IS_PIPELINE (element)) { - GST_DEBUG ("element %s is a pipeline", name); + if (GST_IS_PIPELINE(element)) { + GST_DEBUG("element %s is a pipeline", name); } - gst_element_set_state (element, GST_STATE_READY); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_PLAYING); + gst_element_set_state(element, GST_STATE_READY); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_PLAYING); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_PLAYING); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_PLAYING); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_READY); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_PLAYING); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_READY); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_PLAYING); - gst_element_set_state (element, GST_STATE_PAUSED); - gst_element_set_state (element, GST_STATE_READY); - gst_element_set_state (element, GST_STATE_NULL); - gst_object_unref (GST_OBJECT (element)); + gst_element_set_state(element, GST_STATE_PAUSED); + gst_element_set_state(element, GST_STATE_READY); + gst_element_set_state(element, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(element)); } } GST_END_TEST; +static Suite *states_suite(void) { + Suite *s = suite_create("states_good"); + TCase *tc_chain = tcase_create("general"); -static Suite * -states_suite (void) -{ - Suite *s = suite_create ("states_good"); - TCase *tc_chain = tcase_create ("general"); - - suite_add_tcase (s, tc_chain); - tcase_add_checked_fixture (tc_chain, setup, teardown); - tcase_add_test (tc_chain, test_state_changes_up_and_down_seq); - tcase_add_test (tc_chain, test_state_changes_up_seq); - tcase_add_test (tc_chain, test_state_changes_down_seq); + suite_add_tcase(s, tc_chain); + tcase_add_checked_fixture(tc_chain, setup, teardown); + tcase_add_test(tc_chain, test_state_changes_up_and_down_seq); + tcase_add_test(tc_chain, test_state_changes_up_seq); + tcase_add_test(tc_chain, test_state_changes_down_seq); return s; } -GST_CHECK_MAIN (states); +GST_CHECK_MAIN(states); diff --git a/tests/prototypes/dynamic_limits.cpp b/tests/prototypes/dynamic_limits.cpp index 371ab54..2d98be5 100644 --- a/tests/prototypes/dynamic_limits.cpp +++ b/tests/prototypes/dynamic_limits.cpp @@ -31,25 +31,25 @@ */ #ifdef _MSC_VER // MSVC -#pragma warning(push) -#pragma warning(disable : 4265) +# pragma warning(push) +# pragma warning(disable : 4265) #elif __GNUC__ // GCC, CLANG, MinGW -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#pragma GCC diagnostic ignored "-Woverloaded-virtual" -#pragma GCC diagnostic ignored "-Wunused-variable" -#ifdef __clang__ -#pragma GCC diagnostic ignored "-Wunknown-warning-option" -#endif +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +# pragma GCC diagnostic ignored "-Woverloaded-virtual" +# pragma GCC diagnostic ignored "-Wunused-variable" +# ifdef __clang__ +# pragma GCC diagnostic ignored "-Wunknown-warning-option" +# endif #endif #include #include #ifdef _MSC_VER // MSVC -#pragma warning(pop) +# pragma warning(pop) #elif __GNUC__ // GCC, CLANG, MinWG -#pragma GCC diagnostic pop +# pragma GCC diagnostic pop #endif using namespace std; diff --git a/tests/prototypes/featurewalker.py b/tests/prototypes/featurewalker.py new file mode 100644 index 0000000..69fc641 --- /dev/null +++ b/tests/prototypes/featurewalker.py @@ -0,0 +1,132 @@ +# Copyright (C) 2023 Basler AG +# +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials +# provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of +# its contributors may be used to endorse or promote products +# derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# +from queue import Queue +from typing import List +import pypylon.genicam as geni +import itertools + + +class FeatureSelector(object): + def __init__(self, selector_node, value): + self.selector_node = selector_node + self.value = value + + def __repr__(self): + return f"SEL<{self.selector_node.Node.GetName()}/{self.value}>" + + +class FeatureNode(object): + """ + a feature node is the break down of each camera feature + under each specific selector + """ + + def __init__( + self, + node, + selectors: List[List[FeatureSelector,]] = None, + ): + self.node = node + if selectors is None: + selectors = [] + self.selectors = selectors + + def __repr__(self): + repr = f"FeatureNode {self.node.Node.GetName()} " + if self.selectors: + for sel in self.selectors: + repr += " " + str(sel) + else: + repr += "direct" + return repr + + +def get_node_selectors(node): + selectors = [] + for s in node.Node.GetSelectingFeatures(): + selector_entries = [] + if type(s) == geni.IInteger: + for idx in range(s.GetMin(), s.GetMax() + s.GetInc(), s.GetInc()): + selector_entries.append(FeatureSelector(s, idx)) + elif type(s) == geni.IEnumeration: + for enum_val in s.Symbolics: + if geni.IsImplemented(s.GetEntryByName(enum_val)): + selector_entries.append(FeatureSelector(s, enum_val)) + + selectors.append(selector_entries) + + return selectors + + +# feature walker +# walks down feature tree to get all implemented features +def get_feature_nodes(nodemap, with_selectors: bool, only_implemented: bool): + import queue + + worklist = queue.Queue() + worklist.put(nodemap.GetNode("Root")) + + node_list = [] + while not worklist.empty(): + current_node = worklist.get() + + if only_implemented and not geni.IsImplemented(current_node): + # print(f"skip not implemented node {current_node.Node.Name}") + continue + + if len(current_node.Node.GetSelectedFeatures()): + # print(f"skip selector {current_node.Node.Name}") + continue + + if type(current_node) == geni.ICategory: + # push children of category + children = current_node.Node.GetChildren() + for c in children: + worklist.put(c) + continue + + if type(current_node) in (geni.ICommand, geni.IRegister): + continue + + # loop over all selector options of current node + if with_selectors and len(current_node.Node.GetSelectingFeatures()): + node_selectors = get_node_selectors(current_node) + if len(list(itertools.product(*node_selectors))) > 1: + for selectors in itertools.product(*node_selectors): + node_list.append(FeatureNode(current_node, selectors)) + else: + # for features with only a single selector output the direct form too + node_list.append(FeatureNode(current_node)) + else: + node_list.append(FeatureNode(current_node)) + + return node_list diff --git a/tests/prototypes/list_missing_features.py b/tests/prototypes/list_missing_features.py new file mode 100755 index 0000000..97171e3 --- /dev/null +++ b/tests/prototypes/list_missing_features.py @@ -0,0 +1,116 @@ +#! /usr/bin/env python3 +# Copyright (C) 2023 Basler AG +# +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials +# provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of +# its contributors may be used to endorse or promote products +# derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# + + +import featurewalker +import pypylon.pylon as py +import gi +import os + +gi.require_version("Gst", "1.0") +from gi.repository import Gst + +Gst.init() + + +def create_gst_feature_name(fn: featurewalker.FeatureNode, prefix: str): + f_name = f"{prefix}::{fn.node.Node.GetName()}" + for sel in fn.selectors: + f_name += f"-{sel.value}" + return f_name + +def patch_to_gst_name_convention(name: str): + return name.replace("_", "-") + +def get_pylon_features(device_index:int = 0): + tlf = py.TlFactory.GetInstance() + devs = tlf.EnumerateDevices() + # capture all_features + cam = py.InstantCamera(tlf.CreateDevice(devs[device_index])) + cam.Open() + + features = featurewalker.get_feature_nodes( + cam.GetNodeMap(), with_selectors=True, only_implemented=True + ) + cam_features = [] + for f in features: + cam_features.append(create_gst_feature_name(f, "cam")) + + + features = featurewalker.get_feature_nodes( + cam.GetStreamGrabberNodeMap(), with_selectors=True, only_implemented=True + ) + + stream_features = [] + for f in features: + stream_features.append(create_gst_feature_name(f, "stream")) + + features = featurewalker.get_feature_nodes( + cam.GetTLNodeMap(), with_selectors=True, only_implemented=True + ) + tl_features = [] + for f in features: + tl_features.append(create_gst_feature_name(f, "tl")) + + cam.Close() + + return cam_features, stream_features, tl_features + + +def get_gst_features(device_index:int = 0): + pipe = Gst.parse_launch(f"pylonsrc name=src device-index={device_index} ! fakesink") + pylon = pipe.get_by_name("src") + cam = pylon.get_child_by_name("cam") + stream = pylon.get_child_by_name("stream") + + + gst_cam_features = [] + for f in cam.props: + gst_cam_features.append(f"cam::{f.name}") + + + gst_stream_features = [] + for f in stream.props: + gst_stream_features.append(f"stream::{f.name}") + + return gst_cam_features, gst_stream_features, [] + + +print("MISSING FEATURES") +pylon_features = get_pylon_features(0) +gst_features = get_gst_features(0) + +for p_feat, g_feat in zip(pylon_features, gst_features): + for f in [x for x in p_feat if patch_to_gst_name_convention(x) not in set(g_feat)]: + print(f) +