कैलकुलेटर

हर कैलकुलेटर किसी ग्राफ़ का नोड होता है. हमने बताया है कि कैलकुलेटर, कैलकुलेटर शुरू कैसे करें, इसका हिसाब कैसे लगाएं, इनपुट और आउटपुट स्ट्रीम, टाइमस्टैंप, और विकल्प. ग्राफ़ में प्रत्येक नोड Calculator के तौर पर लागू किया गया. ग्राफ़ का बल्क एक्ज़ीक्यूशन, इसके कैलकुलेटर. कैलकुलेटर को शून्य या उससे ज़्यादा इनपुट स्ट्रीम और/या साइड मिल सकती है और शून्य या उससे ज़्यादा आउटपुट स्ट्रीम और/या साइड पैकेट बनाए जाते हैं.

CalculatorBase

कैलकुलेटर बनाने के लिए CalculatorBase क्लास, कई विधियों को लागू करना, और नई सब-क्लास को मीडियापाइप. एक नए कैलकुलेटर को कम से कम इन चार तरीकों का इस्तेमाल करना होगा

  • GetContract()
    • कैलकुलेटर के लेखक, इनपुट और आउटपुट के अनुमानित टाइप तय कर सकते हैं की एक झलक मिलती है. जब कोई ग्राफ़ शुरू होता है, तो फ़्रेमवर्क एक स्टैटिक तरीके से पुष्टि करता है कि कनेक्ट किए गए इनपुट और आउटपुट, इस स्पेसिफ़िकेशन.
  • Open()
    • ग्राफ़ शुरू होने के बाद, फ़्रेमवर्क Open() को कॉल करता है. इनपुट साइड फ़िलहाल, कैलकुलेटर के लिए पैकेट उपलब्ध हैं. Open() अभी तक किसी भी व्यक्ति ने चेक इन नहीं किया है नोड कॉन्फ़िगरेशन ऑपरेशन को समझता है (ग्राफ़ देखें) और कैलकुलेटर के हर ग्राफ़ पर चलने की स्थिति तैयार करता है. यह फ़ंक्शन हो सकता है कैल्क्यूलेटर आउटपुट पर पैकेट भी लिखें. Open() के दौरान हुई कोई गड़बड़ी तो ग्राफ़ चलाना बंद कर देना चाहिए.
  • Process()
    • इनपुट वाले कैलकुलेटर के लिए, फ़्रेमवर्क Process() को बार-बार कॉल करता है जब भी कम से कम एक इनपुट स्ट्रीम में पैकेट उपलब्ध हो. फ़्रेमवर्क डिफ़ॉल्ट रूप से यह गारंटी देता है कि सभी इनपुट का एक ही टाइमस्टैंप है (देखें ज़्यादा जानकारी के लिए सिंक्रोनाइज़ेशन). एक से ज़्यादा पैरलल एक्ज़ीक्यूशन करने पर, Process() कॉल को एक साथ शुरू किया जा सकता है चालू है. अगर Process() के दौरान कोई गड़बड़ी होती है, तो फ़्रेमवर्क Close() और ग्राफ़ चलना खत्म हो जाता है.
  • Close()
    • Process() को किए गए सभी कॉल खत्म होने या सभी इनपुट स्ट्रीम बंद होने के बाद, फ़्रेमवर्क Close() को कॉल करता है. इस फ़ंक्शन को हमेशा तब कॉल किया जाता है, जब Open() को कॉल किया गया और सफल बनाया गया. ग्राफ़ के खत्म होने के बाद भी ऐसा हुआ कोई गड़बड़ी हुई है. किसी भी इनपुट स्ट्रीम के ज़रिए कोई इनपुट उपलब्ध नहीं है Close() के दौरान, लेकिन इसके पास अब भी इनपुट के साइड पैकेट और इसलिए वे आउटपुट लिख सकते हैं. Close() वापस आने के बाद, कैल्क्यूलेटर को एक डेड नोड माना जाना चाहिए. कैल्क्यूलेटर ऑब्जेक्ट इस तरह से नष्ट किया जाता है: ग्राफ़ का काम पूरा होते ही.

कोड स्निपेट यहां दिए गए हैं: CalculatorBase.h.

class CalculatorBase {
 public:
  ...

  // The subclasses of CalculatorBase must implement GetContract.
  // ...
  static absl::Status GetContract(CalculatorContract* cc);

