// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Small helper function for RegionFlow.
#ifndef MEDIAPIPE_UTIL_TRACKING_REGION_FLOW_H_
#define MEDIAPIPE_UTIL_TRACKING_REGION_FLOW_H_

#include <algorithm>
#include <cmath>
#include <deque>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/vector.h"
#include "mediapipe/util/tracking/motion_models.h"
#include "mediapipe/util/tracking/region_flow.pb.h"

namespace mediapipe {

typedef RegionFlowFrame::RegionFlow RegionFlow;
typedef std::vector<RegionFlowFeature*> RegionFlowFeatureView;

inline RegionFlowFeature FeatureFromFloats(float x, float y, float dx,
                                           float dy) {
  RegionFlowFeature feat;
  feat.set_x(x);
  feat.set_y(y);
  feat.set_dx(dx);
  feat.set_dy(dy);
  return feat;
}

inline RegionFlowFeature FeatureFromVec2f(const Vector2_f& loc,
                                          const Vector2_f& flow) {
  RegionFlowFeature feat;
  feat.set_x(loc.x());
  feat.set_y(loc.y());
  feat.set_dx(flow.x());
  feat.set_dy(flow.y());
  return feat;
}

inline Vector2_f FeatureFlow(const RegionFlowFeature& feature) {
  return Vector2_f(feature.dx(), feature.dy());
}

inline Vector2_f FeatureLocation(const RegionFlowFeature& feature) {
  return Vector2_f(feature.x(), feature.y());
}

inline Vector2_f FeatureMatchLocation(const RegionFlowFeature& feature) {
  return FeatureLocation(feature) + FeatureFlow(feature);
}

inline Vector2_i FeatureIntLocation(const RegionFlowFeature& feature) {
  return Vector2_i::Cast(FeatureLocation(feature) + Vector2_f(0.5f, 0.5f));
}

inline Vector2_i FeatureMatchIntLocation(const RegionFlowFeature& feature) {
  return Vector2_i::Cast(FeatureMatchLocation(feature) + Vector2_f(0.5, 0.5f));
}

// Returns L1 norm of color standard deviation of feature descriptor,
// -1 if descriptor information is not present
// (e.g. if ComputeRegionFlowFeatureDescriptors was not called previously).
// Specifically returns stdev_red + stdev_blue + stdev_green.
inline float PatchDescriptorColorStdevL1(const PatchDescriptor& descriptor) {
  constexpr int kRedIdx = 3;
  constexpr int kGreenIdx = 6;
  constexpr int kBlueIdx = 8;
  DCHECK_GE(descriptor.data(kRedIdx), 0);
  DCHECK_GE(descriptor.data(kGreenIdx), 0);
  DCHECK_GE(descriptor.data(kBlueIdx), 0);

  if (descriptor.data_size() > kBlueIdx) {
    return std::sqrt(descriptor.data(kRedIdx)) +
           std::sqrt(descriptor.data(kGreenIdx)) +
           std::sqrt(descriptor.data(kBlueIdx));
  } else {
    return -1.0f;
  }
}

// Extracts features from region flow. Set distance_from_border > 0 to ensure
// feature and matched location are at least the specified distance away
// from the frame rectangle (test is not executed if distance_from_border <= 0),
// so that feature descriptors can be computed (see function below).
void GetRegionFlowFeatureList(const RegionFlowFrame& flow_frame,
                              int distance_from_border,
                              RegionFlowFeatureList* flow_feature_list);

// Returns L2 norm of difference of mean color (first 3 dimensions of
// feature descriptors).
float RegionFlowFeatureDistance(const PatchDescriptor& patch_desc_1,
                                const PatchDescriptor& patch_desc_2);

// Resets IRLS weight of each RegionFlowFeature to value.
void ResetRegionFlowFeatureIRLSWeights(
    float value, RegionFlowFeatureList* flow_feature_list);

// Returns sum of feature's irls weights.
double RegionFlowFeatureIRLSSum(const RegionFlowFeatureList& feature_list);

// Computes per region flow feature texturedness score. Score is within [0, 1],
// where 0 means low texture and 1 high texture. Requires for each feature
// descriptor to be computed (via ComputeRegionFlowFeatureDescriptors). If
// missing, LOG(WARNING) is issued and value defaults to 1.
// If use_15percent_as_max is set, score is scaled and threshold back to [0, 1]
// such that 1 is assumed at 15% of maximum PER channel variance.
void ComputeRegionFlowFeatureTexturedness(
    const RegionFlowFeatureList& region_flow_feature_list,
    bool use_15percent_as_max, std::vector<float>* texturedness);

// IRLS weights are multiplied by inverse texturedness (expressed as variance
// of feature descriptor), effectively upweighting outliers if in low textured
// areas. Features with texturedness below low_texture_threshold can be
// optionally clamped to low_texture_outlier_clamp (set to -1 for no clamping).
void TextureFilteredRegionFlowFeatureIRLSWeights(
    float low_texture_threshold, float low_texture_outlier_clamp,
    RegionFlowFeatureList* flow_feature_list);

// Same as above but normalizes w.r.t. corner response.
void CornerFilteredRegionFlowFeatureIRLSWeights(
    float low_corner_threshold, float low_corner_outlier_clamp,
    RegionFlowFeatureList* flow_feature_list);

// Simple setter and getter methods for irls weights.
void GetRegionFlowFeatureIRLSWeights(
    const RegionFlowFeatureList& flow_feature_list,
    std::vector<float>* irls_weights);

void SetRegionFlowFeatureIRLSWeights(const std::vector<float>& irls_weights,
                                     RegionFlowFeatureList* flow_feature_list);

// Counts number of region flow features with an irls weight of less than or
// equal to threshold.
int CountIgnoredRegionFlowFeatures(
    const RegionFlowFeatureList& flow_feature_list, float threshold);

// Locates region with id region_id in RegionFlowFrame via binary search.
// Returns NULL if no region with specied region_id is present.
const RegionFlow* GetRegionFlowById(int region_id,
                                    const RegionFlowFrame& flow_frame);

// Same as above for mutable RegionFlow.
RegionFlow* GetMutableRegionFlowById(int region_id,
                                     RegionFlowFrame* flow_frame);

void SortRegionFlowById(RegionFlowFrame* flow_frame);

// Switches each feature with its correspondence, i.e.
// (x, y) <-> (x + dx, x + dy), (dx, dy) -> (-dx, -dy). Same holds for centroid
// and mean flow. Note: Member fundamental_matrix is invalid after inversion,
// FeaturePointLists are not affected from inversion.
void InvertRegionFlow(const RegionFlowFrame& flow_frame,
                      RegionFlowFrame* inverted_flow_frame);

// Same as above for feature lists.
void InvertRegionFlowFeatureList(const RegionFlowFeatureList& feature_list,
                                 RegionFlowFeatureList* inverted_feature_list);

// Inverts a single feature.
void InvertRegionFlowFeature(RegionFlowFeature* feature);

// Removes features that are out bounds of the domain:
// [bounds, frame_width - bounds] x [bounds, frame_height - bounds].
void LimitFeaturesToBounds(int frame_width, int frame_height, float bounds,
                           RegionFlowFeatureList* feature_list);

// List of saliency points for each frame.
typedef std::deque<SalientPointFrame> SaliencyPointList;

// Normalizes region flow by frame diameter, i.e. uniform downscale such that
// feature list fits within [0, 1].
void NormalizeRegionFlowFeatureList(RegionFlowFeatureList* feature_list);
// Inverse of above operation.
void DeNormalizeRegionFlowFeatureList(RegionFlowFeatureList* feature_list);

// Templated implementations.
// Applies model to each feature and displacement vector.
template <class Model>
void TransformRegionFlowFeatureList(const Model& model,
                                    RegionFlowFeatureList* flow_feature_list) {
  for (auto& feature : *flow_feature_list->mutable_feature()) {
    Vector2_f pt =
        ModelAdapter<Model>::TransformPoint(model, FeatureLocation(feature));
    Vector2_f match = ModelAdapter<Model>::TransformPoint(
        model, FeatureMatchLocation(feature));
    feature.set_x(pt.x());
    feature.set_y(pt.y());
    feature.set_dx(match.x() - pt.x());
    feature.set_dy(match.y() - pt.y());
  }
}

// Similar to above but applies transformation to each feature to derive
// matching location, according to the formula:
// (dx, dy) <-- a * (transformed location - location) + b * (dx, dy)
// e.g. with b = 0, and a = 1, (dx, dy) is replaced with distance
// between transformed location and original location.
// If set_match == true, the original feature location is replaced
// with its matching location.
template <class Model>
void RegionFlowFeatureListViaTransform(
    const Model& model, RegionFlowFeatureList* flow_feature_list, float a,
    float b, bool set_match, const MixtureRowWeights* row_weights = nullptr) {
  for (auto& feature : *flow_feature_list->mutable_feature()) {
    Vector2_f match =
        ModelAdapter<Model>::TransformPoint(model, FeatureLocation(feature));
    feature.set_dx(b * feature.dx() + a * (match.x() - feature.x()));
    feature.set_dy(b * feature.dy() + a * (match.y() - feature.y()));
    if (set_match) {
      feature.set_x(match.x());
      feature.set_y(match.y());
    }
  }
}

template <>
inline void RegionFlowFeatureListViaTransform(
    const MixtureHomography& mix, RegionFlowFeatureList* flow_feature_list,
    float a, float b, bool set_match, const MixtureRowWeights* row_weights) {
  CHECK(row_weights) << "Row weights required for mixtures.";

  for (auto& feature : *flow_feature_list->mutable_feature()) {
    const float* weights = row_weights->RowWeights(feature.y());
    Vector2_f match = MixtureHomographyAdapter::TransformPoint(
        mix, weights, FeatureLocation(feature));
    feature.set_dx(b * feature.dx() + a * (match.x() - feature.x()));
    feature.set_dy(b * feature.dy() + a * (match.y() - feature.y()));
    if (set_match) {
      feature.set_x(match.x());
      feature.set_y(match.y());
    }
  }
}

// Helper implementation function for functions below.
// Returns pair of <filtered weight, predicate result> where predicate result is
// the boolean result of applying the predicate to the feature.
template <class Predicate>
std::pair<float, bool> GetFilteredWeightImpl(const Predicate& predicate,
                                             float reset_value,
                                             const RegionFlowFeature& feature) {
  if (feature.irls_weight() == 0.0f) {
    return std::make_pair(0.0f, false);  // Zero is false by default.
  } else if (!predicate(feature)) {
    return std::make_pair(reset_value, false);
  } else {
    return std::make_pair(feature.irls_weight(), true);
  }
}

// If predicate evaluates to false, corresponding irls weight is set to zero.
// Returns number of features with non-zero irls weight.
// Interface for predicate: bool operator()(const RegionFlowFeature&) const
// Example: Predicate compares registration error of a feature under some linear
// model and returns true if error is below some threshold. Consequently all
// features having registration error above the threshold are set to weight zero
// effectively ignoring those features during estimation.
template <class Predicate>
int FilterRegionFlowFeatureList(const Predicate& predicate, float reset_value,
                                RegionFlowFeatureList* flow_feature_list) {
  CHECK(flow_feature_list != nullptr);
  int num_passing_features = 0;
  for (auto& feature : *flow_feature_list->mutable_feature()) {
    std::pair<float, bool> filter_result =
        GetFilteredWeightImpl(predicate, reset_value, feature);
    feature.set_irls_weight(filter_result.first);
    if (filter_result.second) {
      ++num_passing_features;
    }
  }

  return num_passing_features;
}

// Same function as above, but instead of setting the corresponding weights,
// returns resulting weights in a float vector.
template <class Predicate>
int FilterRegionFlowFeatureWeights(const Predicate& predicate,
                                   float reset_value,
                                   const RegionFlowFeatureList& feature_list,
                                   std::vector<float>* result_weights) {
  CHECK(result_weights != nullptr);
  result_weights->clear();

  int num_passing_features = 0;
  for (auto feature : feature_list.feature()) {
    std::pair<float, bool> filter_result =
        GetFilteredWeightImpl(predicate, reset_value, feature);
    result_weights->push_back(filter_result.first);
    if (filter_result.second) {
      ++num_passing_features;
    }
  }

  return num_passing_features;
}

// Select features from the passed list for which the predicate is true.
// Returned view contains pointers to mutable features.
template <class Predicate>
void SelectFeaturesFromList(const Predicate& predicate,
                            RegionFlowFeatureList* feature_list,
                            RegionFlowFeatureView* feature_view) {
  CHECK(feature_list != nullptr);
  CHECK(feature_view != nullptr);
  for (auto& feature : *feature_list->mutable_feature()) {
    if (predicate(feature)) {
      feature_view->push_back(&feature);
    }
  }
}

inline void SelectAllFeaturesFromList(RegionFlowFeatureList* feature_list,
                                      RegionFlowFeatureView* feature_view) {
  CHECK(feature_list != nullptr);
  CHECK(feature_view != nullptr);
  for (auto& feature : *feature_list->mutable_feature()) {
    feature_view->push_back(&feature);
  }
}

// Sorts region flow feature views, w.r.t. predicate. Predicate must define:
// bool operator()(const RegionFlowFeature* lhs,
//                 const RegionFlowFeature* rhs) const;
template <class Predicate>
void SortRegionFlowFeatureView(const Predicate& predicate,
                               RegionFlowFeatureView* feature_view) {
  CHECK(feature_view != nullptr);
  std::sort(feature_view->begin(), feature_view->end(), predicate);
}

// Clamps IRLS weight of each RegionFlowFeature to lie within [lower, upper].
void ClampRegionFlowFeatureIRLSWeights(
    float lower, float upper, RegionFlowFeatureView* flow_feature_list);

// Makes a copy of src to dest without copying any features, i.e. dest will
// have the same values as src for all field except the actual features.
// Implemented by temporarily swapping features in and out, therefore source has
// to be mutable but will not be modified.
void CopyToEmptyFeatureList(RegionFlowFeatureList* src,
                            RegionFlowFeatureList* dst);

// Intersects passed RegionFlowFeatureLists based on track_id, returning new
// RegionFlowFeatureList indicating new motion from features location in list
// 'from' to feature's location in list 'to' (specified by to_location
// function, e.g. pass FeatureLocation or FeatureMatchLocation here).
// Requires RegionFlowFeatureList's computed with long_tracks.
// Output result is initialized to contain all fields from input from (same
// holds for intersected features, minus their match location).
// For performance reasons, from is passed at mutable pointer to use above
// CopyToEmptyFeatureList, but is not modified on output.
// Optionally outputs source index for each feature in result into feature
// array of from.
void IntersectRegionFlowFeatureList(
    const RegionFlowFeatureList& to,
    std::function<Vector2_f(const RegionFlowFeature&)> to_location_eval,
    RegionFlowFeatureList* from, RegionFlowFeatureList* result,
    std::vector<int>* source_indices = nullptr);

// Streaming representation for long feature tracks. Ingests
// RegionFlowFeatureList for passed frames and maps them to their corresponding
// track id.
// Usage example:
// LongFeatureStream stream;
// for (int f = 0; f < frames; ++f) {
//   RegionFlowFeatureList feature_list = ...   // from input.
//   stream.AddFeatures(feature_list, true, true);
//
//   // Traverse tracks starting at the current frame f (going backwards in
//   // time).
//   for (const auto& track : stream) {
//     track.first    // Holds id.
//     track.second   // Holds vector<RegionFlowFeature>, most recent one are at
//                    // the end.
//     Convert track to a list of points.
//     vector<Vector2_f> poly_line;
//     stream.FlattenTrack(track.second, &poly_line, nullptr, nullptr);
//
//     // Returned track where poly_line[0] is the beginning (oldest) and
//     // poly_line[poly_line.size() - 1] the end of the track (most recent
//     // point).
//
//     for (const Vector2_f point : poly_line) {
//       // ... do something ...
//     }
//   }
class LongFeatureStream {
 private:
  // Buffers features according to their track id. Most recent region flow
  // features are added last.
  typedef std::unordered_map<int, std::vector<RegionFlowFeature>> TrackBuffer;

