浏览代码

Merge pull request #31 from unity/additonal_scenario_configs

Additional scenario configs:
- Ability to add valid ranges for all samplers (except for animation curve sampler)
- Ability to add valid range to non sampler numeric scalars through the [Range] attribute
- A new compile definition symbol for exposing the valid ranges of samplers in the scenario UI
- Ability to enable or disable a randomizer through the app-param (for this to work, the already existing enabled property is serialized and deserialized. Code was added to treat properties the same as fields during (de)serialization to achieve this)
- New flag in randomizers to denote whether their enabled state can be switched by the user on datamaker. This is purely used on datamaker.
/0.9.0.preview.1_staging
GitHub Enterprise 3 年前
当前提交
d0a2d55c
共有 12 个文件被更改,包括 316 次插入19 次删除
  1. 2
      com.unity.perception/CHANGELOG.md
  2. 9
      com.unity.perception/Editor/Randomization/Utilities/UIElementsEditorUtilities.cs
  3. 6
      com.unity.perception/Runtime/Randomization/Randomizers/Randomizer.cs
  4. 20
      com.unity.perception/Runtime/Randomization/Samplers/ISampler.cs
  5. 20
      com.unity.perception/Runtime/Randomization/Samplers/SamplerTypes/AnimationCurveSampler.cs
  6. 42
      com.unity.perception/Runtime/Randomization/Samplers/SamplerTypes/ConstantSampler.cs
  7. 39
      com.unity.perception/Runtime/Randomization/Samplers/SamplerTypes/NormalSampler.cs
  8. 39
      com.unity.perception/Runtime/Randomization/Samplers/SamplerTypes/UniformSampler.cs
  9. 14
      com.unity.perception/Runtime/Randomization/Scenarios/Serialization/JsonConverters.cs
  10. 123
      com.unity.perception/Runtime/Randomization/Scenarios/Serialization/ScenarioSerializer.cs
  11. 17
      com.unity.perception/Runtime/Randomization/Scenarios/Serialization/SerializationStructures.cs
  12. 4
      com.unity.perception/Tests/Runtime/Randomization/ScenarioTests/Resources/SampleScenarioConfiguration.json

2
com.unity.perception/CHANGELOG.md


### Added
Added new configuration options to the Scenario JSON configuration. These include a `limits` block on numerical Scalars and Samplers to denote a valid range, and a `state` block on the Randomizers for enabling/disabling them from the config.
The user can now choose the base folder location to store their generated datasets.
Added the AssetSource class for loading assets from generic sources inside randomizers.

9
com.unity.perception/Editor/Randomization/Utilities/UIElementsEditorUtilities.cs


if (originalField == null)
return null;
var tooltipAttribute = originalField.GetCustomAttributes(true)
.ToList().Find(att => att.GetType() == typeof(TooltipAttribute));
var attributes = originalField.GetCustomAttributes(true).ToList();
var tooltipAttribute = attributes.Find(att => att is TooltipAttribute);
var hideInspectorAttribute = attributes.Find(att => att is HideInInspector);
if (hideInspectorAttribute != null)
propertyField.style.display = DisplayStyle.None;
return propertyField;
}
}

6
com.unity.perception/Runtime/Randomization/Randomizers/Randomizer.cs


}
/// <summary>
/// Some Randomizers should not be disabled by the user as they are critical to the project. E.g. We might want to mark this as false for a foreground objects placement randomizer in some projects
/// </summary>
[field: SerializeField]
public bool enabledStateCanBeSwitchedByUser { get; set; } = true;
/// <summary>
/// Returns the scenario containing this Randomizer
/// </summary>
public ScenarioBase scenario => ScenarioBase.activeScenario;

20
com.unity.perception/Runtime/Randomization/Samplers/ISampler.cs


/// Validates that the sampler is configured properly
/// </summary>
void Validate();
/// <summary>
/// Check that the provided values adhere to the <see cref="minAllowed"/> and <see cref="maxAllowed"/> outputs for this sampler.
/// </summary>
public void CheckAgainstValidRange();
/// <summary>
/// Whether the provided <see cref="minAllowed"/> and <see cref="maxAllowed"/> values should be used to validate this sampler.
/// </summary>
public bool shouldCheckValidRange { get; set; }
/// <summary>
/// The smallest value this sampler should output
/// </summary>
public float minAllowed { get; set; }
/// <summary>
/// The largest value this sampler should output
/// </summary>
public float maxAllowed { get; set; }
}
}

