Who Stole my cat's food?

I am trying to build a system that will monitor my cat’s food bowl. Squirrels and rats steal from there all the time.
System overview:
An arduino with motion detectors is overlooking the bowl. When presence is detected, the arduino sends a serial command to the PC by USB. At that time, processing sketch is starting a SaveFrame… etc for about 10 sec.
THe system described is OK, but is late. I would like to see how the rat is coming in my cat’s room in the first place. I would like to save the last 30 sec before the event happens.

For that I would want to:

  1. Record all frames continuously but delete the ones that are NOT the last 900 frames (30sec).
    Is there a file management for that?
  2. If the event is triggered by the arduino, I would like to move the last 900 frames in a new directory for later examination.
    Again, a file management would come handy to move the files in another directory.
    So at the end of 24 hours I would end up with 2-3 30 second clips to examine based on the 2-3 triggered events.

I can handle well: Arduino presence detection and USB communication.
Need suggestion on saving and moving the batches of saved frames.

Thanks
Mitch

hard drive access is expensive.

You could store the images in the RAM (ArrayList) that you would overwrite throughout

in case of the event, you make new folder with date time stamp and store to hard drive

also a RAM Disk usage might be a way for file storage.
( like all ad-hoc data collections ( using python ) on Raspberry Pi i do there / by default available RPI3B /run/shm/ ?60MB … 450MB? )

for the file and overwriting the operating system will take care, if you choose right name.
but as that will give a kind of ring buffer you need to know where it starts/ends.

String fn0 = "data/snap";
int x = 0, xend = 900;
String fn1 = nf(x, 3);
String fn2 = ".png";
String outfile = fn0+fn1+fn2;
String pointfile = "data/pointer.txt";
boolean record = true;

void setup() {
  println("recording: "+record);
}

void draw() {
  background(200, 200, 0);
  recording();
}

void recording() {
  if ( record ) {
    x++; 
    if ( x >= xend ) x = 0;
    fn1 = nf(x, 3);
    outfile = fn0+fn1+fn2;
    text(outfile, 0, 10);
    save(outfile);
    String[] info = {outfile, ""};
    saveStrings(pointfile, info);
  }
}

void mousePressed() {
  if ( record ) { 
    record = false;
    println("last: "+outfile);
    // here should copy the whole data directory OR make the movie
  } else { 
    record = true; 
    x = 0;
  }
  println("recording: "+record);
}

that gives 900 image files and one pointer file.
now, that you do all the time as a recording of the last 900 ( here draw loops )
and in case of a event you can wait n sec ( or do 450 loops more ) and then stop that storing…
in above example i only on mouse click stop / start

1 Like

KLL

Thanks for the guidance and help. I am not your sharpest programmer, so please bear with me. Your code does not include any “saveFrame” so I dont really know what to do.

Here is what I implemented. It does save 900 frames after the trigger and it stops. Still struggling how to save 900 frames before the serial trigger.

Thanks

/**
 * Getting Started with Capture.
 * 
 * Reading and displaying an image from an attached Capture device. 
 */

