Repaintable canvas to show Cognex camera image

I am using Ignition version 8.1.3 and a Cognex 7200 camera that does not have HMI view/http capabilities.
I’ve scoured the forums for ways to get Ignition to show the most recent picture from the cognex camera. The best thing I think I found was on this thread: Updating Image component based off of newest image in a directory

I have tried a few things from that thread and from others and can not get a picture to show on the paintable canvas, let alone update pictures to the newest one. The code I’m currently using is:

g = event.graphics
filename = r"C:Users/User/Desktop/XXXXX Camera Images"

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)

I’ve also recently tried:

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

url = URL("C:Users/User/Desktop/XXXXX Camera Images").value
# Use this for a Local File
url2 = URL("C:Users/User/Desktop/XXXXX Camera Images")
#url2 = globals()['url']
icon = ImageIO.read(url2)
event.graphics.drawImage(icon, 0, 0, event.source)

I have put these on the Paintable canvas as Paint → Repaint script.
The pictures are being saved to the Ignition computer a .jpg.
I have a feeling I’m not getting my code right and there may be another issue as well. Such as maybe I need to be doing something with the pictures to make this work.

Can anyone help me figure this out please?

Under no circumstances should you be reading files or pulling URL contents or doing anything that calls out to the gateway in the paint event itself. (So, no DB queries.) Do all such things in an asynchronous thread that won’t hurt your UI if it stalls. Use a query binding on a custom property to get your DB content. In your code where you check for the cached image bytes, just stop if there are none. Use .putClientProperty in the async code or in a propertyChange for query content arrival.

Keeping your image generation code separate from your image display code will allow you to put logging in the former to find your bug.

ps. Please edit your post to put formatting marks around your code. Highlight all of your pasted code (in the editor window) and click the “Preformatted text” button in the toolbar. Looks like this: </>

Ok, I’ll forget the second set of code then that has URLs in it. I didn’t think that was right but a coworker said to try it and he has more experience than I do.

Does this mean I should separate my code somehow?
I’m very lost on the query binding for DB content. I understand creating a custom property but beyond that I’m lost. I’m very new doing all this.

So, I don’t think ImageIO.read() will be able to read the directory for you and pick out a file. You should be using a timer component to check the folder content frequently to get the full filename you should use. When appropriate, that script should kick off a background task that will read the file into an image. Use invokeLater at the end of the async script to assign the image object to a client property and toggle a custom boolean on the paintable canvas.

(The paintable canvas updates when any custom property changes.)

So. Three separate scripts:

  1. Timer component to find fresh file names,

  2. Background script to read files to yield image objects, and

  3. Paint event that just pulls an image from the client property and displays it.

Where do I put a background script at?

Also, I don’t understand async scripts, could you explain please?

An async script is any script function that is executed via system.util.invokeAsynchronous(). I generally prefer to place such functions in project script modules, but they can also be defined in-line (nested) in another script. Java Swing (the base for Vision) is single-threaded. Component events in Vision are really Java Swing events and run on the Swing GUI thread. Running time-consuming code in a component event freezes the GUI. That would include code that sleeps or waits on the network or the filesystem. Don’t do it.

However, running in a background thread has some limitations, too. Since Swing is single-threaded, it is full of operations on components that are unlocked. You can get away with manipulating components from a background thread for a long time, but eventually you’ll corrupt a component and crash Vision. Not just locked up. Dead. Passing data back to Swing requires using system.util.invokeLater().

See these topics for many details:

For anyone who wants to know how I got things to work:

First, I ended up using the Picture Component, NOT the Paintable Canvas Component.
Second, I went with a Client Timer Script. I used the Client Timer instead of the Gateway Timer due to there only being one Client running at a time on the system. If the customer adds more than one or two more Clients then I will need to change to a Gateway Timer Script.
Third, with the help of a talented, smart, and kind Software Support Engineer from Inductive Automation the following code was come up with. It pulls the newest image from the folder that the Cognex camera is saving to. I also had to make sure the camera was saving the pictures as .jpg.

#https://datatofish.com/latest-file-python/ --- Lines 2 - 10 come from this link
import glob
import os.path

folder_path = 'C:/Users/User/Desktop/XXXXX Camera Images/'
file_type = '\*JPG'
files = glob.glob(folder_path + file_type)
max_file = max(files, key=os.path.getctime)

#print (max_file)

#Parse the image's current file path

imagePath = max_file.split("\\")
imagePath = "/".join(imagePath)
imagePath = 'file:///' + imagePath
#https://docs.inductiveautomation.com/display/DOC81/system.gui.getWindow

#try:
window = system.gui.getWindow('Main Windows/In Process')
window.getRootContainer().getComponent('Image').path = imagePath
 
#try:
window = system.gui.getWindow('Main Windows/Calibration')
window.getRootContainer().getComponent('Image').path = imagePath
  
#except ValueError:
#   system.gui.warningBox("The Header window isn't open")

The reason the folder the camera saves the images to is on the Desktop is because the customer wants to be able to quickly go look at previous images without having the employees go digging through the computer.