  // Open is called before any Process() calls, on a freshly constructed
  // calculator.  Subclasses may override this method to perform necessary
  // setup, and possibly output Packets and/or set output streams' headers.
  // ...
  virtual absl::Status Open(CalculatorContext* cc) {
    return absl::OkStatus();
  }

  // Processes the incoming inputs. May call the methods on cc to access
  // inputs and produce outputs.
  // ...
  virtual absl::Status Process(CalculatorContext* cc) = 0;

  // Is called if Open() was called and succeeded.  Is called either
  // immediately after processing is complete or after a graph run has ended
  // (if an error occurred in the graph).  ...
  virtual absl::Status Close(CalculatorContext* cc) {
    return absl::OkStatus();
  }

  ...
};

कैलकुलेटर की ज़िंदगी

MediaPipe ग्राफ़ के शुरू होने के दौरान, फ़्रेमवर्क GetContract() स्टैटिक तरीका इस्तेमाल करके यह तय किया जाता है कि किस तरह के पैकेट होने चाहिए.

यह फ़्रेमवर्क हर ग्राफ़ रन के लिए पूरे कैलकुलेटर को बनाता और खत्म करता है (उदाहरण के लिए, हर वीडियो के लिए एक बार या हर इमेज के लिए एक बार). ऐसी महंगी या बड़ी चीज़ें जो बची हुई हों ग्राफ़ पर बने कॉन्स्टेंट को इनपुट साइड पैकेट के रूप में दिया जाना चाहिए, ताकि कैलकुलेशन को बार-बार नहीं चलाया जाता.

शुरू करने के बाद, ग्राफ़ के हर रन के लिए, यह क्रम होता है:

  • Open()
  • Process() (बार-बार)
  • Close()

यह फ़्रेमवर्क, कैलकुलेटर को शुरू करने के लिए, Open() को कॉल करता है. Open() को यह करना चाहिए कोई भी विकल्प देखें और कैलकुलेटर के हर ग्राफ़ पर नज़र आने की स्थिति सेट अप करें. Open() अभी तक किसी भी व्यक्ति ने चेक इन नहीं किया है इनपुट साइड पैकेट पा सकते हैं और कैलकुलेटर आउटपुट पर पैकेट लिख सकते हैं. अगर आपने सही है, तो संभावित पैकेट बफ़रिंग को कम करने के लिए इसे SetOffset() को कॉल करना चाहिए का इस्तेमाल किया जा सकता है.

अगर Open() या Process() के दौरान कोई गड़बड़ी होती है (जैसा कि इनमें से किसी एक के ज़रिए बताया गया है) गैर-Ok स्थिति दे रहा है), तो ग्राफ़ को बिना किसी और कॉल के खत्म कर दिया गया है और कैलकुलेटर को नष्ट कर दिया जाता है.

इनपुट वाले कैलकुलेटर के लिए, फ़्रेमवर्क कम से कम Process() को कॉल करता है एक इनपुट में एक पैकेट उपलब्ध है. फ़्रेमवर्क इस बात की गारंटी देता है कि इनपुट में एक जैसा टाइमस्टैंप, जो Process() और कि सभी पैकेट डिलीवर कर दिए जाएं. इस वजह से, हो सकता है कि कुछ इनपुट में Process() को कॉल करने पर पैकेट. ऐसा इनपुट जिसका पैकेट मौजूद नहीं है बिना टाइमस्टैंप के एक खाली पैकेट बनाओ.

यह फ़्रेमवर्क, Process() को किए गए सभी कॉल के बाद Close() को कॉल करता है. सभी इनपुट में खत्म हो गए हैं, लेकिन Close() के पास इनपुट साइड पैकेट का ऐक्सेस है और हो सकता है आउटपुट लिखें. बंद करने के बाद, कैल्क्यूलेटर खत्म हो जाता है.

बिना इनपुट वाले कैलकुलेटर को सोर्स कहा जाता है. सोर्स कैलकुलेटर Process() को तब तक कॉल रखा जाएगा, जब तक यह Ok स्थिति दिखाता है. ऐप्लिकेशन सोर्स कैलकुलेटर बताता है कि स्टॉप स्टेटस देकर यह खत्म हो गया है (उदाहरण के लिए, mediaPipe::tool::StatusStop().).

इनपुट और आउटपुट की पहचान करना

