# How to use SPOUT input with 11.10: Computer Vision: Adding Lifespan to Blobs - Processing Tutorial

**URL:** <https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735>\
**Category:** Libraries\
**Created:** [January 29, 2023, 9:59am UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735 "2023-01-29T09:59:18Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![marijn](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@marijn](https://discourse.processing.org/u/marijn)\
**Post date:** [January 29, 2023, 9:59am UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/1 "2023-01-29T09:59:18Z")

</div>

Hi, I am relatively new to Processing.  
I am trying to make the Blobtrack Persistance example (as seen in [this](https://www.youtube.com/watch?v=o1Ob28sF0N8) video) work with Input from SPOUT receiver instead of the webcam input.

Does anybody have an advice for me?

Many thanks in advance,

Marijn

---

<div class="post-metadata">

**Author:** ![Mikey83](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/mikey83/32/17956_2.png) [@Mikey83](https://discourse.processing.org/u/Mikey83)\
**Post date:** [January 29, 2023, 12:00pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/2 "2023-01-29T12:00:33Z")

</div>

Never heard of it but if you goto tools in processing, click add tool, then click library, and search for the spout library to install, then check out the link I sent, it may help you [Pixel Flow - Spout - Processing 2.x and 3.x Forum](https://forum.processing.org/two/discussion/22900/pixel-flow-spout.html)

---

<div class="post-metadata">

**Author:** ![jafal](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/jafal/32/19112_2.png) [@jafal](https://discourse.processing.org/u/jafal)\
**Post date:** [January 29, 2023, 1:15pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/3 "2023-01-29T13:15:25Z")

</div>

Hi  
If my understand to your question is true here  
Some informations may help

> **[GitHub - leadedge/SpoutControls: Control a Sender from a Receiver](https://github.com/leadedge/SpoutControls)**
>
> Control a Sender from a Receiver. Contribute to leadedge/SpoutControls development by creating an account on GitHub.

[![](https://img.youtube.com/vi/1OId5XCWD7A/maxresdefault.jpg "Processing + resolume + spout") ](https://www.youtube.com/watch?v=1OId5XCWD7A)

> **[GitHub - leadedge/SpoutProcessing: Processing library for Spout texture...](https://github.com/leadedge/SpoutProcessing)**
>
> Processing library for Spout texture sharing between applications on Windows - leadedge/SpoutProcessing

---

<div class="post-metadata">

**Author:** ![marijn](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@marijn](https://discourse.processing.org/u/marijn)\
**Post date:** [January 29, 2023, 2:17pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/4 "2023-01-29T14:17:22Z")

</div>

Hello, Thanks for this. It is really interesting.

Altho it does not really answer my problem.

to be more specific:  
The Blobtracking example relies on Capture() functions such as loadPixels(), video.start(), and others.

I am looking to replace this hardware input with Spout. But the problem is, that spout does not have any of these functions. So I need to somehow “convert” it into live videofeed.

I tried to use a virtual camera for this (OBS Virtualcam) but it does not work.  
So the most elegant solution would be to receive SPOUT directly into processing, and to the blobtracking from there.

Does anyone have tips on how to do this “conversion”?

---

<div class="post-metadata">

**Author:** ![marijn](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@marijn](https://discourse.processing.org/u/marijn)\
**Post date:** [January 29, 2023, 2:52pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/5 "2023-01-29T14:52:47Z")

</div>

Hi there,

To give you some more information;  
I am sending a video signal from TouchDesigner into Processing to do blobtracking.  
Using Shiffman’s blobtracking persistence example 11\_10.  
I am trying to use SPOUT receiver instead of a hardware camera.  
But I don’t know how to convert the Spout texture input, to a video format like Capture()  
So that it can be used with functions like GetPixels() and other functions that are used in the blobtracking example.

Greetings, marijn

---

<div class="post-metadata">

**Author:** ![marijn](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@marijn](https://discourse.processing.org/u/marijn)\
**Post date:** [January 29, 2023, 2:57pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/6 "2023-01-29T14:57:41Z")

</div>

[https://github.com/CodingTrain/website-archive/tree/main/Tutorials/Processing/11\_video/sketch\_11\_10\_BlobTracking\_lifespan](https://github.com/CodingTrain/website-archive/tree/main/Tutorials/Processing/11_video/sketch_11_10_BlobTracking_lifespan)

Here’s the blobtracking code  
The main question is: How do I make this work with SPOUT input?

---

<div class="post-metadata">

**Author:** ![glv](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/glv/32/18785_2.png) [@glv](https://discourse.processing.org/u/glv)\
**Post date:** [January 29, 2023, 9:21pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/7 "2023-01-29T21:21:54Z")

</div>

> [@marijn](#):
>
> But I don’t know how to convert the Spout texture input, to a video format like Capture()  
> So that it can be used with functions like GetPixels() and other functions that are used in the blobtracking example.

Hello @marijn,

I thought I would take this out for a spin!

Steps:

- Installed latest _TouchDesigner_ and used the default example and changed output to _TDSSyphonSpoutOut_
- Modified the _SpoutDataReceiver_ example that comes with Spout library added to Processing. Partial code below… I only included setup() and draw().

```auto
// GLV Edit of SpoutDataReceiver in Examples
// 2023-01-29

//
// SpoutDataReceiver
//
// Receive texture and data from a Spout sender
//
// Spout 2.007
// https://spout.zeal.co
//

// IMPORT THE SPOUT LIBRARY
import spout.*;

PImage pgr, pgr1; // Canvas to receive a texture

// DECLARE A SPOUT OBJECT
Spout spout;

void setup()
  {
  // Initial window size
  size(640, 360, P3D);
  
  pgr1 = createImage(width, height, RGB);
  
  // Screen text size
  textSize(16);
  
  // CREATE A NEW SPOUT OBJECT
  spout = new Spout(this);

  frameRate(30.0);
  } 

int count = 0;

void draw() 
  { 
  background(0);  
  //
  // RECEIVE FROM A SENDER
  //
  
  if(spout.receiveTexture()) 
    {        
    pgr = spout.receiveTexture(pgr);
    
    if(pgr != null)
      {
      //image(pgr, 0, 0);  
      imageProcess1();     
      
      showInfo();
      count++;
      }   
    }
  println(frameCount, count); //Check to see if synched 
  }
  
void imageProcess1()
  {
  // Works!  
  //image(pgr, 0, 0);
  //pgr1 = get(); // copy sketch window to pgr1
  
  // Works
  image(pgr, 0, 0);
  loadPixels(); //Required!
  arrayCopy(pixels, pgr1.pixels); // Makes a copy to work with
  
  // Fast and efficient for accessing x, y locations in pixel array
  for(int y=0; y<pgr1.height; y++)
    {
    int tmp = y*pgr1.width;  
    for(int x=0; x<pgr1.width; x++)  
      {
      int loc = x + tmp;
      if (y>height/2)
        {
        if (brightness(pgr1.pixels[loc]) < 128)
          pgr1.pixels[loc] = 0xFFFFFFFF;
        }  
      }
    }
    
  // Efficient for pixel array access
  //for(int i=0; i<pgr1.pixels.length; i++)
  // {
  // if (brightness(pgr1.pixels[i]) < 128)
  // pgr1.pixels[i] = 0xFFFFFFFF;
  // }  
  
  pgr1.updatePixels(); // Required! 
  image(pgr1, width/2, 0);
  }

```

And voila!

Left is spout data received.  
Right is image processed… see code for details.

![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/3X/a/9/a9823f0ca48395b0ed7ca08577956594ee7ee190.jpeg)

 ![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/3X/7/4/7443f42dcec05942fef40922ff390cf06ff706d8.png)

Thanks for this topic! I had fun with this.

References:  
[https://processing.org/tutorials/pixels](https://processing.org/tutorials/pixels)

`:)`

---

<div class="post-metadata">

**Author:** ![marijn](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@marijn](https://discourse.processing.org/u/marijn)\
**Post date:** [January 31, 2023, 1:17pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/8 "2023-01-31T13:17:27Z")

</div>

wow! thank you so much for taking the time to tackle this problem. This is extremely helpful.

I am trying to implement your example into my blobtracking sketch:

I do get the NDI input in!, Everything seems to be working, Except somehow the video does not end up being blobtracked.

What am I missing here?

```auto
// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain
// Code for: https://youtu.be/o1Ob28sF0N8

import processing.video.*;
import spout.*;
PImage pgr, video; 

// DECLARE A SPOUT OBJECT
Spout spout;

int blobCounter = 0;

int maxLife = 50;

color trackColor; 
float threshold = 40;
float distThreshold = 50;

ArrayList<Blob> blobs = new ArrayList<Blob>();

void setup() {
  size(640, 360, P3D);
  //String[] cameras = Capture.list();
 // printArray(cameras);
 
  video = createImage(width, width, RGB);
  spout = new Spout(this);
 
  
  // 183.0 12.0 83.0
  trackColor = color(183, 12, 83);
}

void captureEvent(Capture video) {
  video.read();
}

void keyPressed() {
  if (key == 'a') {
    distThreshold+=5;
  } else if (key == 'z') {
    distThreshold-=5;
  }
  if (key == 's') {
    threshold+=5;
  } else if (key == 'x') {
    threshold-=5;
  }
}

void draw() {
  
  video = spout.receiveTexture(video);
  video.loadPixels();
  image(video, 0, 0);

  ArrayList<Blob> currentBlobs = new ArrayList<Blob>();

  // Begin loop to walk through every pixel
  for (int x = 0; x < video.width; x++ ) {
    for (int y = 0; y < video.height; y++ ) {
      int loc = x + y * video.width;
      // What is current color
      color currentColor = video.pixels[loc];
      float r1 = red(currentColor);
      float g1 = green(currentColor);
      float b1 = blue(currentColor);
      float r2 = red(trackColor);
      float g2 = green(trackColor);
      float b2 = blue(trackColor);

      float d = distSq(r1, g1, b1, r2, g2, b2); 

      if (d < threshold*threshold) {

        boolean found = false;
        for (Blob b : currentBlobs) {
          if (b.isNear(x, y)) {
            b.add(x, y);
            found = true;
            break;
          }
        }

        if (!found) {
          Blob b = new Blob(x, y);
          currentBlobs.add(b);
        }
      }
    }
  }

  for (int i = currentBlobs.size()-1; i >= 0; i--) {
    if (currentBlobs.get(i).size() < 500) {
      currentBlobs.remove(i);
    }
  }

  // There are no blobs!
  if (blobs.isEmpty() && currentBlobs.size() > 0) {
    println("Adding blobs!");
    for (Blob b : currentBlobs) {
      b.id = blobCounter;
      blobs.add(b);
      blobCounter++;
    }
  } else if (blobs.size() <= currentBlobs.size()) {
    // Match whatever blobs you can match
    for (Blob b : blobs) {
      float recordD = 1000;
      Blob matched = null;
      for (Blob cb : currentBlobs) {
        PVector centerB = b.getCenter();
        PVector centerCB = cb.getCenter();         
        float d = PVector.dist(centerB, centerCB);
        if (d < recordD && !cb.taken) {
          recordD = d; 
          matched = cb;
        }
      }
      matched.taken = true;
      b.become(matched);
    }

    // Whatever is leftover make new blobs
    for (Blob b : currentBlobs) {
      if (!b.taken) {
        b.id = blobCounter;
        blobs.add(b);
        blobCounter++;
      }
    }
  } else if (blobs.size() > currentBlobs.size()) {
    for (Blob b : blobs) {
      b.taken = false;
    }

    // Match whatever blobs you can match
    for (Blob cb : currentBlobs) {
      float recordD = 1000;
      Blob matched = null;
      for (Blob b : blobs) {
        PVector centerB = b.getCenter();
        PVector centerCB = cb.getCenter();         
        float d = PVector.dist(centerB, centerCB);
        if (d < recordD && !b.taken) {
          recordD = d; 
          matched = b;
        }
      }
      if (matched != null) {
        matched.taken = true;
        // Resetting the lifespan here is no longer necessary since setting `lifespan = maxLife;` in the become() method in Blob.pde
        // matched.lifespan = maxLife;
        matched.become(cb);
      }
    }

    for (int i = blobs.size() - 1; i >= 0; i--) {
      Blob b = blobs.get(i);
      if (!b.taken) {
        if (b.checkLife()) {
          blobs.remove(i);
        }
      }
    }
  }

  for (Blob b : blobs) {
    b.show();
  } 

  textAlign(RIGHT);
  fill(0);
  //text(currentBlobs.size(), width-10, 40);
  //text(blobs.size(), width-10, 80);
  textSize(24);
  text("color threshold: " + threshold, width-10, 50);  
  text("distance threshold: " + distThreshold, width-10, 25);
  
  
}

float distSq(float x1, float y1, float x2, float y2) {
  float d = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
  return d;
}

float distSq(float x1, float y1, float z1, float x2, float y2, float z2) {
  float d = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) +(z2-z1)*(z2-z1);
  return d;
}

void mousePressed() {
  // Save color where the mouse is clicked in trackColor variable
  int loc = mouseX + mouseY*video.width;
  trackColor = video.pixels[loc];
  println(red(trackColor), green(trackColor), blue(trackColor));
}

```

---

<div class="post-metadata">

**Author:** ![glv](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/glv/32/18785_2.png) [@glv](https://discourse.processing.org/u/glv)\
**Post date:** [February 1, 2023, 12:24am UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/9 "2023-02-01T00:24:01Z")

</div>

Hello @marijn,

I was able to integrate my example:

```auto
// In setup()
video = createImage(640, 360, RGB);

//in draw()
  video = spout.receiveTexture(video);
  image(video, 0, 0);
  loadPixels();
  arrayCopy(pixels, video.pixels); // Makes a copy to work with

```

This will also work:

```auto
void draw() {
  
  video = spout.receiveTexture(video);
  image(video, 0, 0);
  loadPixels();

  ArrayList<Blob> currentBlobs = new ArrayList<Blob>();

  // Begin loop to walk through every pixel
  for (int x = 0; x < width; x++ ) {
    for (int y = 0; y < height; y++ ) {
      int loc = x + y * width;
      // What is current color
      color currentColor = pixels[loc];

// ...

```

If you set the maximum _textSize()_ you are using in _setup()_ the text in _draw()_ won’t be blurry.

![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/3X/7/a/7a7f9630ff9b9dd798e385466ac2de84b482fbb3.jpeg)

`:)`

---

<div class="post-metadata">

**Author:** ![marijn](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@marijn](https://discourse.processing.org/u/marijn)\
**Post date:** [February 1, 2023, 4:58pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/10 "2023-02-01T16:58:27Z")

</div>

Hey @glv thank you very much! It is starting to become more clear to me.  
Somehow I Can not get it to work. I can’t figure out what is different in my code from your example 🤔

```auto
// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain
// Code for: https://youtu.be/o1Ob28sF0N8

import processing.video.*;
import spout.*;
PImage video; 

// DECLARE A SPOUT OBJECT
Spout spout;

int blobCounter = 0;

int maxLife = 50;

color trackColor; 
float threshold = 0;
float distThreshold = 10;

ArrayList<Blob> blobs = new ArrayList<Blob>();

void setup() {
  size(640, 360, P3D);
  //String[] cameras = Capture.list();
 // printArray(cameras);
 
  
  spout = new Spout(this);
  
  // 183.0 12.0 83.0
  trackColor = color(183, 12, 83);
}

//void captureEvent(Capture video) {
// video.read();
//}

void keyPressed() {
  if (key == 'a') {
    distThreshold+=5;
  } else if (key == 'z') {
    distThreshold-=5;
  }
  if (key == 's') {
    threshold+=5;
  } else if (key == 'x') {
    threshold-=5;
  }
}

void draw() {
  
  video = spout.receiveTexture(video);
  image(video, 0, 0);
//arrayCopy(pixels, video.pixels); // Makes a copy to work with
  video.loadPixels();
  //arrayCopy(pixels, video.pixels); // Makes a copy to work with

  ArrayList<Blob> currentBlobs = new ArrayList<Blob>();

  // Begin loop to walk through every pixel
  for (int x = 0; x < video.width; x++ ) {
    for (int y = 0; y < video.height; y++ ) {
      int loc = x + y * video.width;
      // What is current color
      color currentColor = video.pixels[loc];
      float r1 = red(currentColor);
      float g1 = green(currentColor);
      float b1 = blue(currentColor);
      float r2 = red(trackColor);
      float g2 = green(trackColor);
      float b2 = blue(trackColor);

      float d = distSq(r1, g1, b1, r2, g2, b2); 

      if (d < threshold*threshold) {

        boolean found = false;
        for (Blob b : currentBlobs) {
          if (b.isNear(x, y)) {
            b.add(x, y);
            found = true;
            break;
          }
        }

        if (!found) {
          Blob b = new Blob(x, y);
          currentBlobs.add(b);
        }
      }
    }
  }

  for (int i = currentBlobs.size()-1; i >= 0; i--) {
    if (currentBlobs.get(i).size() < 500) {
      currentBlobs.remove(i);
    }
  }

  // There are no blobs!
  if (blobs.isEmpty() && currentBlobs.size() > 0) {
    println("Adding blobs!");
    for (Blob b : currentBlobs) {
      b.id = blobCounter;
      blobs.add(b);
      blobCounter++;
    }
  } else if (blobs.size() <= currentBlobs.size()) {
    // Match whatever blobs you can match
    for (Blob b : blobs) {
      float recordD = 1000;
      Blob matched = null;
      for (Blob cb : currentBlobs) {
        PVector centerB = b.getCenter();
        PVector centerCB = cb.getCenter();         
        float d = PVector.dist(centerB, centerCB);
        if (d < recordD && !cb.taken) {
          recordD = d; 
          matched = cb;
        }
      }
      matched.taken = true;
      b.become(matched);
    }

    // Whatever is leftover make new blobs
    for (Blob b : currentBlobs) {
      if (!b.taken) {
        b.id = blobCounter;
        blobs.add(b);
        blobCounter++;
      }
    }
  } else if (blobs.size() > currentBlobs.size()) {
    for (Blob b : blobs) {
      b.taken = false;
    }

    // Match whatever blobs you can match
    for (Blob cb : currentBlobs) {
      float recordD = 1000;
      Blob matched = null;
      for (Blob b : blobs) {
        PVector centerB = b.getCenter();
        PVector centerCB = cb.getCenter();         
        float d = PVector.dist(centerB, centerCB);
        if (d < recordD && !b.taken) {
          recordD = d; 
          matched = b;
        }
      }
      if (matched != null) {
        matched.taken = true;
        // Resetting the lifespan here is no longer necessary since setting `lifespan = maxLife;` in the become() method in Blob.pde
        // matched.lifespan = maxLife;
        matched.become(cb);
      }
    }

    for (int i = blobs.size() - 1; i >= 0; i--) {
      Blob b = blobs.get(i);
      if (!b.taken) {
        if (b.checkLife()) {
          blobs.remove(i);
        }
      }
    }
  }

  for (Blob b : blobs) {
    b.show();
  } 

  textAlign(RIGHT);
  fill(0);
  //text(currentBlobs.size(), width-10, 40);
  //text(blobs.size(), width-10, 80);
  textSize(24);
  text("color threshold: " + threshold, width-10, 50);  
  text("distance threshold: " + distThreshold, width-10, 25);
  
  
}

float distSq(float x1, float y1, float x2, float y2) {
  float d = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
  return d;
}

float distSq(float x1, float y1, float z1, float x2, float y2, float z2) {
  float d = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) +(z2-z1)*(z2-z1);
  return d;
}

void mousePressed() {
  // Save color where the mouse is clicked in trackColor variable
  int loc = mouseX + mouseY*video.width;
  trackColor = video.pixels[loc];
  println(red(trackColor), green(trackColor), blue(trackColor));
}

```

---

<div class="post-metadata">

**Author:** ![glv](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/glv/32/18785_2.png) [@glv](https://discourse.processing.org/u/glv)\
**Post date:** [February 1, 2023, 10:25pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/11 "2023-02-01T22:25:20Z")

</div>

> [@marijn](#):
>
> I can’t figure out what is different in my code from your example

Ho @marijn,

I updated my last post.

Your code is not the same as mine.  
Scrutinize your code and my example a bit more.

Take a look at the examples that come with the Spout library.

OPTION 4 will work directly with the pixels array and you should be able to integrate with your code:

```auto
    // OPTION 1: Receive and draw the texture
    - see example    
    
   // OPTION 2: Receive into PGraphics
     - see example  

    // OPTION 3: Receive into PImage texture
    - see example  
      
    // OPTION 4: Receive into PImage pixels
    // Note that receiving pixels is slower than receiving
    // a texture alone and depends on the sender size.
    img = spout.receiveImage(img);
    if(img.loaded)
    image(img, 0, 0, width, height);

```

![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/3X/a/2/a24ccfd96977d843e8bb6e4a3fa68de20d8180b9.jpeg)

`:)`

---

<div class="post-metadata">

**Author:** ![marijn](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@marijn](https://discourse.processing.org/u/marijn)\
**Post date:** [February 3, 2023, 1:21pm UTC](https://discourse.processing.org/t/how-to-use-spout-input-with-11-10-computer-vision-adding-lifespan-to-blobs-processing-tutorial/40735/12 "2023-02-03T13:21:23Z")

</div>

hi @glv Thanks a lot for the patient explanation.  
I did manage to make it work thanks to your great examples.

Thanks a lot!
