Au - SiO2 - Au GIF Creation Notes
Step 1: Esq_1.vtr
Visualization
Mayavi Phase
In order to visualize the nearfield data from the DDSCAT output, I used tyhe Mayavi software. From their github README.md:
Mayavi seeks to provide easy and interactive visualization of 3D data.
It provides a user interface that allows for interaction with the data visualization details. However, it can also be easily implimented into a python script that runs the UI with some saved settings. This following terminal command opens the mayavi client with the python script (where the -x
switch lets mayavi know you are passing a python script). If you do not want to have the mayavi client open and instead have the processing happen off screen, we add the -o
switch.
$ mayavi2 -x test.py -o
If you have multiple datasets needing to be processed in the same manner, this method allows the application of the same settings to every dataset.
Python Script
First, you need to import the off screen version of the mayavi api. Use the commented version if you want the on screen version.
from mayavi.api import OffScreenEngine
# from mayavi.api import Engine
Next, we import the vtk/vtr reader. If your vtk/vtr file is an older binary version, use the commented line.
from mayavi.sources.api import VTKXMLFileReader
# from mayavi.sources.api import VTKFileReader
This last import gives access to the graphing module. Our files contain 3D information, but for our research purposes, we only want a 2D contour plane.
from mayavi.modules.contour_grid_plane import ContourGridPlane
Now create the MayaVi engine and start it.
engine = OffScreenEngine()
engine.start()
scene = engine.new_scene()
We read in the vtk/vtr file and add the file source.
reader = VTKXMLFileReader()
reader.initialize("Esq_1.vtr")
engine.add_source(reader)
In order to create the contour grid module, we initialize it with its main command, and then add it to the engine (not the scene).
ContourGridPlane = ContourGridPlane()
engine.add_module(ContourGridPlane)
Now comes the settings I applied. Our data object (ContourGridPlane with the initialized Esq_1.vtr
) has a width of 360 frames (derived from the size of the DDSCAT grid I imagine), so we place the frame in the middle (180).
ContourGridPlane.grid_plane.position = 180
However, the frame needs the contour removed so we can see the nanoparticle and not the whole scene.
ContourGridPlane.enable_contours = False
We then create a manager to apply some scene changes.
module_manager = engine.scenes[0].children[0].children[0]
This sets the colormap look up table (lut) to binary. Other color options are availible.
module_manager.scalar_lut_manager.lut_mode = 'binary'
We then set the color range to only represent values from 0 to 500. The default color range is insufficient.
module_manager.scalar_lut_manager.use_default_range = False
module_manager.scalar_lut_manager.scalar_bar.position = [0.1 , 0.01]
module_manager.scalar_lut_manager.scalar_bar.position2 = [0.8 , 0.17]
module_manager.scalar_lut_manager.data_range = [ 0., 500.]
We place the camera orthogonal to the the -x plane.
scene.scene.x_minus_view()
Finally, we save the image created.
scene.scene.save_png("binary.png")
So, in all, the python script looks like this.
mayavi_image_creator.py
```python from mayavi.api import OffScreenEngine from mayavi.sources.api import VTKXMLFileReader from mayavi.modules.contour_grid_plane import ContourGridPlane # Create the MayaVi engine and start it. engine = OffScreenEngine() engine.start() scene = engine.new_scene() # Read in VTK file and add as source reader = VTKXMLFileReader() reader.initialize("Esq_1.vtr") engine.add_source(reader) # Add Surface Module ContourGridPlane = ContourGridPlane() engine.add_module(ContourGridPlane) ContourGridPlane.enable_contours = False ContourGridPlane.grid_plane.position = 180 module_manager = engine.scenes[0].children[0].children[0] module_manager.scalar_lut_manager.lut_mode = 'binary' module_manager.scalar_lut_manager.use_default_range = False module_manager.scalar_lut_manager.scalar_bar.position = [0.1 , 0.01] module_manager.scalar_lut_manager.scalar_bar.position2 = [0.8 , 0.17] module_manager.scalar_lut_manager.data_range = [ 0., 500.] # Move the camera scene.scene.x_minus_view() # Save scene to image file scene.scene.save_png("binary.png") ```HPC Phase
File Structure
The Esq_1.vtr
files sit in their respective directory named lamxxx
. In my jobs, we have lam300
to lam1000
.
All of the lamxxx
folders sit in the main job directory, where I placed the python script and the following shell script.
Shell Script
Basically, this shell script opens each lamxxx
folder, copies the python script, and runs the mayavi command. For the HPC, the module load openswr
loads the necessary modules for mayavi.
#!/bin/bash
for i in {300..1000..10}
do
dir_name="lam"$i
echo $dir_name
cd $dir_name
cp ../mayavi_image_creator.py .
module load openswr
mayavi2 -x mayavi_image_creator.py -o
cd ../
done
Post Processing Python Script
This python script opens each lamxxx
folder, takes the two images (I made one with the colormap Binary and another in the colormap Copper), places them in the parent director in a folder named esqPictures
, and renames them by the folder they came from.
import os
pwd = os.getcwd()
for root, dirs, files in os.walk(pwd, topdown=False):
folders = dirs
if "esqPictures" not in folders:
print("here!")
os.mkdir(os.path.join(root, "esqPictures"))
for folder in folders:
for root2, dirs, files in os.walk(os.path.join(pwd, folder), topdown=False):
if "binary.png" in files:
os.rename("".join([os.path.join(root, folder), "/binary.png"]), "".join([pwd, "/esqPictures/", folder,"binary.png"]))
if "copper.png" in files:
os.rename("".join([os.path.join(root, folder), "/copper.png"]), "".join([pwd, "/esqPictures/", folder,"copper.png"]))
Finally, once all the images are loaded into one folder, I have the script add some details to the images like the wavelength and the scale (you can also add a scale to the mayavi python script, but it messes up some formatting), and then it creates a gif of all the images. Some positioning of the labels and images will need to be changed depending on resolution of the mayavi output images and the scale images.
The font file cmunrm.ttf
of the text in the image needs to be included in the same folder the python script reads in. Other files include the scale images.
import imageio.v2 as imageio
from PIL import Image, ImageFont, ImageDraw
filenameCopper = []
filenameBinary = []
# Makes separate lists of all copper pictures and all binary pictures
for root, dirs, files in os.walk("".join([pwd, "/esqPictures"]), topdown=False):
for filename in files:
if "copper" in filename:
filenameCopper.append(filename)
if "binary" in filename:
filenameBinary.append(filename)
# adds text to image, adds scale and legend to image, and creates gif
images = []
for file1 in sorted(filenameCopper):
my_image = Image.open("".join([folder,"/",file1]))
im2 = Image.open("scaleCopper.png")
posx, posy = [330, 63]
sizex, sizey = im2.size
sizex, sizey = sizex/5, sizey/5
title_font = ImageFont.truetype('cmunrm.ttf', size=20)
title_text = file1[0:6]
image_editable = ImageDraw.Draw(my_image)
image_editable.text((15,15), title_text, (255, 255, 255), font=title_font)
title_font = ImageFont.truetype('cmunrm.ttf', size=10)
image_editable.text((posx + 5*sizex+5, posy + 5*sizey-5), "0", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 4*sizey-5), "100", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 3*sizey-5), "200", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 2*sizey-5), "300", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 1*sizey-5), "400", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 0*sizey-5), "500", (255, 255, 255), font=title_font)
my_image.save("".join([folder,"/",file1]))
im1 = Image.open("".join([folder,"/",file1]))
back_im = im1.copy()
back_im.paste(im2, (posx, posy))
back_im.save("".join([folder,"/",file1]))
images.append(imageio.imread("".join([folder,"/",file1])))
imageio.mimsave("".join([pwd,"/", folder,'_copper.gif']), images, duration=0.05)
images = []
for file2 in sorted(filenameBinary):
my_image = Image.open("".join([folder,"/",file2]))
im2 = Image.open("scaleBinary.png")
posx, posy = [330, 63]
sizex, sizey = im2.size
sizex, sizey = sizex/5, sizey/5
title_font = ImageFont.truetype('cmunrm.ttf', size=20)
title_text = file2[0:6]
image_editable = ImageDraw.Draw(my_image)
image_editable.text((15,15), title_text, (255, 255, 255), font=title_font)
title_font = ImageFont.truetype('cmunrm.ttf', size=10)
image_editable.text((posx + 5*sizex+5, posy + 5*sizey-5), "0", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 4*sizey-5), "100", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 3*sizey-5), "200", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 2*sizey-5), "300", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 1*sizey-5), "400", (255, 255, 255), font=title_font)
image_editable.text((posx + 5*sizex+5, posy + 0*sizey-5), "500", (255, 255, 255), font=title_font)
my_image.save("".join([folder,"/",file2]))
im1 = Image.open("".join([folder,"/",file2]))
im2 = Image.open("scaleBinary.png")
back_im = im1.copy()
back_im.paste(im2, (posx, posy))
back_im.save("".join([folder,"/",file2]))
images.append(imageio.imread("".join([folder,"/",file2])))
imageio.mimsave("".join([pwd,"/", folder,'_binary.gif']), images, duration=0.05)
After that, here is an example of my results!
Here is a scrubbable .mp4
version of the gif.
To do list:
-
I would like to make the Mayavi scripts runnable on the HPC, but right now, an error occurs on the HPC side of things that is preventing me from submitting the whole script as an SBATCH.
-
If I can have this run on HPC, I would like to increase the definition of these images. Right now, 400px x 350px isn’t cutting edge.
-
Ideally I figure out a way to have the scubber run on a gif and not a converted mp4. Just personal preference.
-
It would be nice to have an arrow scroll on the scale of the gif to show the highest intensity of that frame.