import processing.video.*;
import java.awt.Font;
import java.awt.Rectangle;
PFont f;
PFont font;
int w= #FFFFFF; // white
int y= #FCF442; //yellow
int g= #A0FFA3; //green
int b= #64E1FF; // blue
int m= #CC15D3; // Magenta
int o= #FF6C67;  // orange
int i= #B767FF; // indigo
int r= #FC0B03; // red
int bk = (#212121);        
import processing.serial.*;
Serial ArduCOM_Port; 

Capture cam;
int IncomingByte;

String fn0 = "data/snap";
int x = 0, xend = 200;
String fn1 = nf(x, 3);
String fn2 = ".png";
String outfile = fn0+fn1+fn2;
String pointfile = "data/pointer.txt";
boolean record = true;

void setup() {
    size (640, 480, JAVA2D);
     font = loadFont("AgencyFB-Bold-200.vlw");

  String[] cameras = Capture.list();

  if (cameras == null) {
    println("Failed to retrieve the list of available cameras, will try the default...");
    cam = new Capture(this, 640, 480);
  } if (cameras.length == 0) {
    println("There are no cameras available for capture.");
    exit();
  } else {
    println("Available cameras:");
    printArray(cameras);

    // The camera can be initialized directly using an element
    // from the array returned by list():
    cam = new Capture(this, cameras[7]);
    // Or, the settings can be defined based on the text in the list
    //cam = new Capture(this, 640, 480, "Built-in iSight", 30);
    
    // Start capturing the images from the camera
    cam.start();
    
  }
  frameRate(30);
    ArduCOM_Port = new Serial(this, "COM4", 115200);
    record=false;
}  //end setup

void draw() {
 // background(bk); 
  if (cam.available() == true) {
    cam.read();
  }
  image(cam, 0, 0, 640, 480);
  
  if ( ArduCOM_Port.available() > 0 ) { 
    IncomingByte = ArduCOM_Port.read(); 
    println (IncomingByte);
    if (IncomingByte==1)
    record=true;
    else 
    record=false;
    
  }
    
    
   textFont(font, 35); 
  fill(r);
  
   if (record)
   {saveFrame("Demos/Demos-######.png");
    x++; 
    text( "R ",10,460);
    if ( x >= xend ) 
    {x = 0;
    IncomingByte=0;
    record=false;
    }
   }
   
  else
  {text( "M ",10,460);
  }
}

void keyPressed() {
  if (key == CODED) {
     background(bk); 
    if (keyCode == UP) {
       textFont(font, 35); 
      fill(r);
      record= true;
      
    } else if (keyCode == DOWN) {
       record= false;
      text( "M",10,460);
    }
  }
}

the idea was to record all the time, and at THE EVENT
stop overwriting the files / copy the directory / make the movie …

  • that requires also to take the above RAM usage ideas
    images in RAM or files in RAM DISK, seriously
    as it could damage your drive

but you start the recording at THE EVENT
what will never give info about the time prior


your arduino code seems to also directly set record true/false
that would never allow any time frame thinking


also i tested here:

    //save(outfile);
    saveFrame(outfile);

same result.

i assume you not run my code as posted above?


also you could guess that i love cats too,
so can you post some photos of yours?

Here is stripping the concept down to almost the simplest possible ring buffer for saveFrame.

// https://discourse.processing.org/t/who-stole-my-cats-food/13895/6
int clip = 0;
int bufferCount = 6;

void setup() {
  frameRate(2);
  colorMode(HSB,90);
  strokeWeight(5);
}

void draw() {
  // draw something
  translate(frameCount*4%width, 0);
  stroke(frameCount%60,100,100);
  line(0,0,0,height);

  // save to a buffer file --
  // % is what loops across the files
  String frameName = str(clip)+"_"+str(frameCount % bufferCount);
  saveFrame(frameName+".png");
}

void keyReleased() {
  // advance the clip file name part --
  // this automatically "saves" the old buffer
  // because those files are no longer overwritten
  clip++;
}

This doesn’t rename the files on disk – it just keeps overwriting them in a ring, then switches to a new buffer when you trigger an event.

For the next step you can rename the files when you finalize the buffer (e.g. on the event) to order them correctly – although honestly, if you are checking pngs for rats you can probably just sort your file browser by date modified.

1 Like

Thank you.
I got it to work with serial input, I am just incrementing the folder name on that date with each serial event.

 if ( ArduCOM_Port.available() > 0 ) { 
    IncomingByte = ArduCOM_Port.read(); 
    println (IncomingByte);
    if (IncomingByte==1)
    {
      println("last: "+outfile);
      ClipIncr++;
      IncomingByte=0;
    }
  }

void recording() {
  if ( record ) {
    x++; 
    if ( x >= xend ) 
    {
      x = 0;
    }
    TimeStampFun();
    // println (TimeStamp);
    fn1 = nf(x, 4);
    outfile = ClipIncr+"_"+DateStamp+fn0+fn1+fn2;
    fill(0, 0, 0);
    rect(0, 0, 120, 45, 7);
    //textFont(font,16); 
    fill(w);
    text(outfile, 5, 15);
    text(DateStamp +" "+ TimeStamp, 5, 35);

    save(outfile);
    String[] info = {outfile, ""};
    saveStrings(pointfile, info);
  }
}

A couple of problems remain:

  1. The “Movie Maker” from tools will assemble a film based on the file sequence name. So I end up with a broken film. (If my event happen at frame 97, the movie maker will not regard that as the begging of the film if there is a frame 0)

  2. I would like the sketch to make me the film. I searched a lot. Everyone points to the tool. Is there a function that will take the pngs and make a .MOV?

Thanks everyone

Jeremy, I will study your suggestion later. Thanks

    if (IncomingByte==1)
    {
      println("last: "+outfile);
      ClipIncr++;
      IncomingByte=0;
    }

in your case ( combine my counter / jeremydouglass clip / your DateStamp // ideas )
you can just reset the x = 0; too, when you change the clip number,
so the movie starts with 000.

also a little MOD to the clip idea,
not use it as filename, use it as path? ( tested “saveFrame” makes the path automatically )


it does make the use of movie maker easier, but not solve the pointer info ( now inside its path ) problem


nevertheless, if xend is still 900
every THE EVENT creates 900 more files and your drive will get full.
and if there is no event you burn your drive ( SSD ? ) as you still
store to the sketch path?


with

nf(clip,3)
clip++

and clip unlimited can get max 1000 sub directories,
again you could limit your clips like

if ( clip++ >= 9 ) clip = 0;
x=0;

get only the last 9 event ( and the running directory )
nice but creates the same ring buffer problem again for the directories

  • you must look for the date of the pointer file to know whats the newest one.
// rev 0 make filename
// rev 1 make point file
// rev 2 try clip as path idea what fits to moviemaker
// rev 3 use new win10 ramdisk 
// https://www.youtube.com/watch?v=KvjH6p5pB5Q
// http://memory.dataram.com/products-and-services/software/ramdisk

String d0  = "R:/TEMP/";                   // use my new WIN 10 / RAMDISK ( with save / recover option so not loose files on reboot!)
//String d0  = "";                           // now use sketch path
int clip = 0, clipend = 3;                 // create max 4 subdirs
String p0  = "event"+nf(clip, 3)+"/";      // make a path
String fn0 = "snap";
int fps = 2;                               // 2 frames / pictures per sec 
int x = 0, xend = 9;                       // and max 10 files == 5 sec recording time
String fn1 = nf(x, 3);
String fn2 = ".png";
String outfile = p0+fn0+fn1+fn2;           // filename
String pointfile = p0+"pointer.txt";       // pointer file at same path
boolean record = true;                     // init mode RECORDING

void setup() {
  size(300, 100);
  println("recording: "+record);
  frameRate(fps);
}                                          // end function setup

void draw() {
  background(200, 200, 0);
  recording();
}                                          // end function draw

void recording() {                         // called from main draw()
  if ( record ) { 
    fn1 = nf(x, 3);
    if (  x++  >= xend ) x = 0;            // so we start with "000"
    p0  = "event"+nf(clip, 3)+"/";         // use clip counter sub dir
    outfile   = d0+p0+fn0+fn1+fn2;         // drive / path / file + counter + .filetype
    pointfile = d0+p0+"pointer.txt";       // pointerfile there too
    text(outfile, 0, 10);                  // test show filename in canvas ( to check in files later )
    //save(outfile);
    saveFrame(outfile);                    // save every frame
    String[] info = {outfile, ""};
    saveStrings(pointfile, info);          // and record that name in first line of a pointer file
  }
}                                          // end function recording

void mousePressed() {
  if ( record ) {                          // toggle recording
    record = false;
    println("last: "+outfile+" \nrecording: "+record+" ,for start recording must click again");
  } else { 
    record = true; 
    if (clip++ >= clipend ) clip = 0;      // increment and limit recorded clips ( path )
    x = 0;                                 // reset filename number
    println("recording: "+record);
  }
}                                          // end function mousePressed

KLL

Thanks again for your reply and the ideas.
You said
you can just reset the x = 0; too, when you change the clip number,
so the movie starts with 000.

This wont work. If I do that I will start at clip 000 but after x >= xend the recounting starts.
Say I dont have a trigger till 5 min later. By now the x count is at some random number (Say 48) . That should be my first frame.
The triggers starts recording all frames in a loop in a new folder.
But what do I have in the old folder? 900 frames numbered from 0 to 900. The last frame should be frame 49, when the event happened. That is the OLDEST frame in my folder but is not numbered accordingly.
All the other frames should be re-numbered accordingly.
Meaning Frame 49 = Frame 900, frame 48= frame 899, etc.

Is there a file naming library for Processing?
Is there a way to do this with framecount?

Where the framecount is in the the file name and files older than (frameCount-900) are deleted?

GoToLoop

Is there any useful info for me in those links.
I know what nf is .
I do not want to Prevent overwriting files.

Here is what I want . I think the import java.io.*; library would do it, but I can not find any reference.

I want to save in a directory a frame and name it snap and the frameCount number.
Then I want to delete the frames that are older than current frame minus 900 frames.
THis way when I make a film, I only do the last 30 seconds of what my camera captured.

Is there a command that would go to the SaveFrame directory and delete files based on name, or time of creation?
Thanks

I tried

  String fileName = dataPath("\5_17-9-2019data\snap0521.png");
  File f = new File(fileName);
  if (f.exists()) {
    f.delete();
    println("deleted:", fileName);
  }
  else
  println("NOT FOUND");
  
  
}

But it did not work. Processing wont even accept the directory description,
dataPath("\5_17-9-2019data\snap0521.png");

I was hoping to delete with every frame some of the old frames. This way the frame number is always increasing.

Please help, Jeremy, I got this syntax from you on a different topic.
THanks

back to the beginning:
-a- you have a trigger from arduino
you used to save pictures from that moment, BUT
you wanted pictures from the time prior to the trigger.

so i give you the idea of saving permanent pictures
( to a ram-disk ) in ring-buffer way to prevent filling up the drive.
for not need to check on the timestamp of the files later i had the idea of the pointer file.

-b- that ring buffer
need a logic so you can bring that pictures in to the right order
( at moment of stop storing, at the moment when you make the movie… )

-c- but lets put that please back for a moment as there is a
still one not addressed point we have to do first:

the post trigger saving.

now that and using arduino and video…
i all packed already here
http://kll.engineering-news.org/kllfusion01/articles.php?article_id=162
there the idea is that you can configure like 1/2 == 50/50% or 1/3 == 66/33%
time prior / post trigger picture saving.

i play on and put it on my server because it looked like you
stopped play on this question…

This is my attempt using SimpleDateFormat::format() + File::delete() + Queue::remove() to always keep max screenshots at 900: :star_struck:

/**
 * Queued Stamped Snapshots (v1.3)
 * GoToLoop (2019/Sep/17)
 * https://Discourse.Processing.org/t/who-stole-my-cats-food/13895/16
 */

import java.text.SimpleDateFormat;
import java.util.Date;

import java.util.Queue;
import java.util.ArrayDeque;

static final String PATTERN = "yyyy-MMM-dd.HH-mm-ss.", EXT = ".png";
static final int MAX_FILES = 900, FPS = 20;

final Queue<File> paths = new ArrayDeque<File>(MAX_FILES);

void setup() {
  size(400, 150);
  frameRate(FPS);
}

void draw() {
  background((color) random(#000000));
  makeSnapshot();
  displayTitle();
}

static final String getTimeStamp() {
  return new SimpleDateFormat(PATTERN).format(new Date());
}

void makeSnapshot() {
  if (paths.size() == MAX_FILES)  paths.remove().delete();

  final File newPath = dataFile(getTimeStamp() + frameCount + EXT);
  println(newPath);

  paths.add(newPath);
  saveFrame(newPath.getPath());
}

void displayTitle() {
  final String msg =
    "Frame: " + frameCount +
    "  -  Size: " + paths.size() + 
    "  -  FPS: " + round(frameRate);

  surface.setTitle(msg);
}

GoToLoop,
Thanks a lot, that is what I wanted. The oldest frames are dying and the new frames are being recorded.
So I implemented it with my USB cam and it works fine.

No to drive this home, I need to create a new save directory with each Serial event.
When the Arduino sends a message, I need to save these frames in a new directory so I can look back and see what happened 10 sec before that signal,

Here is what I tried

  if ( ArduCOM_Port.available() > 0 ) { 
    IncomingByte = ArduCOM_Port.read(); 
    println (IncomingByte);
    if (IncomingByte==1)
    {
    
      ClipIncr++;
      IncomingByte=0;
    }
  }

 
  
  makeSnapshot();
  displayTitle();
}

static final String getTimeStamp() {
  return new SimpleDateFormat(PATTERN).format(new Date());
}

void makeSnapshot() {
  if (paths.size() == MAX_FILES)  dataFile(paths.remove()).delete();

  final String newPath = dataPath(getTimeStamp() + frameCount + EXT);
  String ClipIncrStr=(nf(3,ClipIncr)); 
  
 // saveFrame(newPath);
  //paths.add(newPath);
  
  saveFrame(ClipIncrStr+"_"+newPath);
  paths.add(ClipIncrStr+"_"+newPath);
  //println (ClipIncr+"_"+newPath);
}

I was expecting to have a 0_data, 1_data etc after each Arduino trigger.
Instead nothing got recorded anymore.

What to do?
Thanks

Folder paths are separated w/ /, not “_”. :warning:

Thanks again,
I did try:

saveFrame(ClipIncr+"/"+newPath);
 paths.add(ClipIncr+"/"+newPath);

I do not get the “data” directory but I get directories named 0,1,2 etc increasing with each serial event.

The problem is, they are all empty, no frames are saved.

Any ideas? THANKS

PS
I did try the directories as strings, such as

`` String ClipIncrStr=(nf(ClipIncr,3));

saveFrame(ClipIncrStr+“/”+newPath);
paths.add(ClipIncrStr+“/”+newPath);
``

The directories are not even being made.
In red i get:

Error while saving image.
java.io.IOException: image save failed.
at processing.core.PImage.saveImageIO(PImage.java:3275)
at processing.core.PImage.save(PImage.java:3406)
at processing.core.PGraphics.save(PGraphics.java:8304)
at processing.core.PApplet.saveFrame(PApplet.java:3985)
at TimedRecording_05_FrameCount.makeSnapshot(TimedRecording_05_FrameCount.java:139)
at TimedRecording_05_FrameCount.draw(TimedRecording_05_FrameCount.java:122)
at processing.core.PApplet.handleDraw(PApplet.java:2476)
at processing.awt.PSurfaceAWT$12.callDraw(PSurfaceAWT.java:1547)
at processing.core.PSurfaceNone$AnimationThread.run(PSurfaceNone.java:313)

I’ve modified makeSnapshot() v1.3 to accept an extra String folder parameter: :prayer_beads:

void makeSnapshot() {
  makeSnapshot("");
}

void makeSnapshot(final String folder) {
  if (paths.size() == MAX_FILES)  paths.remove().delete();

  final String fold =
    folder == null || folder.isEmpty()? "" :
    folder.endsWith("/") || folder.endsWith("\\")? folder :
    folder + "/";

  final File newPath = dataFile(fold + getTimeStamp() + frameCount + EXT);
  println(newPath);

  paths.add(newPath);
  saveFrame(newPath.getPath());
}

Thanks for the help.
The weirdest thing happened. The 'delete" and “add” are not accepted anymore.

Also, it is not clear to me where would I plug the ClipIncrStr string?
Here is a snapshot.
Thanks