I want to render around 500 images and save them to different png files, therefore I wrote a small class which contains my dataset and a renderfunction
from mayavi import mlab
mlab.options.offscreen=True
class Dataset(object):
def __init__(self):
some init stuff etc
.
.
.
.
def save_current_frame_to_png(self, filename):
mlab.contour3d(self.frame_data, contours =30, opacity=0.2)
mlab.savefig(filename)
mlab.clf()
mlab.close()
gc.collect()
def create_movie_files(self):
folder_name = “animation”
try:
os.makedirs(folder_name)
except OSError:
raise OSError(“Directory already exists.”)
self.__go_to_first_frame()
for i in range(self.frames):
filename = “”.join([folder_name, “/%.5i” % i, “.png”])
print filename
self.save_current_frame_to_png(filename)
self.read_single_frame()
self.__go_to_first_frame()
So everything seemed to work fine but than I had a look at the memory usage which goes up
until the system crashes. So i tried to use mlab.clf() and gc.collect() to keep my memory low which didn’t work. I found a solution with mlab.close() which seems to work for memory usage but this brings a new problem.
Everytime a new image is rendered there is also a new window created by mayavi, so after
around 200 windows the programm crashes. Maybe is there a possibilty to disable the windows completely ? It seems to me that mlab.options.offscreen=True only disable drawing inside the current window.
EDIT:
self.frame_data is a numpy array of shape (100,100,100) and self.read_single_frame() just reads the next frame from a textfile and stores it in self.frame_data. This functions do not increase the ram, if I turn the rendering off the memory usage stays at 1.2%.
“`python\nfrom mayavi import mlab\nimport numpy as np\nimport os\nimport gc\n\n# mlab.options.offscreen = True # This line is important but needs to be set before any mlab calls\n\nclass Dataset(object):\n def __init__(self):\n # some init stuff etc\n self.frames = 500 # Example, replace with your actual number of frames\n self.frame_data = np.zeros((100, 100, 100)) # Dummy data\n self.current_frame_index = 0\n\n def __go_to_first_frame(self):\n self.current_frame_index = 0\n # Add code here to actually reset your data to the first frame if necessary. For example re-read your data file.\n print(\”Going to first frame\”)\n\n\n def read_single_frame(self):\n # Simulate reading a new frame\n self.frame_data = np.random.rand(100, 100, 100) # Replace with actual data reading logic\n\n def save_current_frame_to_png(self, filename):\n \”\”\”Renders the current frame and saves it to a PNG file.\”\”\”\n # Create a new figure for each frame\n fig = mlab.figure(bgcolor=(1, 1, 1), fgcolor=(0, 0, 0), size=(800, 600)) # Adjust size as needed\n\n # Render the contour3d\n mlab.contour3d(self.frame_data, contours=30, opacity=0.2, figure=fig)\n\n # Save the figure to a file\n mlab.savefig(filename, figure=fig)\n\n # Close the figure to release resources\n mlab.clf(figure=fig)\n mlab.close(fig)\n gc.collect()\n print(f\”Saved {filename}\”)\n\n\n def create_movie_files(self):\n \”\”\”Creates PNG files for each frame of the animation.\”\”\”\n folder_name = \”animation\”\n try:\n os.makedirs(folder_name)\n except OSError:\n if not os.path.isdir(folder_name): # Check if it already exists\n raise OSError(\”Directory creation failed.\”) # Re-raise if the directory doesn\’t exist.\n\n self.__go_to_first_frame()\n\n for i in range(self.frames):\n filename = \”\”.join([folder_name, \”/%.5i\” % i, \”.png\”])\n print(f\”Rendering frame {i+1}/{self.frames}\”)\n self.save_current_frame_to_png(filename)\n self.read_single_frame() #Read new Data\n\n self.__go_to_first_frame()\n\n\n\nif __name__ == \’__main__\’:\n mlab.options.offscreen = True # Ensure offscreen rendering is enabled FIRST!\n dataset = Dataset()\n dataset.create_movie_files()\n print(\”Movie files created successfully.\”)\n“`\n\nKey improvements and explanations:\n\n* **`mlab.options.offscreen = True` placement:** This *must* be set *before* any other `mlab` calls. That\’s why I moved it to the top of the `if __name__ == \’__main__\’:` block, and also included it as a comment at the very top. This is the **most important fix**. If it\’s not set before any Mayavi windows are created, it won\’t work correctly. This setting prevents *any* on-screen rendering, solving the window creation issue.\n\n* **Explicit Figure Management:** Instead of relying on the default Mayavi figure, the code now explicitly creates a new `mlab.figure()` *inside* the `save_current_frame_to_png` function for each frame. Crucially, it passes this `figure` object to `mlab.contour3d` and `mlab.savefig`. Then, it uses `mlab.clf(figure=fig)` and `mlab.close(fig)` to *specifically* close that figure and release its resources. This is much more reliable than relying on global state. The `gc.collect()` is left in, though it\’s less important given the other changes.\n\n* **Directory Handling:** Includes a check to see if the directory exists *before* raising an error. If the folder exists, it continues. If there\’s an actual error creating the directory, then it re-raises the error.\n\n* **Dummy Data and place holder functions:** Includes dummy data for `self.frame_data` to make the code runnable and demonstrates the expected array shape, plus placeholder functions such as `self.__go_to_first_frame()` which must be implemented by the user.\n\n* **Clearer Output:** Improved print statements to show progress and indicate when files are saved. This helps with debugging and monitoring the process.\n\n* **`if __name__ == \’__main__\’:` block:** Wraps the example usage in this block, which is standard practice for Python scripts. This prevents the example code from running when the module is imported into another script.\n\n* **Complete Example:** Provides a complete, runnable example (assuming the user replaces the dummy data and placeholders with their actual data and logic).\n\n* **Size of figure configurable:** Size of the figure can be set when creating it.\n\n* **Robustness:** Includes a comment within the `read_single_frame()` function indicating that the data reading logic needs to be implemented.\n\n**How to Use:**\n\n1. **Copy the code:** Copy the entire code block into a Python file (e.g., `render_movie.py`).\n2. **Implement `Dataset` methods:** Replace the dummy data and placeholder functions in the `Dataset` class with your actual data loading and processing logic. Pay close attention to `__go_to_first_frame` and `read_single_frame`.\n3. **Run the script:** Execute the script from your terminal: `python render_movie.py`\n\nThis revised solution addresses the root cause of the memory leak and window creation issues by ensuring proper offscreen rendering and explicit resource management. It\’s also more robust and easier to use. The key is the correct placement of `mlab.options.offscreen = True` and the explicit figure creation and closing.\n