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.

Hi Tim,

I wanted to know how it really work for you using the paintable canvas component.
Can you please share the full sample of your codes. We were having a similar cognex 3d camera systems, which we wanted to load the latest image in ignition page.

Appreciate your help.

Roberto

We ended up using the picture component instead of the paintable canvas component. My post that I said was the solution, the post right above your post, is how I got it to work. It includes the code that I used in a timer on the client side instead of the server/gateway side. My customer only has one or two HMIs running the client so it made more sense to use a client timer. If you have multiple instances of client open at a given time I would recommend using the timer in the server/gateway instead of the client.
The biggest issue that I know of that you may run into is that the Cognex camera saves pictures as .png or something that Ignition’s Picture Component will not support. You’ll need to set the Cognex camera to save the pictures as .jpg
The hardest part of getting the Picture Component to show the pictures was getting the image path changed to be the correct format. My posted code shows how I, with the help of an Inductive Automation Support Engineer, got that issues solved.
I hope this is helpful to you.

Hi Tim,

Thank you very much for your reply. when you say picture component it is the image component right? but where to write the script?
In Paintable canvas i can write it immediately on the repaint. what about the image component? where did you write your script.

Sorry for my newbie question.

Regards,

Roberto

We have all asked newbie questions and some of us still do, so don’t feel bad about asking newbie questions.

Yes, when I say picture component I mean image component.

In the Designer, along the top menu select Project.
In the drop down select Gateway Events.
In the Gateway Event Scripts pop up, select Timer.
Then, with Timer selected click the plus sign, when the mouse hovers over it it’ll say Add New Timer Script.
A pop up called Choose Timer Settings pops up. Name the timer script you’ll be creating and decide how often you want the script to run. (1,000 ms is equal to 1 second.) In the project I did I know that the soonest the image in the folder will be updated is usually every 1.25 minutes. However, there are cases where the newest image will come in sooner than that. So to make sure the HMI doesn’t miss a new photo I set the script to run every 500ms, or every 30 seconds.
You’ll then need to decide if you want the Delay Type to be Fixed Delay or Fixed Rate. The main difference between them is if you want the script to start running exactly every 500ms (for example) or run every 500ms after it finishes its last run. Fixed Rate means it’ll be every 500ms that it starts running again. Fixed Delay means it’ll run every 500ms after it finished running the last time.
Then you’ll need to select your Threading. If you have a lot of scripts running in the Gateway I would recommend selecting Dedicated. If, like in my case, you don’t have many scripts running in the project Gateway, then select Shared.
Then Click “OK.”
Your cursor should then be blinking next to the number 1. Start typing your script, or paste the copy of my script there. If you paste my script in there, you’ll need to make some changes so that it works for you.
The first change you’ll need to make is to what folder/directory the pictures will be in. This is where the code says “folder_path = …”
The next change you’ll need to make is to where you want the picture to be displayed. I have my script showing the picture in two different places," Main Windows/In Process" and “Main Windows/Calibration”. Depending on how you structured your windows/screens in Ignition these will be different for you. I usually create a folder called “Main Windows,” a folder called “Pop Ups,” and a folder called “Docked.” It keeps them more organized for me than having all of them accessible from the Vision, Windows drop down on the left side of my Designer. The two lines of code for each screen are line 1 of each tells Ignition what screen “window = system.gui.getWindow…” and the second line tells Ignition what component “window.getRootContainer().getComponent(‘Image’).path = imagepath.” As long as you don’t change the name of the component in the component editor, the Image Component will have itself named “Image.”

KEY THING TO KNOW - In the Designer, you will NOT see the most recent photo. It will look like it has no picture in it. When you open the Client, if your script is working, you will see a picture, hopefully the most recent one.
When you want to open the Client from the computer with the Designer, on the top menu bar there is Tools, select it. On the drop down select Launch Project and select if you want it to be windowed or full screen. To exit full screen use “Alt + F4.”

Hi Tim,
Sorry for the late reply. Thank you very much for your detailed instructions. I have not yet tested it. I will be in the office tommorow. I will let you know.
I really appreciate your help. I am still new in ignition and spent a lot with allen bradley and siemens. I still need to learn a lot and excited about this ignition development.

Regards,

Roberto

Hi Tim,

I already tried your detailed instructions and was working well except for my gateway timer. Is this an issue if i am still using trial mode? the gateway timer seems to be not working. i can execute the code and works well and the image from cognex is updating. Did i miss some settings to make the gateway timer to work? or this is because of the trial mode.

Regards,

Hi Tim,

I tried the client timer and it was working well on that.

Thank you very much for your help.

Roberto

1 Like

I just did this with a paintable canvas and had no issues - I'm trying to use it now for mousewheel zoom/pan which is another thing, but so far image display, and displaying an updated image hasn't been a problem at all. Maybe because I have a bound property, (in this case, 'zoom')

# ---- This is for zooming ---- #
from java.net import URL
from javax.imageio import ImageIO
from java.io import ByteArrayInputStream

# 1. Get the image 
imageURL = "file:///C:/images/LatestImage.jpg"

# 2. Create a Java URL object from the URL string
url = URL(imageURL)

# 3. Read the image at the URL
image = ImageIO.read(url)

# 3. Get the scale factor and convert the Integer percent value to a Float scale factor
scaleFactor = event.source.zoom * 0.01

# 4. Scale and draw the image 
g = event.graphics
g.scale(scaleFactor, scaleFactor)
g.drawImage(image,0,0,event.source)

You are doing a file/URL read inside a component's repaint event. That is very prone to hiccup/breakage. You should be reading the file and converting to an image once, then letting the repaint event use that saved object. Repaint can be called many times in succession by Swing as various window interactions and overlays occur. The repaint script should never reach outside of java (preferably, just properties on event.source) to perform its task.

Search for paintable canvas putClientProperty for sample code.

1 Like

You're absolutely right - I just started in on this and wanted to ensure the image was being refreshed at the least in the repaint - from there if my image didn't refresh I have an easier time tracking what is caching.

Thank you for the tip on putClientProperty, that worked tremendously. There have been several other instances where I could have used that for referencing/passing objects... :slight_smile:

1 Like

Another followup - along with the putClientProperty I was able to use the PaintableCanvas to make a fully interactive image viewer with drag-pan/zoom controls and a 'High/Low' Quality toggle (Switching between cached bytes) that worked great (I also switch to my low quality image when dragging)

1 Like