Using matplotlib in dynamo to create images for Openseespy and Opsvis

Creating new topic for discussion from this thread below @Hadi.Moosavi

It is possible to get images using matplotlib by creating a temp file and loading it in using the DSCore.IO methods

Be warned it is quite the task to get all the python libraries working as openseespy has to be version 3.4.0.8 (python 3.9 / Dynamo 2.19) including a modification to the __init__.py file in the openseespywin folder to point to the tkinter install. Basic code below and image using an Opsvis example

import clr
import io
import os
import tempfile

import matplotlib.pyplot as plt

clr.AddReference("DSCoreNodes")
import DSCore

def image(fig):
    with io.BytesIO() as buf:
        fig.canvas.print_png(buf)

        with tempfile.NamedTemporaryFile(
            mode="w+b", prefix="IMG_", suffix=".png", delete=False
        ) as fp:
            fp.seek(0)
            fp.write(buf.getvalue())
            fp.close()

            # Dynamo Core Image
            filepath = DSCore.IO.FileSystem.FileFromPath(str(fp.name))
            image = DSCore.IO.Image.ReadFromFile(filepath)
            os.remove(fp.name)

    return image

# Clear memory
plt.close("all") 

# Script here creating pyplot figures

OUT = [image(plt.figure(n)) for n in plt.get_fignums()]

3 Likes

another solution with PIL and io

import sys
import clr
import System

clr.AddReference('System.Drawing')
import System.Drawing
from System.Drawing import Bitmap
from System.IO import MemoryStream

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import io
import pandas as pd
from scipy import misc
face = misc.face() #ndarray 3 dimmensions
face2dG = misc.face(gray = True) #ndarray 2 dimmensions

def plt2arr(fig):
    """
    need to draw if figure is not drawn yet
    """
    fig.canvas.draw()
    rgba_buf = fig.canvas.buffer_rgba()
    (w,h) = fig.canvas.get_width_height()
    rgba_arr = np.frombuffer(rgba_buf, dtype=np.uint8).reshape((h,w,4))
    return rgba_arr

def convertToBitmap2(npImgArray):
    bitmap_ = None
    # remove alpha
    if npImgArray.ndim == 3 and npImgArray.shape[-1] == 4:
        npImgArray = npImgArray[:, :, :-1]
    # convert to PIL Image
    if npImgArray.ndim == 3:
        image = Image.fromarray(npImgArray, "RGB")
    else:
        image = Image.fromarray(npImgArray, "L")
    # convert to Python ByteArray
    byteIO = io.BytesIO()
    image.save(byteIO, format='BMP')
    byteArr = byteIO.getvalue()
    # convert to Net ByteArray
    netBytes = System.Array[System.Byte](byteArr)
    with MemoryStream(netBytes) as ms:
        bitmap_ = Bitmap(ms)
    return bitmap_

# Example 1 : plot sin and cos wave
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.linspace(-np.pi, np.pi)
ax.set_xlim(-np.pi, np.pi)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.plot(x, np.sin(x), label="sin")
ax.plot(x, np.cos(x), label="cos")
ax.legend()
ax.set_title("sin(x) and cos(x)")
img_sin_cos = plt2arr(fig)
bitmap1 = convertToBitmap2(img_sin_cos)
#
# Example 2 : convert img scipy
bitmap2 = convertToBitmap2(face)
bitmap3 = convertToBitmap2(face2dG)
#
# Example 3 : plot a Dataframe
file_name = "https://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv"
df_iris = pd.read_csv(file_name)
# ax = df_iris.plot(kind="scatter", x="sepal_length", y="sepal_width", figsize=(10, 8)
ax = df_iris.plot(kind="scatter", x="sepal_length", y="sepal_width")
img_iris = plt2arr(ax.get_figure())
bitmap4 = convertToBitmap2(img_iris)
#
OUT = bitmap1, bitmap2, bitmap3, bitmap4
2 Likes

A little bit of digging through source code… matplotlib has a private method _print_pil

def image2bitmap(fig):
    with io.BytesIO() as buf:
        fig.canvas._print_pil(buf, fmt="BMP", pil_kwargs={})
        byte_array = System.Array[System.Byte](buf.getvalue())

    with MemoryStream(byte_array) as ms:
        bitmap = Bitmap(ms)

    return bitmap
2 Likes

Nice find

2 Likes

This is very interesting.
I tried. I get an error saying the file is being used by another process. See below.

Is it the plt that is still accessing it?

Posting the code would be easier than trying to guess what is above line 19
Read the full warning and try and work back
Line 38 is incorrect - change to fp.close() to call the method
Uncomment import clr and add the following lines if they are not present above

import io
import tempfile
import matplotlib.pyplot as plt

clr.AddReference("DSCore")
import DSCore
1 Like

my bad. it was the fp.close() typo. I think I was expecting it to give an error if that was the issue. Thanks a lot!

1 Like