20
com.unity.perception/Runtime/Randomization/Samplers/SamplerTypes/AnimationCurveSampler.cs


using System;
using UnityEngine.Assertions;
namespace UnityEngine.Perception.Randomization.Samplers
{

/// </summary>
[Tooltip("Probability distribution curve used for this sampler. The X axis corresponds to the values this sampler will pick from, and the Y axis corresponds to the relative probability of the values. The relative probabilities (Y axis) do not need to max out at 1 as only the shape of the curve matters. The Y values cannot however be negative.")]
public AnimationCurve distributionCurve;
public void CheckAgainstValidRange()
{
throw new NotImplementedException();
//no range check currently performed for this sampler
}
///<inheritdoc/>
[field: HideInInspector]
[field: SerializeField]
public float minAllowed { get; set; }
///<inheritdoc/>
[field: HideInInspector]
[field: SerializeField]
public float maxAllowed { get; set; }
///<inheritdoc/>
[field: HideInInspector]
[field: SerializeField]
public bool shouldCheckValidRange { get; set; }
/// <summary>
/// Number of samples used for integrating over the provided AnimationCurve.

42
com.unity.perception/Runtime/Randomization/Samplers/SamplerTypes/ConstantSampler.cs


using System;
using UnityEngine.Assertions;
namespace UnityEngine.Perception.Randomization.Samplers
{

/// </summary>
public float value;
///<inheritdoc/>
#if !SCENARIO_CONFIG_POWER_USER
[field: HideInInspector]
#endif
[field: SerializeField]
public float minAllowed { get; set; }
///<inheritdoc/>
#if !SCENARIO_CONFIG_POWER_USER
[field: HideInInspector]
#endif
[field: SerializeField]
public float maxAllowed { get; set; }
///<inheritdoc/>
#if !SCENARIO_CONFIG_POWER_USER
[field: HideInInspector]
#endif
[field: SerializeField]
public bool shouldCheckValidRange { get; set; }
/// <summary>
/// Constructs a ConstantSampler
/// </summary>

/// Constructs a new ConstantSampler
/// </summary>
/// <param name="value">The value from which samples will be generated</param>
public ConstantSampler(float value)
/// <param name="shouldCheckValidRange">Whether the provided <see cref="minAllowed"/> and <see cref="maxAllowed"/> values should be used to validate the <see cref="value"/> provided</param>
/// <param name="minAllowed">The smallest min value allowed for this range</param>
/// <param name="maxAllowed">The largest max value allowed for this range</param>
public ConstantSampler(float value, bool shouldCheckValidRange = false, float minAllowed = 0, float maxAllowed = 0)
this.shouldCheckValidRange = shouldCheckValidRange;
this.minAllowed = minAllowed;
this.maxAllowed = maxAllowed;
}
/// <summary>

/// <summary>
/// Validates that the sampler is configured properly
/// </summary>
public void Validate() {}
public void Validate()
{
CheckAgainstValidRange();
}
public void CheckAgainstValidRange()
{
if (shouldCheckValidRange && (value < minAllowed || value > maxAllowed))
{
Debug.LogError($"The value a {GetType().Name} exceeds the allowed valid range. Clamping to valid range.");
value = Mathf.Clamp(value, minAllowed, maxAllowed);
}
}
}
}

39
com.unity.perception/Runtime/Randomization/Samplers/SamplerTypes/NormalSampler.cs


using System;
using UnityEngine.Assertions;
namespace UnityEngine.Perception.Randomization.Samplers
{

mean = 0;
standardDeviation = 1;
}
///<inheritdoc/>
#if !SCENARIO_CONFIG_POWER_USER
[field: HideInInspector]
#endif
[field: SerializeField]
public float minAllowed { get; set; }
///<inheritdoc/>
#if !SCENARIO_CONFIG_POWER_USER
[field: HideInInspector]
#endif
[field: SerializeField]
public float maxAllowed { get; set; }
///<inheritdoc/>
#if !SCENARIO_CONFIG_POWER_USER
[field: HideInInspector]
#endif
[field: SerializeField]
public bool shouldCheckValidRange { get; set; }
/// <summary>
/// Constructs a normal distribution sampler