कैलकुलेटर के सार्वजनिक इंटरफ़ेस में इनपुट स्ट्रीम का एक सेट होता है और आउटपुट स्ट्रीम. CalculatorGraphConfiguration में, कुछ कैलकुलेटर नाम का इस्तेमाल करके अन्य कैलकुलेटर के इनपुट से जुड़े होते हैं स्ट्रीम. आम तौर पर, स्ट्रीम के नाम अंग्रेज़ी के छोटे अक्षरों में होते हैं, जबकि इनपुट और आउटपुट टैग में सामान्य रूप से अपरकेस. नीचे दिए गए उदाहरण में, VIDEO टैग के साथ आउटपुट नाम वाली स्ट्रीम का इस्तेमाल करके VIDEO_IN टैग नाम वाले इनपुट से कनेक्ट किया गया video_stream.

# Graph describing calculator SomeAudioVideoCalculator
node {
  calculator: "SomeAudioVideoCalculator"
  input_stream: "INPUT:combined_input"
  output_stream: "VIDEO:video_stream"
}
node {
  calculator: "SomeVideoCalculator"
  input_stream: "VIDEO_IN:video_stream"
  output_stream: "VIDEO_OUT:processed_video"
}

इनपुट और आउटपुट स्ट्रीम की पहचान इंडेक्स नंबर, टैग के नाम या का कॉम्बिनेशन बनाने के लिए किया जा सकता है. इनपुट के कुछ उदाहरण देखे जा सकते हैं और आउटपुट आइडेंटिफ़ायर, नीचे दिए गए उदाहरण में दिए गए हैं. SomeAudioVideoCalculator पहचान करता है टैग के आधार पर वीडियो आउटपुट और टैग के संयोजन से इसके ऑडियो आउटपुट और इंडेक्स करें. VIDEO टैग वाला इनपुट, इस नाम वाली स्ट्रीम से कनेक्ट है video_stream. AUDIO टैग और इंडेक्स 0 और 1 वाले आउटपुट ये हैं audio_left और audio_right नाम की स्ट्रीम से कनेक्ट किया गया. SomeAudioCalculator अपने ऑडियो इनपुट की पहचान सिर्फ़ इंडेक्स से करता है (किसी टैग की ज़रूरत नहीं है).

# Graph describing calculator SomeAudioVideoCalculator
node {
  calculator: "SomeAudioVideoCalculator"
  input_stream: "combined_input"
  output_stream: "VIDEO:video_stream"
  output_stream: "AUDIO:0:audio_left"
  output_stream: "AUDIO:1:audio_right"
}

node {
  calculator: "SomeAudioCalculator"
  input_stream: "audio_left"
  input_stream: "audio_right"
  output_stream: "audio_energy"
}

कैलकुलेटर लागू करने के दौरान, इनपुट और आउटपुट की पहचान टैग से भी की जाती है नाम और इंडेक्स नंबर. नीचे दिए गए फ़ंक्शन में, इनपुट और आउटपुट की पहचान की गई है:

  • इंडेक्स नंबर से: मिली-जुली इनपुट स्ट्रीम की पहचान, सिर्फ़ इंडेक्स से की जाती है 0.
  • टैग के नाम से: वीडियो आउटपुट स्ट्रीम की पहचान, "VIDEO" टैग से की जाती है.
  • टैग के नाम और इंडेक्स नंबर से: आउटपुट ऑडियो स्ट्रीम की पहचान यह टैग नाम AUDIO और इंडेक्स नंबर 0 और 1 का कॉम्बिनेशन होता है.
