ParameterSchema Library

Overview

ParameterSchema is a lightweight, non-Qt static library at src/ParameterSchema/ that provides compile-time parameter schema extraction for reflect-cpp structs. It is a shared dependency used by both TransformsV2 (for transform parameter forms) and the Command Architecture (for command introspection and guided pipeline editors).

The library has no Qt dependency and depends only on reflect-cpp and the C++ standard library.

Source Location

src/ParameterSchema/
├── CMakeLists.txt
├── ParameterSchema.hpp   — All types, template function, and helper declarations
└── ParameterSchema.cpp   — Non-template helper implementations

Key Types

ParameterFieldDescriptor

Describes a single field in a parameter struct at runtime:

Field Type Description
name std::string Reflect-cpp field name (snake_case)
type_name std::string Underlying type: "float", "int", "std::string", "bool", "enum"
raw_type_name std::string Full raw type string from reflect-cpp
display_name std::string Human-readable “Title Case” label
min_value / max_value std::optional<double> From rfl::Validator constraints
is_exclusive_min/max bool Exclusive vs. inclusive bound
allowed_values std::vector<std::string> For enum-like string fields; auto-populated for enum class fields
default_value_json std::optional<std::string> Default value as JSON string
tooltip std::string UI hint (from ParameterUIHints)
group std::string Grouping label for the UI
is_advanced bool Collapsed by default in the UI
is_optional bool True for std::optional<T> fields
is_bound bool True if typically populated from a value store
is_variant bool True for rfl::TaggedUnion fields
variant_discriminator std::string Tag field name (e.g. "model")
variant_alternatives std::vector<VariantAlternative> One entry per union alternative

ParameterSchema

Holds the complete schema for one parameter struct:

struct ParameterSchema {
    std::string params_type_name;
    std::vector<ParameterFieldDescriptor> fields;

    ParameterFieldDescriptor * field(std::string const & name);
    ParameterFieldDescriptor const * field(std::string const & name) const;
};

ParameterUIHints<T>

A template trait struct for providing custom UI annotations. Specialize it alongside a params struct to add tooltips, allowed values, grouping, and ordering without modifying the struct itself:

template<>
struct ParameterUIHints<MyParams> {
    static void annotate(ParameterSchema & schema) {
        schema.field("threshold")->tooltip = "Detection threshold (0.0–1.0)";
        schema.field("mode")->allowed_values = {"Fast", "Accurate"};
    }
};

extractParameterSchema<T>()

The core function template. Enumerates all fields of a reflect-cpp struct at compile time, parses type strings (stripping std::optional<>, rfl::Validator<> wrappers), extracts validator constraints, and applies any ParameterUIHints<T> specialization:

ParameterSchema schema = extractParameterSchema<MyParams>();

This is typically called once per type — either at static registration time (TransformsV2) or lazily by the command factory (Command Architecture).

Enum Class Auto-Detection

C++ enum class fields (including std::optional<enum class>) are automatically detected at compile time using rfl::to_view() and std::is_enum_v. When detected:

  • type_name is set to "enum"
  • allowed_values is populated with all enumerator names via rfl::get_enumerator_array<E>()
  • Default values serialize as JSON strings (e.g. "fast" for MyEnum::fast)
  • No ParameterUIHints specialization is needed
enum class MotionModel { linear, sinusoidal, brownian };

struct MyParams {
    MotionModel model = MotionModel::linear;   // → combo box with 3 entries
    std::optional<MotionModel> alt_model;      // → gated combo box
};

Limitations:

  • Enum values must be in the range [-256, 256] (reflect-cpp default; configurable via rfl::config::enum_range<E>).
  • The enum must be in a named namespace (anonymous namespaces break reflect-cpp’s compile-time name extraction).

Tagged Variant (Discriminated Union) Auto-Detection

