using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.Analytics;
using UnityEngine.Perception.GroundTruth;
using UnityEngine.Perception.Randomization.Parameters;
using UnityEngine.Perception.Randomization.Randomizers;
using UnityEngine.Perception.Randomization.Samplers;
namespace UnityEngine.Perception
{
public static class PerceptionEngineAnalytics
{
const string k_VendorKey = "unity.perception";
const int k_MaxElementsInStruct = 100;
const int k_MaxEventsPerHour = 100;
static bool s_AllEventsRegistered;
// Runtime Events
static string[] allEvents => new[] { k_EventScenarioCompleted };
const string k_EventScenarioCompleted = "perceptionScenarioCompleted";
#region Event: Scenario Completed
///
/// Which labelers will be identified and included in the analytics information.
///
static readonly Type[] k_LabelerAllowList = new[]
{
typeof(BoundingBox3DLabeler), typeof(BoundingBox2DLabeler), typeof(InstanceSegmentationLabeler),
typeof(KeypointLabeler), typeof(ObjectCountLabeler), typeof(SemanticSegmentationLabeler)
};
#region Data Structures
[Serializable]
public class PerceptionCameraData
{
public string captureTriggerMode;
public int startAtFrame;
public int framesBetweenCaptures;
}
[Serializable]
public class LabelerData
{
public string name;
public int labelConfigCount;
public string objectFilter;
public int animationPoseCount;
public static LabelerData FromLabeler(CameraLabeler labeler)
{
var labelerType = labeler.GetType();
if (!k_LabelerAllowList.Contains(labelerType))
return null;
var labelerData = new LabelerData()
{
name = labeler.GetType().Name
};
switch (labeler)
{
case BoundingBox3DLabeler bb3dl:
labelerData.labelConfigCount = bb3dl.idLabelConfig.labelEntries.Count;
break;
case BoundingBox2DLabeler bb2dl:
labelerData.labelConfigCount = bb2dl.idLabelConfig.labelEntries.Count;
break;
case InstanceSegmentationLabeler isl:
labelerData.labelConfigCount = isl.idLabelConfig.labelEntries.Count;
break;
case KeypointLabeler kpl:
labelerData.labelConfigCount = kpl.idLabelConfig.labelEntries.Count;
labelerData.objectFilter = kpl.objectFilter.ToString();
labelerData.animationPoseCount = kpl.animationPoseConfigs.Count;
break;
case ObjectCountLabeler ocl:
labelerData.labelConfigCount = ocl.labelConfig.labelEntries.Count;
break;
case SemanticSegmentationLabeler ssl:
labelerData.labelConfigCount = ssl.labelConfig.labelEntries.Count;
break;
}
return labelerData;
}
}
[Serializable]
public class MemberData
{
public string name;
public string value;
public string type;
}
[Serializable]
public class ParameterField
{
public string name;
public string distribution;
public float value = int.MinValue;
public float rangeMinimum = int.MinValue;
public float rangeMaximum = int.MinValue;
public float mean = int.MinValue;
public float stdDev = int.MinValue;
public int categoricalParameterCount = int.MinValue;
public static ParameterField ExtractSamplerInformation(ISampler sampler, string fieldName = "value")
{
switch (sampler)
{
case AnimationCurveSampler _:
return new ParameterField()
{
name = fieldName,
distribution = "AnimationCurve"
};
case ConstantSampler cs:
return new ParameterField()
{
name = fieldName,
distribution = "Constant",
value = cs.value
};
case NormalSampler ns:
return new ParameterField()
{
name = fieldName,
distribution = "Normal",
mean = ns.mean,
stdDev = ns.standardDeviation,
rangeMinimum = ns.range.minimum,
rangeMaximum = ns.range.maximum
};
case UniformSampler us:
return new ParameterField()
{
name = fieldName,
distribution = "Uniform",
rangeMinimum = us.range.minimum,
rangeMaximum = us.range.maximum
};
default:
return null;
}
}
public static List ExtractSamplersInformation(IEnumerable samplers, IEnumerable indexToNameMapping)
{
var samplersList = samplers.ToList();
var indexToNameMappingList = indexToNameMapping.ToList();
if (samplersList.Count > indexToNameMappingList.Count)
throw new Exception("Insufficient names provided for mapping ParameterFields");
return samplersList.Select((t, i) => ExtractSamplerInformation(t, indexToNameMappingList[i])).ToList();
}
}
[Serializable]
public class ParameterData
{
public string name;
public string type;
public List fields;
}
[Serializable]
public class RandomizerData
{
public string name;
public MemberData[] members;
public ParameterData[] parameters;
public static RandomizerData FromRandomizer(Randomizer randomizer)
{
if (randomizer == null)
return null;
var data = new RandomizerData()
{
name = randomizer.GetType().Name,
};
var randomizerType = randomizer.GetType();
// Only looks for randomizers included by the Perception package.
if (randomizerType.Namespace == null || !randomizerType.Namespace.StartsWith("UnityEngine.Perception"))
return null;
// Naming configuration
var vectorFieldNames = new[] { "x", "y", "z", "w" };
var hsvaFieldNames = new[] { "hue", "saturation", "value", "alpha" };
var rgbFieldNames = new[] { "red", "green", "blue" };
// Add member fields and parameters separately
var members = new List();
var parameters = new List();
foreach (var field in randomizerType.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
var member = field.GetValue(randomizer);
var memberType = member.GetType();
// If member is either a categorical or numeric parameter
if (memberType.IsSubclassOf(typeof(Parameter)))
{
var pd = new ParameterData()
{
name = field.Name,
type = memberType.Name,
fields = new List()
};
// All included parameter types
switch (member)
{
case CategoricalParameterBase cp:
pd.fields.Add(new ParameterField()
{
name = "values",
distribution = "Categorical",
categoricalParameterCount = cp.probabilities.Count
});
break;
case BooleanParameter bP:
pd.fields.Add(ParameterField.ExtractSamplerInformation(bP.value));
break;
case FloatParameter fP:
pd.fields.Add(ParameterField.ExtractSamplerInformation(fP.value));
break;
case IntegerParameter iP:
pd.fields.Add(ParameterField.ExtractSamplerInformation(iP.value));
break;
case Vector2Parameter v2P:
pd.fields.AddRange(ParameterField.ExtractSamplersInformation(v2P.samplers, vectorFieldNames));
break;
case Vector3Parameter v3P:
pd.fields.AddRange(ParameterField.ExtractSamplersInformation(v3P.samplers, vectorFieldNames));
break;
case Vector4Parameter v4P:
pd.fields.AddRange(ParameterField.ExtractSamplersInformation(v4P.samplers, vectorFieldNames));
break;
case ColorHsvaParameter hsvaP:
pd.fields.AddRange(ParameterField.ExtractSamplersInformation(hsvaP.samplers, hsvaFieldNames));
break;
case ColorRgbParameter rgbP:
pd.fields.AddRange(ParameterField.ExtractSamplersInformation(rgbP.samplers, rgbFieldNames));
break;
}
parameters.Add(pd);
}
else
{
members.Add(new MemberData()
{
name = field.Name,
type = memberType.FullName,
value = member.ToString()
});
}
}
data.members = members.ToArray();
data.parameters = parameters.ToArray();
return data;
}
}
[Serializable]
public class ScenarioCompletedData
{
public string platform;
public PerceptionCameraData perceptionCamera;
public LabelerData[] labelers;
public RandomizerData[] randomizers;
}
#endregion
internal static void ReportScenarioCompleted(PerceptionCamera cam, IEnumerable randomizers)
{
if (!TryRegisterEvents())
return;
var data = new ScenarioCompletedData();
if (cam != null)
{
// Perception Camera Data
data.perceptionCamera = new PerceptionCameraData()
{
captureTriggerMode = cam.captureTriggerMode.ToString(),
startAtFrame = cam.firstCaptureFrame,
framesBetweenCaptures = cam.framesBetweenCaptures
};
// Labeler Data
data.labelers = cam.labelers
.Select(LabelerData.FromLabeler)
.Where(labeler => labeler != null).ToArray();
}
var randomizerList = randomizers.ToArray();
if (randomizerList.Any())
{
data.randomizers = randomizerList
.Select(RandomizerData.FromRandomizer)
.Where(x => x != null).ToArray();
}
EditorAnalytics.SendEventWithLimit(k_EventScenarioCompleted, data);
}
#endregion
static bool TryRegisterEvents()
{
if (s_AllEventsRegistered)
return true;
var success = true;
foreach (var eventName in allEvents)
{
success &= EditorAnalytics.RegisterEventWithLimit(eventName, k_MaxEventsPerHour, k_MaxElementsInStruct, k_VendorKey) == AnalyticsResult.Ok;
}
s_AllEventsRegistered = success;
return success;
}
}
}