 public:
  // Default constructor for LongFeatureStream. The default long feature stream
  // is backward.
  LongFeatureStream() = default;

  // Constructor for LongFeatureStream. The param forward indicates if the long
  // feature stream is forward or backward.
  explicit LongFeatureStream(bool forward) : forward_(forward) {}

  // Adds new features for the current frame. Region flow must be computed
  // w.r.t to previous or next frame (i.e. inter-frame distance = 1, CHECKED).
  // If check_connectivity is specified, CHECKS if a feature's match location
  // equals it last known location in the buffer.
  // Optionally removes features from the buffer that are not present
  // in the current list.
  void AddFeatures(const RegionFlowFeatureList& feature_list,
                   bool check_connectivity, bool purge_non_present_features);

  // Traversal example:
  // LongFeatureStream stream;
  // for (auto track : stream) {
  //   track.first    // Holds id.
  //   track.second   // Holds vector<RegionFlowFeature>.
  //                  // Note: These are always backward flow features
  //                  //       even if you added forward ones. Ordered in time,
  //                  //       oldest features come first.
  //   vector<Vector2_f> poly_line;
  //   stream.FlattenTrack(track.second, &poly_line, nullptr, nullptr);
  // }
  typename TrackBuffer::const_iterator begin() const { return tracks_.begin(); }
  typename TrackBuffer::const_iterator end() const { return tracks_.end(); }

