Updating Image component based off of newest image in a directory

Hello all,

I’m working with a Cognex camera currently, it isn’t an In-Sight series, so it doesn’t have the simple FTP push to server functionality. It uses VisionPro and Cognex Designer which do not allow this as easy.

So, a work around I am trying to perform is to have a directory of images on the same system that Ignition is hosted on be the source location for an Image component. I would want it to be updated via the one that is most recently put into the source pathway.

I found a post by Travis that appears to be on the correct track. (How can I create a picture slider?)

I would essentially just want it to read in the newest file every time a tag value is changed. However, I am not quite sure how to go about scripting this to show the newest file, as the name is different per file.

No one has any idea how I would go about doing this? I would compare this idea to the filmstrip idea that a lot of vision HMI’s use.

Are these images saved with sequential filenames (0001.jpg, 0002.jpg) or are they timestamps (2017-08-07 08:30:10.jpg)?

In either case - you’ll probably want to look at the os.path module (built in to the Jython standard library), specifically os.path.walk(), which returns a sequence of paths and filenames inside of a directory. You would then need to sort that return path to get the most recent timestamp, or highest index, or whatever.
Or, a more generic solution, you could use os.path.getmtime(path) to get the last modified time of each file in the directory.

So I’ve essentially figured it all out except for one thing… How can I make the Image component read the file from the local drive, rather than uploading through File Browser for each image?

The solution so far:

1.) I linked the Image Path to a string tag.
2.) The tag for the image path is written to when a trigger is called.
3.) When the trigger’s bool value is changed, a Tag Event (Value Changed) is performed:

import os
import os.path
import glob

newest = max(glob.iglob('C:\\Users\\me\\Documents\\blah\\blah\\blah\\imagename\\*.[Pp][Nn][Gg]'), key=os.path.getctime)
value = newest
system.tag.write('Vision/imagePath', value)

value = system.gui.getParentWindow(event).getComponentForPath('_parent').title"

Now when I toggle the trigger, it writes to the Image Path field for the Image component… however, I have a feeling there is one more step to get it to auto upload, or bypass… I saw some reference to a paintable canvas?

Well, I’ve got it to work for the Paintable Canvas!

All that’s left is to figure out how to make the image scale down to fit in the window… This is done using Java2D, so hopefully it will be easy to “zoom out” so to speak as the images are 3840x2748px in size and they’re going to a screen with a resolution of 800x600!

The code I used in the Paintable Canvas is:

from java.net import URL
from javax.imageio import ImageIO

url = system.tag.read("Vision/imagePath").value
# Use this for a local file
url2 = URL(url)
#url2 = globals()['url']
icon = ImageIO.read(url2)
event.graphics.drawImage(icon, 0, 0, event.source)

Here’s some code to display an image, scaled, on the paintable canvas. Also caches the read image to avoid a little bit of extra load:

g = event.graphics
filename = r"path/to/file.png"

cachedName = event.source.getClientProperty("img-bytes-cached-name")
bytes = event.source.getClientProperty("img-bytes-cached")
if filename!=cachedName or not bytes:
	# Image not cached - load from database
	#bytes = system.db.runScalarQuery("SELECT data FROM image_test WHERE imgname='%s'"%name)
	bytes = system.file.readFileAsBytes(filename)
	event.source.putClientProperty("img-bytes-cached",bytes)
	event.source.putClientProperty("img-bytes-cached-name",filename)

if bytes:
	from javax.imageio import ImageIO
	from java.io import ByteArrayInputStream
#	from java.awt.geom import AffineTransform
#	scale = AffineTransform()
	image = ImageIO.read(ByteArrayInputStream(bytes))
	
	g.drawImage(image, 0, 0, event.width, event.height, event.source)

Wow! Thank you.

So, it seems that when trying to interface what you gave me and what I wrote, it doesn’t quite come out right, probably due to image = ImageIO.read(ByteArrayInputStream(bytes)) ?

It was giving me OutOfMem errors when using your method… I just realize that the arguments I needed were only event.width, event.height… simple oversight from me and I can’t believe I didn’t see that…

If I don’t use all the code you gave me, and just load the file the way I wrote the code, should I ever worry about OutOfMem errors?

Yeah, there’s no reason to avoid using your code - ultimately it’s ending up at the same step. Out Of Memory is probably just due to the very large images - you could also bump up the client memory if it’s at the fairly low initial setting (256mB).

The computer this will be running on will be fairly under-utilized memory wise, so this is great. I will set the max to at least 1 or 2 GB’s.

Just wondering how Ignition clears memory to make room for the next loaded image? It seems as if every time I added a new image to the directory it loads the file from and then triggered it to find the next one, that it only increased the memory and it never went down at all.

I’ll do some more testing to try and reduce it, but I did have the allocated memory set to default, which is low for what I’m trying to do.

Ignition’s memory cleanup is all driven by Java/the Java virtual machine’s garbage collection. This happens automatically in the background, and is difficult to predict - generally speaking, an unused object will be marked for removal then purged in the next GC cycle, whenever that occurs.