// c++ Code snippet describing the SomeAudioVideoCalculator GetContract() method
class SomeAudioVideoCalculator : public CalculatorBase {
 public:
  static absl::Status GetContract(CalculatorContract* cc) {
    cc->Inputs().Index(0).SetAny();
    // SetAny() is used to specify that whatever the type of the
    // stream is, it's acceptable.  This does not mean that any
    // packet is acceptable.  Packets in the stream still have a
    // particular type.  SetAny() has the same effect as explicitly
    // setting the type to be the stream's type.
    cc->Outputs().Tag("VIDEO").Set<ImageFrame>();
    cc->Outputs().Get("AUDIO", 0).Set<Matrix>();
    cc->Outputs().Get("AUDIO", 1).Set<Matrix>();
    return absl::OkStatus();
  }

प्रोसेस जारी है

किसी गैर-स्रोत नोड पर कॉल किए गए Process() को absl::OkStatus() वापस करना होगा बताएं कि सभी चीज़ें ठीक से हुई हैं या किसी गड़बड़ी का संकेत देने के लिए कोई दूसरा स्टेटस कोड है

अगर कोई नॉन-सोर्स कैलकुलेटर tool::StatusStop() दिखाता है, तो यह ग्राफ़ को जल्दी रद्द किया जा रहा है. इस मामले में, सभी सोर्स कैलकुलेटर और ग्राफ़ इनपुट स्ट्रीम बंद हो जाएंगी (और बाकी पैकेट, ग्राफ़).

ग्राफ़ में सोर्स नोड तब तक Process() को कॉल करता रहेगा क्योंकि यह absl::OkStatus( दिखाता है). यह बताने के लिए कि आपके पास कोई और डेटा नहीं है जनरेट किया गया रिटर्न tool::StatusStop(). कोई अन्य स्थिति बताती है कि कोई गड़बड़ी है हुआ.

सफलता दिखाने के लिए Close(), absl::OkStatus() दिखाता है. कोई अन्य स्थिति गड़बड़ी दिखाता है.

यहां बेसिक Process() फ़ंक्शन दिया गया है. यह Input() तरीके का इस्तेमाल करता है (जो ये कर सकता है इसका इस्तेमाल सिर्फ़ तब किया जाना चाहिए, जब कैलकुलेटर में एक इनपुट हो). यह इसके बाद, आउटपुट पैकेट के लिए ज़रूरी मेमोरी तय करने के लिए std::unique_ptr का इस्तेमाल करता है, और कैलकुलेशन करता है. इसे पूरा करने पर, पॉइंटर को जोड़ते समय इसे रिलीज़ कर दिया जाता है आउटपुट स्ट्रीम.

absl::Status MyCalculator::Process() {
  const Matrix& input = Input()->Get<Matrix>();
  std::unique_ptr<Matrix> output(new Matrix(input.rows(), input.cols()));
  // do your magic here....
  //    output->row(n) =  ...
  Output()->Add(output.release(), InputTimestamp());
  return absl::OkStatus();
}

कैलकुलेटर के विकल्प

कैलकुलेटर (1) इनपुट स्ट्रीम पैकेट (2) के ज़रिए प्रोसेसिंग पैरामीटर स्वीकार करते हैं इनपुट साइड पैकेट, और (3) कैलकुलेटर के विकल्प. कैलकुलेटर के विकल्प, अगर बताया गया है, तो node_options फ़ील्ड में लिटरल वैल्यू के तौर पर CalculatorGraphConfiguration.Node मैसेज.

  node {
    calculator: "TfLiteInferenceCalculator"
    input_stream: "TENSORS:main_model_input"
    output_stream: "TENSORS:main_model_output"
    node_options: {
      [type.googleapis.com/mediapipe.TfLiteInferenceCalculatorOptions] {
        model_path: "mediapipe/models/detection_model.tflite"
      }
    }
  }

node_options फ़ील्ड में Proto3 सिंटैक्स इस्तेमाल किया जा सकता है. इसके अलावा, कैलकुलेटर Proto2 सिंटैक्स का इस्तेमाल करके, options फ़ील्ड में विकल्पों की जानकारी दी जा सकती है.

  node {
    calculator: "TfLiteInferenceCalculator"
    input_stream: "TENSORS:main_model_input"
    output_stream: "TENSORS:main_model_output"
    node_options: {
      [type.googleapis.com/mediapipe.TfLiteInferenceCalculatorOptions] {
        model_path: "mediapipe/models/detection_model.tflite"
      }
    }
  }

सभी कैलकुलेटर पर कैलकुलेटर के विकल्प स्वीकार नहीं किए जाते. विकल्पों को स्वीकार करने के लिए, आम तौर पर, कैलकुलेटर एक नया प्रोटोबफ़ मैसेज टाइप तय करता है, जो विकल्प, जैसे कि PacketClonerCalculatorOptions. इसके बाद कैलकुलेटर उस प्रोटोबफ़ संदेश को उसकी CalculatorBase::Open विधि में पढ़ें और शायद इसके CalculatorBase::GetContract फ़ंक्शन या CalculatorBase::Process तरीका. आम तौर पर, नया प्रोटोबफ़ मैसेज टाइप को ".proto" का इस्तेमाल करके, प्रोटोबफ़ स्कीमा के तौर पर बताया जाएगा फ़ाइल और mediapipe_proto_library() बिल्ड का नियम.