  // Extract track as poly-line (vector of positions).
  // Specifically, tracks[0] is the beginning (oldest) and
  // tracks[tracks.size() - 1] the end of the track (most recent point).
  // Optionally, returns irls weight for each point pair along the track,
  // i.e weight at position N, specifies weight of track between points
  // N and N + 1. For convenience, weight at last position is replicated, i.e.
  // length of tracks and irls_weight is identical.
  // Optionally, returns the flow vector associated with each point on the
  // track with a direction as requested by the flow direction of the
  // constructor. Note: For N points, N - 1 flow vectors are returned.
  void FlattenTrack(const std::vector<RegionFlowFeature>& features,
                    std::vector<Vector2_f>* tracks,
                    std::vector<float>* irls_weight,      // optional.
                    std::vector<Vector2_f>* flow) const;  // optional.

  // Random access. Returns nullptr if not found.
  const std::vector<RegionFlowFeature>* TrackById(int id) const;

  // Convenience function calling TrackById and FlattenTrack. Returns empty
  // vector if track id is not present.
  std::vector<Vector2_f> FlattenedTrackById(int id) const;

 private:
  // Long Feature tracks indexed by id.
  TrackBuffer tracks_;

  // Stores old ids that have been removed. Used during check_connectivity.
  std::unordered_set<int> old_ids_;