rfl::TaggedUnion fields are automatically detected at compile time using a is_tagged_union_v<T> SFINAE trait (checks for T::VariantType and T::discrimininator_). When detected:

  • type_name is set to "variant"
  • is_variant is set to true
  • variant_discriminator is set to the compile-time discriminator string (e.g. "model")
  • variant_alternatives is populated with one VariantAlternative per union alternative, each containing the tag name and a recursively-extracted ParameterSchema for that alternative’s fields
  • allowed_values is auto-populated with the tag names (for combo box rendering)
  • default_value_json is the full JSON serialization of the default-constructed variant (including discriminator and sub-fields)
struct LinearMotionParams {
    float velocity_x = 1.0f;
    float velocity_y = 0.0f;
};

struct SinusoidalMotionParams {
    float amplitude_x = 0.0f;
    float amplitude_y = 0.0f;
    float frequency_x = 0.0f;
    float frequency_y = 0.0f;
    float phase_x = 0.0f;
    float phase_y = 0.0f;
};

struct BrownianMotionParams {
    float diffusion = 1.0f;
    int seed = 42;
};

using MotionVariant = rfl::TaggedUnion<
    "model",
    LinearMotionParams,
    SinusoidalMotionParams,
    BrownianMotionParams>;

struct MovingPointParams {
    float start_x = 100.0f;
    int num_frames = 100;
    MotionVariant motion = LinearMotionParams{};  // ← variant field
};

JSON output:

{
    "start_x": 100.0,
    "num_frames": 100,
    "motion": {
        "model": "LinearMotionParams",
        "velocity_x": 1.0,
        "velocity_y": 0.0
    }
}

AutoParamWidget renders variant fields as a combo box (discriminator) above a QStackedWidget containing one sub-form per alternative. Changing the combo box selection swaps the visible sub-form.

Important: rfl::TaggedUnion has no default constructor. The variant field in your params struct must have a default member initializer that provides one of the alternative types (e.g. = LinearMotionParams{}).

Limitations:

  • Only rfl::TaggedUnion (internally tagged) variants are supported. std::variant without a tag is not auto-detected.
  • Alternative structs must be default-constructible.

VariantAlternative

Describes one alternative in a variant field:

Field Type Description
tag std::string Discriminator value (struct name, e.g. "LinearMotionParams")
schema std::unique_ptr<ParameterSchema> Sub-schema for this alternative’s fields

std::unique_ptr is used to break the circular type dependency (VariantAlternativeParameterSchemaParameterFieldDescriptorVariantAlternative).

Helper Functions

Function Description
snakeCaseToDisplay(s) "scale_factor""Scale Factor"
parseUnderlyingType(s) Strips optional<> / Validator<> to return "float", "int", etc.
isOptionalType(s) True if the type string contains "optional"
hasValidator(s) True if the type string contains "Validator"
extractConstraints(s) Returns ConstraintInfo with min_value, max_value, exclusivity flags

CMake Integration

The CMake target is WhiskerToolbox::ParameterSchema. Link it directly when you need schema extraction without pulling in TransformsV2:

target_link_libraries(MyTarget PUBLIC WhiskerToolbox::ParameterSchema)

TransformsV2 already links ParameterSchema publicly, so any target that links TransformsV2 inherits access to the ParameterSchema headers automatically.

Namespace

All types live in WhiskerToolbox::Transforms::V2. The namespace was preserved during the extraction from TransformsV2 to avoid breaking all existing code that uses using namespace WhiskerToolbox::Transforms::V2.

Include Path

#include "ParameterSchema/ParameterSchema.hpp"

The library’s target_include_directories exposes src/ as the include root, so this include works from any target that links WhiskerToolbox::ParameterSchema (directly or transitively through TransformsV2).

History

The ParameterSchema types were originally defined inside src/TransformsV2/core/ParameterSchema.hpp. They were extracted into this standalone library (Step 1.11 of the Command Architecture Roadmap) to allow the Command Architecture (src/DataManager/Commands/) and future UI libraries to depend on schema extraction without coupling to TransformsV2.