/// <param name="mean">The mean of the normal distribution to sample from</param>
/// <param name="standardDeviation">The standard deviation of the normal distribution to sample from</param>
/// <param name="shouldCheckValidRange">Whether the provided <see cref="minAllowed"/> and <see cref="maxAllowed"/> values should be used to validate the range provided with <see cref="minimum"/> and <see cref="maximum"/></param>
/// <param name="minAllowed">The smallest min value allowed for this range</param>
/// <param name="maxAllowed">The largest max value allowed for this range</param>
float min, float max, float mean, float standardDeviation)
float min, float max, float mean, float standardDeviation, bool shouldCheckValidRange = false, float minAllowed = 0, float maxAllowed = 0)
this.shouldCheckValidRange = shouldCheckValidRange;
this.minAllowed = minAllowed;
this.maxAllowed = maxAllowed;
}
/// <summary>

public void Validate()
{
range.Validate();
CheckAgainstValidRange();
}
public void CheckAgainstValidRange()
{
if (shouldCheckValidRange && (range.minimum < minAllowed || range.maximum > maxAllowed))
{
Debug.LogError($"The provided min and max values for a {GetType().Name} exceed the allowed valid range. Clamping to valid range.");
range.minimum = Mathf.Clamp(range.minimum, minAllowed, maxAllowed);
range.maximum = Mathf.Clamp(range.maximum, minAllowed, maxAllowed);
}
}
}
}

39
com.unity.perception/Runtime/Randomization/Samplers/SamplerTypes/UniformSampler.cs


using System;
using UnityEngine.Assertions;
namespace UnityEngine.Perception.Randomization.Samplers
{

/// A range bounding the values generated by this sampler
/// </summary>
public FloatRange range;
///<inheritdoc/>
#if !SCENARIO_CONFIG_POWER_USER
[field: HideInInspector]
#endif
[field: SerializeField]
public float minAllowed { get; set; }
///<inheritdoc/>
#if !SCENARIO_CONFIG_POWER_USER
[field: HideInInspector]
#endif
[field: SerializeField]
public float maxAllowed { get; set; }
///<inheritdoc/>
#if !SCENARIO_CONFIG_POWER_USER
[field: HideInInspector]
#endif
[field: SerializeField]
public bool shouldCheckValidRange { get; set; }
/// <summary>
/// Constructs a UniformSampler

/// </summary>
/// <param name="min">The smallest value contained within the range</param>
/// <param name="max">The largest value contained within the range</param>
public UniformSampler(float min, float max)
/// <param name="shouldCheckValidRange">Whether the provided <see cref="minAllowed"/> and <see cref="maxAllowed"/> values should be used to validate the range provided with <see cref="minimum"/> and <see cref="maximum"/></param>
/// <param name="minAllowed">The smallest min value allowed for this range</param>
/// <param name="maxAllowed">The largest max value allowed for this range</param>
public UniformSampler(float min, float max, bool shouldCheckValidRange = false, float minAllowed = 0, float maxAllowed = 0)
this.shouldCheckValidRange = shouldCheckValidRange;
this.minAllowed = minAllowed;
this.maxAllowed = maxAllowed;
}
/// <summary>

public void Validate()
{
range.Validate();
CheckAgainstValidRange();
}
public void CheckAgainstValidRange()
{
if (shouldCheckValidRange && (range.minimum < minAllowed || range.maximum > maxAllowed))
{
Debug.LogError($"The provided min and max values for a {GetType().Name} exceed the allowed valid range. Clamping to valid range.");
range.minimum = Mathf.Clamp(range.minimum, minAllowed, maxAllowed);
range.maximum = Mathf.Clamp(range.maximum, minAllowed, maxAllowed);
}
}
}
}

14
com.unity.perception/Runtime/Randomization/Scenarios/Serialization/JsonConverters.cs


if (itemValue is Parameter)
newObj["param"] = JObject.FromObject(itemValue);
else
newObj["scalar"] = JObject.FromObject(itemValue);
newObj["scalar"] = JObject.FromObject(itemValue, new JsonSerializer { NullValueHandling = NullValueHandling.Ignore });
output[itemKey] = newObj;
}
output.WriteTo(writer);

key = "normal";
else
throw new TypeAccessException($"Cannot serialize type ${options.defaultSampler.GetType()}");
output[key] = JObject.FromObject(options.defaultSampler);
output[key] = JObject.FromObject(options.defaultSampler, new JsonSerializer { NullValueHandling = NullValueHandling.Ignore });
output.WriteTo(writer);
}