  // A flag indicating if the long feature stream is forward or backward.
  bool forward_ = false;
};

// Helper class for testing which features are present, computing overall track
// length and other statistics.
// Usage:
//   LongFeatureInfo lfi;
//   std::vector<RegionFlowFeatureList> feature_lists = FROM_OUTSIDE
//   for (const auto& feature_list : feature_lists) {
//     lfi.AddFeatures(feature_list);
//     // Get track length for each feature, so far.
//     std::vector<int> track_length;
//     lfi.TrackLenghts(feature_list, &track_length);
//  }
class LongFeatureInfo {
 public:
  // Adds features to current info state.
  void AddFeatures(const RegionFlowFeatureList& feature_list);

  // Adds a single feature. If used instead of above function, requires
  // IncrementFrame to be called manually.
  void AddFeature(const RegionFlowFeature& feature);

  // Returns track length for each passed feature.
  // Note: If feature is not yet present, zero is returned as length.
  void TrackLengths(const RegionFlowFeatureList& feature_list,
                    std::vector<int>* track_lengths) const;

  // Same as above for an individual feature.
  int TrackLength(const RegionFlowFeature& feature) const;

  // Returns starting frame for a feature.
  int TrackStart(const RegionFlowFeature& feature) const;

  int NumFrames() const { return num_frames_; }