  mediapipe_proto_library(
      name = "packet_cloner_calculator_proto",
      srcs = ["packet_cloner_calculator.proto"],
      visibility = ["//visibility:public"],
      deps = [
          "//mediapipe/framework:calculator_options_proto",
          "//mediapipe/framework:calculator_proto",
      ],
  )

कैलकुलेटर का उदाहरण

इस सेक्शन में, PacketClonerCalculator को लागू करने के बारे में चर्चा की गई है. यह अन्य तरीके से आसान काम करता है. इसका इस्तेमाल कई कैलकुलेटर ग्राफ़ में किया जाता है. PacketClonerCalculator, अपने हाल ही के इनपुट पैकेट की कॉपी जनरेट करता है मांग पर.

PacketClonerCalculator का इस्तेमाल करना तब फ़ायदेमंद होता है, जब पहुंचने वाले डेटा पैकेट के टाइमस्टैंप वे सही तरह से अलाइन नहीं होते हैं. मान लीजिए कि हमारे एक कमरे में माइक्रोफ़ोन, लाइट सेंसर और एक वीडियो कैमरा है जो सेंसरी डेटा इकट्ठा कर रहा है. हर सेंसर यह स्वतंत्र रूप से काम करता है और बीच-बीच में डेटा इकट्ठा करता है. मान लें कि आउटपुट हर सेंसर का सेंसर है:

  • माइक्रोफ़ोन = कमरे में आवाज़ के डेसिबल में तेज़ आवाज़ (पूर्णांक)
  • लाइट सेंसर = कमरे की चमक (पूरी संख्या)
  • वीडियो कैमरा = कमरे का आरजीबी इमेज फ़्रेम (ImageFrame)

हमारी सिंपल पर्सेप्शन पाइपलाइन को इन तीन सेंसर के सेंसरी डेटा को प्रोसेस करने के लिए डिज़ाइन किया गया है सेंसर जैसे कि किसी भी समय जब हमारे पास कैमरे से इमेज फ़्रेम का डेटा होता है, इसे माइक्रोफ़ोन की आवाज़ और शोर के आखिरी बार इकट्ठा किए गए डेटा और रोशनी के साथ सिंक किया गया हो सेंसर की रोशनी का डेटा. MediaPipe के साथ ऐसा करने के लिए, हमारी पर्सेप्शन पाइपलाइन में इनपुट स्ट्रीम:

  • Room_mic_signal - इस इनपुट स्ट्रीम में डेटा का हर पैकेट, पूर्णांक डेटा है यह दिखाता है कि टाइमस्टैंप के साथ कमरे में ऑडियो कितनी तेज़ है.
  • Room_lightening_sensor - इस इनपुट स्ट्रीम में डेटा का हर पैकेट, पूर्णांक है इससे पता चलता है कि टाइमस्टैंप की मदद से कमरे में कितनी रोशनी है.
  • Room_video_tick_signal - इस इनपुट स्ट्रीम में डेटा का हर पैकेट वीडियो डेटा का इमेजफ़्रेम, जो कैमरे से इकट्ठा किए गए वीडियो को दर्शाता है टाइमस्टैंप के साथ कमरा.

नीचे PacketClonerCalculator को लागू करने की जानकारी दी गई है. यहां दी गई जानकारी देखने के लिए, GetContract(), Open(), और Process() तरीके के साथ-साथ इंस्टेंस वैरिएबल current_, जिसमें सबसे नए इनपुट पैकेट होते हैं.

// This takes packets from N+1 streams, A_1, A_2, ..., A_N, B.
// For every packet that appears in B, outputs the most recent packet from each
// of the A_i on a separate stream.

#include <vector>

#include "absl/strings/str_cat.h"
#include "mediapipe/framework/calculator_framework.h"

namespace mediapipe {

// For every packet received on the last stream, output the latest packet
// obtained on all other streams. Therefore, if the last stream outputs at a
// higher rate than the others, this effectively clones the packets from the
// other streams to match the last.
//
// Example config:
// node {
//   calculator: "PacketClonerCalculator"
//   input_stream: "first_base_signal"
//   input_stream: "second_base_signal"
//   input_stream: "tick_signal"
//   output_stream: "cloned_first_base_signal"
//   output_stream: "cloned_second_base_signal"
// }
//
class PacketClonerCalculator : public CalculatorBase {
 public:
  static absl::Status GetContract(CalculatorContract* cc) {
    const int tick_signal_index = cc->Inputs().NumEntries() - 1;
    // cc->Inputs().NumEntries() returns the number of input streams
    // for the PacketClonerCalculator
    for (int i = 0; i < tick_signal_index; ++i) {
      cc->Inputs().Index(i).SetAny();
      // cc->Inputs().Index(i) returns the input stream pointer by index
      cc->Outputs().Index(i).SetSameAs(&cc->Inputs().Index(i));
    }
    cc->Inputs().Index(tick_signal_index).SetAny();
    return absl::OkStatus();
  }

