Skip to content

Commit

Permalink
feat: implement Components.Containers.* (#972)
Browse files Browse the repository at this point in the history
  • Loading branch information
homuler committed Jul 29, 2023
1 parent ba6246a commit 3eb66c8
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2023 homuler
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

namespace Mediapipe.Tasks.Components.Containers
{
/// <summary>
/// Defines a single classification result.
///
/// The label maps packed into the TFLite Model Metadata [1] are used to populate
/// the 'category_name' and 'display_name' fields.
///
/// [1]: https://www.tensorflow.org/lite/convert/metadata
/// </summary>
public readonly struct Category
{
/// <summary>
/// The index of the category in the classification model output.
/// </summary>
public readonly int index;
/// <summary>
/// The score for this category, e.g. (but not necessarily) a probability in [0,1].
/// </summary>
public readonly float score;
/// <summary>
/// The optional ID for the category, read from the label map packed in the
/// TFLite Model Metadata if present. Not necessarily human-readable.
/// </summary>
public readonly string categoryName;
/// <summary>
/// The optional human-readable name for the category, read from the label map
/// packed in the TFLite Model Metadata if present.
/// </summary>
public readonly string displayName;

internal Category(int index, float score, string categoryName, string displayName)
{
this.index = index;
this.score = score;
this.categoryName = categoryName;
this.displayName = displayName;
}

public static Category CreateFrom(Classification proto)
{
var categoryName = proto.HasLabel ? proto.Label : null;
var displayName = proto.HasDisplayName ? proto.DisplayName : null;
return new Category(proto.Index, proto.Score, categoryName, displayName);
}

public override string ToString() => $"{{ \"index\": {index}, \"score\": {score}, \"categoryName\": \"{categoryName}\", \"displayName\": \"{displayName}\" }}";
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) 2023 homuler
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

using System.Collections.Generic;
using System.Linq;

namespace Mediapipe.Tasks.Components.Containers
{
/// <summary>
/// Represents one detected object in the object detector's results.
/// </summary>
public readonly struct Detection
{
private const int _DefaultCategoryIndex = -1;

/// <summary>
/// A list of <see cref="Category" /> objects.
/// </summary>
public readonly IReadOnlyList<Category> categories;
/// <summary>
/// The bounding box location.
/// </summary>
public readonly Rect boundingBox;
/// <summary>
/// Optional list of keypoints associated with the detection. Keypoints
/// represent interesting points related to the detection. For example, the
/// keypoints represent the eye, ear and mouth from face detection model. Or
/// in the template matching detection, e.g. KNIFT, they can represent the
/// feature points for template matching.
/// </summary>
public readonly IReadOnlyList<NormalizedKeypoint> keypoints;

internal Detection(IReadOnlyList<Category> categories, Rect boundingBox, IReadOnlyList<NormalizedKeypoint> keypoints)
{
this.categories = categories;
this.boundingBox = boundingBox;
this.keypoints = keypoints;
}

public static Detection CreateFrom(Mediapipe.Detection proto)
{
var categories = new List<Category>(proto.Score.Count);
for (var idx = 0; idx < proto.Score.Count; idx++)
{
categories.Add(new Category(
proto.LabelId.Count > idx ? proto.LabelId[idx] : _DefaultCategoryIndex,
proto.Score[idx],
proto.Label.Count > idx ? proto.Label[idx] : "",
proto.DisplayName.Count > idx ? proto.DisplayName[idx] : ""
));
}

var boundingBox = proto.LocationData != null ? new Rect(
proto.LocationData.BoundingBox.Xmin,
proto.LocationData.BoundingBox.Ymin,
proto.LocationData.BoundingBox.Xmin + proto.LocationData.BoundingBox.Width,
proto.LocationData.BoundingBox.Ymin + proto.LocationData.BoundingBox.Height
) : new Rect(0, 0, 0, 0);

List<NormalizedKeypoint> keypoints = null;
if (proto.LocationData.RelativeKeypoints.Count > 0)
{
keypoints = new List<NormalizedKeypoint>(proto.LocationData.RelativeKeypoints.Count);
foreach (var keypoint in proto.LocationData.RelativeKeypoints)
{
keypoints.Add(new NormalizedKeypoint(
keypoint.X,
keypoint.Y,
keypoint.HasKeypointLabel ? keypoint.KeypointLabel : null,
#pragma warning disable IDE0004 // for Unity 2020.3.x
keypoint.HasScore ? (float?)keypoint.Score : null
#pragma warning restore IDE0004
));
}
}

return new Detection(categories, boundingBox, keypoints);
}

public override string ToString()
{
var categoriesStr = $"[{string.Join(", ", categories.Select(category => category.ToString()))}]";
var keypointsStr = keypoints == null ? "null" : $"[{string.Join(", ", keypoints.Select(keypoint => keypoint.ToString()))}]";
return $"{{\"categories\": {categoriesStr}, \"boundingBox\": {boundingBox}, \"keypoints\": {keypointsStr}}}";
}
}

/// <summary>
/// Represents the list of detected objects.
/// </summary>
public readonly struct DetectionResult
{
/// <summary>
/// A list of <see cref="Detection" /> objects.
/// </summary>
public readonly IReadOnlyList<Detection> detections;

internal DetectionResult(IReadOnlyList<Detection> detections)
{
this.detections = detections;
}

public static DetectionResult CreateFrom(IReadOnlyList<Mediapipe.Detection> detectionsProto)
{
var detections = new List<Detection>(detectionsProto.Count);
foreach (var detectionProto in detectionsProto)
{
detections.Add(Detection.CreateFrom(detectionProto));
}
return new DetectionResult(detections);
}

public override string ToString()
{
var detectionsStr = string.Join(", ", detections.Select(detection => detection.ToString()));
return $"{{ \"detections\": [{detectionsStr}] }}";
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2023 homuler
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

namespace Mediapipe.Tasks.Components.Containers
{
/// <summary>
/// A keypoint, defined by the coordinates (x, y), normalized by the image dimensions.
/// </summary>
public readonly struct NormalizedKeypoint
{
/// <summary>
/// x in normalized image coordinates.
/// </summary>
public readonly float x;
/// <summary>
/// y in normalized image coordinates.
/// </summary>
public readonly float y;
/// <summary>
/// optional label of the keypoint.
/// </summary>
public readonly string label;
/// <summary>
/// optional score of the keypoint.
/// </summary>
public readonly float? score;

internal NormalizedKeypoint(float x, float y, string label, float? score)
{
this.x = x;
this.y = y;
this.label = label;
this.score = score;
}

public override string ToString()
{
var scoreStr = score == null ? "null" : $"{score}";
return $"{{ \"x\": {x}, \"y\": {y}, \"label\": \"{label}\", \"score\": {scoreStr} }}";
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021 homuler
// Copyright (c) 2023 homuler
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
Expand All @@ -11,6 +11,27 @@

namespace Mediapipe.Tasks.Components.Containers
{
/// <summary>
/// Defines a rectangle, used e.g. as part of detection results or as input region-of-interest.
/// </summary>
public readonly struct Rect
{
public readonly int left;
public readonly int top;
public readonly int right;
public readonly int bottom;

internal Rect(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}

public override string ToString() => $"{{ \"left\": {left}, \"top\": {top}, \"right\": {right}, \"bottom\": {bottom} }}";
}

/// <summary>
/// A rectangle, used as part of detection results or as input region-of-interest.
///
Expand Down Expand Up @@ -43,5 +64,7 @@ bool IEquatable<RectF>.Equals(RectF other)
public override int GetHashCode() => Tuple.Create(left, top, right, bottom).GetHashCode();
public static bool operator ==(RectF lhs, RectF rhs) => lhs.Equals(rhs);
public static bool operator !=(RectF lhs, RectF rhs) => !(lhs == rhs);

public override string ToString() => $"{{ \"left\": {left}, \"top\": {top}, \"right\": {right}, \"bottom\": {bottom} }}";
}
}

0 comments on commit 3eb66c8

Please sign in to comment.