TrackingSnapshot.java

package org.hammer.audio.experimental.acoustic.tracking;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.hammer.audio.acquisition.SynchronizationAssessment;
import org.hammer.audio.experimental.acoustic.TdoaConsistencyReport;
import org.hammer.audio.experimental.acoustic.wingbeat.ClassificationResult;

/**
 * Visualization-ready output of one {@link TrackingPipeline} step.
 *
 * @param sourceFrameIndex frame index of the analysed audio block
 * @param sourceTimestampNanos capture timestamp of the analysed audio block
 * @param clusters frequency clusters detected in this frame (after multi-peak + clustering),
 *     ordered by total magnitude (descending)
 * @param tracks currently active tracked sources (immutable, sorted by id)
 * @param processingNanos wall-clock processing time of the pipeline for this block
 * @param classificationResults optional per-track classification results, keyed by {@link
 *     TrackedSource#id()}; empty when no classifier has been applied
 * @param synchronization timing quality used for localization in this frame
 * @param tdoaConsistency physical and cycle-consistency evidence for pairwise TDOA estimates
 */
public record TrackingSnapshot(
    long sourceFrameIndex,
    long sourceTimestampNanos,
    List<FrequencyCluster> clusters,
    List<TrackedSource> tracks,
    long processingNanos,
    Map<Integer, ClassificationResult> classificationResults,
    SynchronizationAssessment synchronization,
    TdoaConsistencyReport tdoaConsistency) {

  /* Validate and defensively copy lists and maps. */
  public TrackingSnapshot {
    Objects.requireNonNull(clusters, "clusters");
    Objects.requireNonNull(tracks, "tracks");
    Objects.requireNonNull(classificationResults, "classificationResults");
    Objects.requireNonNull(synchronization, "synchronization");
    Objects.requireNonNull(tdoaConsistency, "tdoaConsistency");
    if (processingNanos < 0L) {
      throw new IllegalArgumentException("processingNanos must be >= 0");
    }
    clusters = List.copyOf(clusters);
    tracks = List.copyOf(tracks);
    classificationResults = Map.copyOf(classificationResults);
  }

  /** Creates a snapshot without explicit pair-consistency evidence. */
  public TrackingSnapshot(
      long sourceFrameIndex,
      long sourceTimestampNanos,
      List<FrequencyCluster> clusters,
      List<TrackedSource> tracks,
      long processingNanos,
      Map<Integer, ClassificationResult> classificationResults,
      SynchronizationAssessment synchronization) {
    this(
        sourceFrameIndex,
        sourceTimestampNanos,
        clusters,
        tracks,
        processingNanos,
        classificationResults,
        synchronization,
        TdoaConsistencyReport.notEvaluated());
  }

  /** Creates a shared-clock snapshot with explicit classification results. */
  public TrackingSnapshot(
      long sourceFrameIndex,
      long sourceTimestampNanos,
      List<FrequencyCluster> clusters,
      List<TrackedSource> tracks,
      long processingNanos,
      Map<Integer, ClassificationResult> classificationResults) {
    this(
        sourceFrameIndex,
        sourceTimestampNanos,
        clusters,
        tracks,
        processingNanos,
        classificationResults,
        SynchronizationAssessment.nominalSharedClock(),
        TdoaConsistencyReport.notEvaluated());
  }

  /**
   * Create a shared-clock snapshot without classification or pair-consistency results.
   *
   * @param sourceFrameIndex frame index of the analysed audio block
   * @param sourceTimestampNanos capture timestamp of the analysed audio block
   * @param clusters frequency clusters detected in this frame
   * @param tracks currently active tracked sources
   * @param processingNanos wall-clock processing time of the pipeline for this block
   */
  public TrackingSnapshot(
      long sourceFrameIndex,
      long sourceTimestampNanos,
      List<FrequencyCluster> clusters,
      List<TrackedSource> tracks,
      long processingNanos) {
    this(
        sourceFrameIndex,
        sourceTimestampNanos,
        clusters,
        tracks,
        processingNanos,
        Map.of(),
        SynchronizationAssessment.nominalSharedClock(),
        TdoaConsistencyReport.notEvaluated());
  }
}