Subscribe Bookmark
JohnPonte

Staff

Joined:

Jun 17, 2013

Embedding images in JSL scripts

Every now and then a question arises that spawns enough discussion that I think it is worthy of a blog post. The topic of discussion around JMP's virtual water cooler this time is the idea of including an image inside a JSL script. A similar discussion arose not long ago and prompted me to write a blog post called How to Add an Image to a Graph in JMP 9. But this time the initial question, and subsequent discussion, specifically asked about embedding the image in the script such that the script is not dependent on an external image file. An email thread started on Tuesday, and on Wednesday it found its way into my inbox. By Friday, I was still seeing more responses to the thread. I decided if there was that much interest within JMP, certainly there would be some interest throughout the JMP user community.

So can you embed an image within a JSL script so that the original image file is no longer necessary? The answer is, of course, yes. (If it was no I wouldn't be writing this blog post!) Ironically, the whole discussion about embedding images was going on at the same time I was writing my last blog post, Using images to add context to your data. So let's start where that one left off.

In that blog post, I used a JMP data table to generate a bivariate plot. The graph included arrows showing wind speed and direction. I then added an image to the graph. The image was a map of the area where the wind readings took place -- around Lake Michigan. If I wanted to save the graph, I could use the little-red-triangle (lrt) and select Script->Save Script to Script Window. This would write the JSL necessary to reproduce the entire graph. If I do that, I will see the following code. (This is only a portion of the actual code.)

Add Image(

      Set Blob(

            Char To Blob(

                  "625799eJM//9WNs7G2+0s6euR1e ... Ix0RcMtzpWCTfj/AXh1fKw=",

                  "base64compressed"

            ),

            "png"

      ),

      Bounds(

            Left( -90.96 ),

            Right( -84.02 ),

            Top( 44.97 ),

            Bottom( 39.04 )

      )

),

If you read How to Add an Image to a Graph in JMP 9, you will recognize the Add Image command. But in that example, I used a file reference, which meant that the JSL was dependent on an external image file. In this example, instead of a file reference, we are using a blob. The blob is a long string of character data that has been compressed using base64 compression. This is the smallest way to store an image as pixel data inside of a script. By the way, the ... in the middle of the string is to show that the majority of the character string has been removed for brevity. The actual string is much longer than what I've shown here. In addition to the compressed character data, the image type is also written out. This is so that when the script is read in, JMP knows how to interpret the character data stored in the blob. The nice thing about this is that I can generate the graph I want and then use Save Script to Script Window to get JMP to generate the compressed character data for me. I don't have to figure out how to do it myself. Nice!

At this point you might be wondering how big the script file is now. Well, in my case, the script without the image embedded is 1 KB. The image file is a PNG file and it is 26 KB. So the two files combined would be 27 KB. If I embed the image in the script file and save it to disk, the script file is now 34 KB. So the script containing the image is larger than the two individual files combined. But, I have to admit, I was surprised that it wasn't much larger. The PNG file is a binary file of compressed pixel data. The image embedded in the script is written out as ASCII characters. So I expected the script to be much larger. I suppose different images will have different results. In other words, your mileage may vary.

Now I can share my JSL script with others, and when they run it in JMP, they will see my graph, complete with the image. And they won't see the error message in the log that says the image file couldn't be found.

I hope you enjoy this little trick and, as always, please let me know what you think.

7 Comments
Community Member

Zoe Jewell wrote:

Very neat! I especially like the way JMP can generate the compressed character data.

Community Member

amit bothra wrote:

Hi John,

I want to create a dataset from an image having following columns: X, Y, r, b, g where X and Y are cordinates and R,G and B are the colours. Is this possible in JMP.

My idea is to run a K means cluster on it. My source for this is http://lamages.blogspot.com/2012/12/now-i-see-it-k-means-cluster-analysis.html#more

Regards,

Amit

Community Member

John Ponte wrote:

Hi Amit,

This sure is possible and here is the JSL to do it:

// Open/read the image into JMP

img = NewImage("$SAMPLE_IMAGES\tile.jpg");

// Get the color values as 3 distinct matrices

{r, g, b} = img << getPixels("rgb");

// Get the dimensions of the image/matrices

numRows = nrow(r);

numCols = ncol(r);

// Create a data table

dt = New Table("Pixel Data",

newColumn("Y", set values(shape(repeat(1::numRows,numCols)`,numCols,numRows))),

newColumn("X", set values(repeat(1::numCols, numRows))),

newColumn("R", set values(r)),

newColumn("G", set values(g)),

newColumn("B", set values(b)),

);

The x and y values are the pixel location in the image. The r, g, and b values are the color components of each pixel, in normalized color space (values between 0.0 and 1.0). Just change the first line to reference your own image and you should be good to go. Although you may also want to change the name of the data table.

Let me know if you have any other questions and good luck!

John

Community Member

amit bothra wrote:

Hi John,

Thanks for the reply. I have one more request. How do i convert the table back into an image. The table has following columns: Y , X, R, G, B.

Do you have a jmp email I can reach you at?

Regards,

Amit

Staff

John Ponte wrote:

Hi Amit,

To convert back you just have to know what your width and height is. An image has a width and a height but a data table has some number of rows and columns. But the number of columns corresponds to the number of unique variables, not to one of the dimensions of the image. If you use the code I gave you previously, then you know the width and the height from the original image. If you don't still have those values but you know you created the data table from the code above, then you could look for the max value in both the X and Y columns and that would give you your width (max Y) and your height (max X). Using that technique, there is an assumption about the values in those two columns, but we know that assumption is true if you created the data table per the code above.

So, with that in mind, here is the code to re-create the image:

// Reverse process - re-create the image from the data table

// Create three matrices (for pixel colors)

r2 = J(numRows, numCols);

g2 = J(numRows, numCols);

b2 = J(numRows, numCols);

// Save the r, g, b values for each pixel

for each row (

r2[:y, :x] = :R;

g2[:y, :x] = :G;

b2[:y, :x] = :B;

);

// Save new image

img2 = NewImage("rgb", {r2, g2, b2});

img2 << saveImage("C:\JMP\images\newImage.png", png);

Notice I use a '2' in front of the variables. You could just reuse the same variables as before, but I wanted to test my code and make sure it worked. If you reused the same variables you would not need to worry about width and height or create the matrices again. There might actually be a more efficient way of doing this but this is the solution that hit me off the top of my head. Also, notice I save the image as a PNG. You could use a different format as well, such as JPG.

BTW - my email is John.Ponte@jmp.com

Enjoy!

Community Member

mark cipollone wrote:

John,

Is it possible to embed either a PDF or Powerpoint file within a JMP file somewhere? What I would like to do is embed the file, and then allow the user to launch/view the file via a table script. The idea is to add a 'Click here for More Info' table script. When a user clicks it, the PDF or powerpoint file is launched, which will present more detailed info for the JMP file. I like the idea of embedding it, since dragging along a separate file is always messy.

thanks, Mark Cipollone.

Staff

John Ponte wrote:

The technique I showed here is designed to work with images, such as JPG or PNG. But on the Mac, pdf is considered an image format. So you could get the pdf converted into a blob by adding an image to a graph, using the Open() command and specify your pdf as the external image. Refer to "How to Add an Image to a Graph in JMP 9", above, to see how to do that. Then save that script to the script window, which will convert the pdf into a blob. Then you could write your own script that would display the image, using the blob as the image source. Once you have the blob you could get rid of the original graph that you created. It was just a way to get the pdf as a blob. Does that make sense?