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_nameis set to"enum"allowed_valuesis populated with all enumerator names viarfl::get_enumerator_array<E>()- Default values serialize as JSON strings (e.g.
"fast"forMyEnum::fast) - No
ParameterUIHintsspecialization 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_nameis set to"variant"is_variantis set totruevariant_discriminatoris set to the compile-time discriminator string (e.g."model")variant_alternativesis populated with oneVariantAlternativeper union alternative, each containing the tag name and a recursively-extractedParameterSchemafor that alternative’s fieldsallowed_valuesis auto-populated with the tag names (for combo box rendering)default_value_jsonis 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::variantwithout 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 (VariantAlternative → ParameterSchema → ParameterFieldDescriptor → VariantAlternative).
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.