if (value.ContainsKey("str"))
scalar.value = new StringScalarValue { str = value["str"].Value<string>() };
else if (value.ContainsKey("num"))
scalar.value = new DoubleScalarValue { num = value["num"].Value<double>() };
{
Limits limits = null;
if (value.ContainsKey("limits"))
{
limits = value["limits"].ToObject<Limits>();
}
scalar.value = new DoubleScalarValue { num = value["num"].Value<double>(), limits = limits};
}
return scalar;
}
}

123
com.unity.perception/Runtime/Randomization/Scenarios/Serialization/ScenarioSerializer.cs


foreach (var randomizer in randomizers)
{
var randomizerData = SerializeRandomizer(randomizer);
if (randomizerData.items.Count == 0)
if (randomizerData.items.Count == 0 && !randomizerData.state.canBeSwitchedByUser)
continue;
serializedRandomizers.Add(randomizer.GetType().Name, randomizerData);
}

static Group SerializeRandomizer(Randomizer randomizer)
{
var randomizerData = new Group();
randomizerData.state.enabled = randomizer.enabled;
randomizerData.state.canBeSwitchedByUser = randomizer.enabledStateCanBeSwitchedByUser;
var fields = randomizer.GetType().GetFields();
foreach (var field in fields)
{

if (sampler is Samplers.ConstantSampler constantSampler)
samplerData.defaultSampler = new ConstantSampler
{
value = constantSampler.value
value = constantSampler.value,
limits = constantSampler.shouldCheckValidRange? new Limits {
min = constantSampler.minAllowed,
max = constantSampler.maxAllowed,
} : null
max = uniformSampler.range.maximum
max = uniformSampler.range.maximum,
limits = uniformSampler.shouldCheckValidRange? new Limits {
min = uniformSampler.minAllowed,
max = uniformSampler.maxAllowed,
} : null
};
else if (sampler is Samplers.NormalSampler normalSampler)
samplerData.defaultSampler = new NormalSampler

mean = normalSampler.mean,
stddev = normalSampler.standardDeviation
stddev = normalSampler.standardDeviation,
limits = normalSampler.shouldCheckValidRange? new Limits {
min = normalSampler.minAllowed,
max = normalSampler.maxAllowed,
} : null
};
else
throw new ArgumentException($"Invalid sampler type ({sampler.GetType()})");

if (field.FieldType == typeof(bool))
return new BooleanScalarValue { boolean = (bool)field.GetValue(obj) };
if (field.FieldType == typeof(float) || field.FieldType == typeof(double) || field.FieldType == typeof(int))
return new DoubleScalarValue { num = Convert.ToDouble(field.GetValue(obj)) };
{
var minAllowed = 0f;
var maxAllowed = 0f;
var shouldCheckValidRange = false;
var rangeAttributes = field.GetCustomAttributes(typeof(RangeAttribute));
if (rangeAttributes.Any())
{
var rangeAttribute = (RangeAttribute)rangeAttributes.First();
minAllowed = rangeAttribute.min;
maxAllowed = rangeAttribute.max;
shouldCheckValidRange = true;
}
return new DoubleScalarValue
{
num = Convert.ToDouble(field.GetValue(obj)),
limits = shouldCheckValidRange? new Limits {
min = minAllowed,
max = maxAllowed,
} : null
};
}
return null;
}
#endregion

static void DeserializeRandomizer(Randomizer randomizer, Group randomizerData)
{
randomizer.enabled = randomizerData.state.enabled;
randomizer.enabledStateCanBeSwitchedByUser = randomizerData.state.canBeSwitchedByUser;
foreach (var pair in randomizerData.items)
{
var field = randomizer.GetType().GetField(pair.Key);

if (samplerOption is ConstantSampler constantSampler)
return new Samplers.ConstantSampler
{
value = (float)constantSampler.value
value = (float)constantSampler.value,
minAllowed = constantSampler.limits != null ? (float)constantSampler.limits.min : 0,
maxAllowed = constantSampler.limits != null ? (float)constantSampler.limits.max : 0,
shouldCheckValidRange = constantSampler.limits != null
};
if (samplerOption is UniformSampler uniformSampler)
return new Samplers.UniformSampler

minimum = (float)uniformSampler.min,
maximum = (float)uniformSampler.max
}
maximum = (float)uniformSampler.max,
},
minAllowed = uniformSampler.limits != null ? (float)uniformSampler.limits.min : 0,
maxAllowed = uniformSampler.limits != null ? (float)uniformSampler.limits.max : 0,
shouldCheckValidRange = uniformSampler.limits != null
};
if (samplerOption is NormalSampler normalSampler)
return new Samplers.NormalSampler