  absl::Status Open(CalculatorContext* cc) final {
    tick_signal_index_ = cc->Inputs().NumEntries() - 1;
    current_.resize(tick_signal_index_);
    // Pass along the header for each stream if present.
    for (int i = 0; i < tick_signal_index_; ++i) {
      if (!cc->Inputs().Index(i).Header().IsEmpty()) {
        cc->Outputs().Index(i).SetHeader(cc->Inputs().Index(i).Header());
        // Sets the output stream of index i header to be the same as
        // the header for the input stream of index i
      }
    }
    return absl::OkStatus();
  }

  absl::Status Process(CalculatorContext* cc) final {
    // Store input signals.
    for (int i = 0; i < tick_signal_index_; ++i) {
      if (!cc->Inputs().Index(i).Value().IsEmpty()) {
        current_[i] = cc->Inputs().Index(i).Value();
      }
    }

    // Output if the tick signal is non-empty.
    if (!cc->Inputs().Index(tick_signal_index_).Value().IsEmpty()) {
      for (int i = 0; i < tick_signal_index_; ++i) {
        if (!current_[i].IsEmpty()) {
          cc->Outputs().Index(i).AddPacket(
              current_[i].At(cc->InputTimestamp()));
          // Add a packet to output stream of index i a packet from inputstream i
          // with timestamp common to all present inputs
        } else {
          cc->Outputs().Index(i).SetNextTimestampBound(
              cc->InputTimestamp().NextAllowedInStream());
          // if current_[i], 1 packet buffer for input stream i is empty, we will set
          // next allowed timestamp for input stream i to be current timestamp + 1
        }
      }
    }
    return absl::OkStatus();
  }

 private:
  std::vector<Packet> current_;
  int tick_signal_index_;
};

REGISTER_CALCULATOR(PacketClonerCalculator);
}  // namespace mediapipe

आम तौर पर, कैलकुलेटर में सिर्फ़ .cc फ़ाइल होती है. .h ज़रूरी नहीं है, क्योंकि मीडियापाइप, रजिस्ट्रेशन का इस्तेमाल करके कैलकुलेटर की पहचान करता है. आपके पास इतने समय के बाद तय करें कि आपका कैलकुलेटर क्लास कैसे तय किया जाए. साथ ही, उसे मैक्रो प्रॉम्प्ट के साथ रजिस्टर करें REGISTER_CALCULATOR(calculator_class_name).

नीचे एक छोटा MediaPipe ग्राफ़ दिया गया है, जिसमें तीन इनपुट स्ट्रीम और एक नोड है (PacketClonerCalculator) और दो आउटपुट स्ट्रीम.

input_stream: "room_mic_signal"
input_stream: "room_lighting_sensor"
input_stream: "room_video_tick_signal"

node {
   calculator: "PacketClonerCalculator"
   input_stream: "room_mic_signal"
   input_stream: "room_lighting_sensor"
   input_stream: "room_video_tick_signal"
   output_stream: "cloned_room_mic_signal"
   output_stream: "cloned_lighting_sensor"
 }

नीचे दिया गया डायग्राम दिखाता है कि PacketClonerCalculator अपने आउटपुट को कैसे परिभाषित करता है पैकेट (नीचे), इनपुट पैकेट की सीरीज़ के हिसाब से (सबसे ऊपर).

PacketClonerCalculator का इस्तेमाल करके ग्राफ़ बनाएं
जब भी इसे TICK इनपुट स्ट्रीम पर एक पैकेट मिलता है, तो PacketClonerCalculator अपनी हर इनपुट स्ट्रीम से सबसे हाल का पैकेट देता है. आउटपुट पैकेट (नीचे) का क्रम, इनपुट पैकेट (सबसे ऊपर) और उनके टाइमस्टैंप के क्रम से तय होता है. टाइमस्टैंप, डायग्राम की दाईं ओर दिखाए जाते हैं.