  void Reset();

  // Returns track length at passed percentile across all tracks added so far.
  int GlobalTrackLength(float percentile) const;

  void IncrementFrame() { ++num_frames_; }

 private:
  struct TrackInfo {
    int length = 0;
    int start = 0;
  };

  // Maps track id to above info struct.
  std::unordered_map<int, TrackInfo> track_info_;

  int num_frames_ = 0;
};

// Scales a salient point in x and y by specified scales. For example, use to
// map a salient point to a specific frame width and height.
void ScaleSalientPoint(float scale_x, float scale_y,
                       SalientPoint* salient_point);

// Scales salient points in saliency by factor scale. If normalize_to_scale
// is true, the weights of salient points per frame sum up to scale.
void ScaleSalientPointFrame(float scale, bool normalize_to_scale,
                            SalientPointFrame* saliency);

// Convenience function for SaliencyPointList's invoking above function on each
// frame.
void ScaleSaliencyList(float scale, bool normalize_to_scale,
                       SaliencyPointList* saliency_list);

// Resets the normalized bounds of salient points in saliency list to
// specified bounds.
void ResetSaliencyBounds(float left, float bottom, float right, float top,
                         SaliencyPointList* saliency_list);

// Returns major and minor axis from covariance matrix (scaled by one sigma) in
// each direction) and angle of major axis (in radians, counter clockwise).
// Returns false if degenerate (ellipses with less than 2 pixels in diameter
// in either direction).
// Assuming 2x2 Covariance matrix of the form [a  bc
//                                             bc  d]
// for variances specified in pixels^2.
bool EllipseFromCovariance(float a, float bc, float d,
                           Vector2_f* axis_magnitude, float* angle);

// Calculate the bounding box from an axis aligned ellipse defined by the major
// and minor axis. Returns 4 corners of a minimal bounding box.
void BoundingBoxFromEllipse(const Vector2_f& center, float norm_major_axis,
                            float norm_minor_axis, float angle,
                            std::vector<Vector2_f>* bounding_box);

// Helper function used by BuildFeature grid to determine the sample taps
// for a domain of size dim_x x dim_y with a specified tap_radius.
void GridTaps(int dim_x, int dim_y, int tap_radius,
              std::vector<std::vector<int>>* taps);

// Generic function to bin features (of generic type Feature, specified for a
// set of frames) over the domain frame_width x frame_height into equally
// sized square bins of size grid_resolution x grid_resolution.
// Function outputs for each bin the indicies of neighboring bins according
// 3x3 or 5x5 neighborhood centered at that bin. Also output number of bins
// created along each dimension as 2 dim vector (x, y).
// FeatureEvaluator should implement:
// Vector2_f operator()(const Feature& f) const {
//   // Returns spatial location for feature f;
// }
template <class Feature>
using FeatureFrame = std::vector<Feature*>;

template <class Feature>
using FeatureGrid = std::vector<FeatureFrame<Feature>>;

template <class Feature, class FeatureEvaluator>
void BuildFeatureGrid(
    float frame_width, float frame_height, float grid_resolution,
    const std::vector<FeatureFrame<Feature>>& feature_views,
    const FeatureEvaluator& evaluator,
    std::vector<std::vector<int>>* feature_taps_3,  // Optional.
    std::vector<std::vector<int>>* feature_taps_5,  // Optional.
    Vector2_i* num_grid_bins,                       // Optional.
    std::vector<FeatureGrid<Feature>>* feature_grids) {
  CHECK(feature_grids);
  CHECK_GT(grid_resolution, 0.0f);

  const int num_frames = feature_views.size();
  const int grid_dim_x = std::ceil(frame_width / grid_resolution);
  const int grid_dim_y = std::ceil(frame_height / grid_resolution);
  const int grid_size = grid_dim_x * grid_dim_y;
  const float grid_scale = 1.0f / grid_resolution;

  // Pre-compute neighbor grids.
  feature_grids->clear();
  feature_grids->resize(num_frames);
  for (int f = 0; f < num_frames; ++f) {
    // Populate.
    auto& curr_grid = (*feature_grids)[f];
    curr_grid.resize(grid_size);
    const FeatureFrame<Feature>& curr_view = feature_views[f];
    for (int i = 0, size = curr_view.size(); i < size; ++i) {
      Feature* feature = curr_view[i];
      Vector2_f feature_loc = evaluator(*feature);
      const int x = feature_loc.x() * grid_scale;
      const int y = feature_loc.y() * grid_scale;
      DCHECK_LT(y, grid_dim_y);
      DCHECK_LT(x, grid_dim_x);
      const int grid_loc = y * grid_dim_x + x;
      curr_grid[grid_loc].push_back(feature);
    }
  }

  if (feature_taps_3 != NULL) {
    GridTaps(grid_dim_x, grid_dim_y, 1, feature_taps_3);
  }
  if (feature_taps_5 != NULL) {
    GridTaps(grid_dim_x, grid_dim_y, 2, feature_taps_5);
  }

  if (num_grid_bins) {
    *num_grid_bins = Vector2_i(grid_dim_x, grid_dim_y);
  }
}

}  // namespace mediapipe

#endif  // MEDIAPIPE_UTIL_TRACKING_REGION_FLOW_H_