maximum = (float)normalSampler.max
},
mean = (float)normalSampler.mean,
standardDeviation = (float)normalSampler.stddev
standardDeviation = (float)normalSampler.stddev,
minAllowed = normalSampler.limits != null ? (float)normalSampler.limits.min : 0,
maxAllowed = normalSampler.limits != null ? (float)normalSampler.limits.max : 0,
shouldCheckValidRange = normalSampler.limits != null
};
throw new ArgumentException($"Cannot deserialize unsupported sampler type {samplerOption.GetType()}");
}

var rangeAttributes = field.GetCustomAttributes(typeof(RangeAttribute));
RangeAttribute rangeAttribute = null;
if (rangeAttributes.Any())
{
rangeAttribute = (RangeAttribute) rangeAttributes.First();
}
var readScalar = ReadScalarValue(obj, scalar);
var tolerance = 0.00001f;
if (readScalar.Item1 is double num)
{
if (rangeAttribute != null)
{
if (readScalar.Item2 != null &&
(Math.Abs(rangeAttribute.min - readScalar.Item2.max) > tolerance || Math.Abs(rangeAttribute.max - readScalar.Item2.max) > tolerance))
{
//the field has a range attribute and the json has a limits block for this field, but the numbers don't match
Debug.LogError($"The limits provided in the Scenario JSON for the field \"{field.Name}\" of \"{obj.GetType().Name}\" do not match this field's range set in the code. Ranges for scalar fields can only be set in code using the Range attribute and not from the Scenario JSON.");
}
else if (readScalar.Item2 == null)
{
//the field has a range attribute but the json has no limits block for this field
Debug.LogError($"The provided Scenario JSON specifies limits for the field \"{field.Name}\" of \"{obj.GetType().Name}\", while the field has no Range attribute in the code. Ranges for scalar fields can only be set in code using the Range attribute and not from the Scenario JSON.");
}
if (num < rangeAttribute.min || num > rangeAttribute.max)
{
Debug.LogError($"The provided value for the field \"{field.Name}\" of \"{obj.GetType().Name}\" exceeds the allowed valid range. Clamping to valid range.");
var clamped = Mathf.Clamp((float)num, rangeAttribute.min, rangeAttribute.max);
field.SetValue(obj, Convert.ChangeType(clamped, field.FieldType));
}
}
else
{
if (readScalar.Item2 != null)
//the field does not have a range attribute but the json has a limits block for this field
Debug.LogError($"The provided Scenario JSON specifies limits for the field \"{field.Name}\" of \"{obj.GetType().Name}\", but the field has no Range attribute in code. Ranges for scalar fields can only be set in code using the Range attribute and not from the Scenario JSON.");
}
}
else
{
field.SetValue(obj, Convert.ChangeType(readScalar, field.FieldType));
}
}
static (object, Limits) ReadScalarValue(object obj, Scalar scalar)
{
Limits limits = null;
{
limits = doubleValue.limits;
}
field.SetValue(obj, Convert.ChangeType(value, field.FieldType));
return (value, limits);
}
#endregion

17
com.unity.perception/Runtime/Randomization/Scenarios/Serialization/SerializationStructures.cs


public string imageLink = string.Empty;
}
class RandomizerStateData
{
public bool enabled;
public bool canBeSwitchedByUser;
}
class Limits
{
public double min;
public double max;
}
public RandomizerStateData state = new RandomizerStateData();
[JsonConverter(typeof(GroupItemsConverter))]
public Dictionary<string, IGroupItem> items = new Dictionary<string, IGroupItem>();
}

{
public double min;
public double max;
public Limits limits;
}
class NormalSampler : ISamplerOption

public double mean;
public double stddev;
public Limits limits;
public Limits limits;
}
#endregion

class DoubleScalarValue : IScalarValue
{
public double num;
public Limits limits;
}
class BooleanScalarValue : IScalarValue

4
com.unity.perception/Tests/Runtime/Randomization/ScenarioTests/Resources/SampleScenarioConfiguration.json


"description": "",
"imageLink": ""
},
"state": {
"enabled": true,
"canBeSwitchedByUser": true
},
"items": {
"rotation": {
"param": {

正在加载...
取消
保存