Search code examples
pythonopencvmatplotlibmatplotlib-3d

3D quiver plot video using 3D arrays


I am attempting to create a 3d quiver plot of velocity vectors, using 3 arrays containing the vectors in x, y, z space with respect to time. I.e. a video of the quiver plot. Can someone help with this? I have showed the error message below from running the code as it is.

For example one frame of the output should look a bit like this:

enter image description here

Main code: part 1 and part 2. Also here:

(Note this code was successfully used for the 2D version now being upgraded to 3D)

    print("Rendering 'Nucleation and Motion in G gradient in 3D'")
    print("Lattice constant dx = {}, time step dt = {}".format(fluid_model_g.dx, fluid_model_g.dt))
    for n in progressbar.progressbar(range(args.num_frames)):
        fluid_model_g.step()
        for _ in range(20):
            indices = tf.cast(flow_particles, 'int32')
            for index in indices.numpy():
                flow_streaks[index[0], index[1]] += 0.15 / args.oversampling
            dx = tf.gather_nd(fluid_model_g.u, indices)
            dy = tf.gather_nd(fluid_model_g.v, indices)
            dz = tf.gather_nd(fluid_model_g.w, indices)
            flow_particles = (flow_particles + tf.stack([dx, dy, dz], axis=1) * 400) % x.shape
        if n % args.oversampling == 0:
            rgb = [
                tf.reduce_mean((7*fluid_model_g.G)**2, axis=2) + flow_streaks,
                tf.reduce_mean((4*fluid_model_g.Y)**2, axis=2),
                tf.reduce_mean((2*fluid_model_g.X)**2, axis=2),
            ]
            frame = make_video_frame(rgb)
            writer.append_data(frame)
            flow_streaks *= 0
            flow_particles = tf.constant(flow_particle_origins, dtype='float64')
            
#-------------------BJD 22.4.2021----additional rough code at present-----------------------------------------        
            if n == 200:
                print("n = ", n)
                break

            c1 = c1 + 1

            nx, ny, nz = 240, 426, 426

            x1 = range(nx)
            y1 = range(ny)
            z1 = range(nz)

            U = np.loadtxt("/home/brendan/runs/tf2-model-g_3d_vel_vector/tf2-model-g/arrays/quiver3D_array31/u.txt")
            V = np.loadtxt("/home/brendan/runs/tf2-model-g_3d_vel_vector/tf2-model-g/arrays/quiver3D_array31/v.txt")
            W = np.loadtxt("/home/brendan/runs/tf2-model-g_3d_vel_vector/tf2-model-g/arrays/quiver3D_array31/w.txt")

            X1, Y1, Z1 = np.meshgrid(x1, y1, z1)

            fig = plt.figure(figsize=(10,10))
            ax = fig.gca(projection='3d')
            ax.set_title("pivot='mid'; every 10th arrow; units='velocity vector' time=" + str(c1))
            Q = ax.quiver(X1[::10, ::10, ::10], Y1[::10, ::10, ::10], Z1[::10, ::10, ::10], U[::10, ::10, ::10],
                V[::10, ::10, ::10], W[::10, ::10, ::10], pivot='mid', units='inches')
            Q.set_array(np.random.rand(np.prod(x.shape)))  # may need this? BJD 22.4.2021
            ax.scatter(X1[::10, ::10, ::10], Y1[::10, ::10, ::10], Z1[::10, ::10, ::10], color='c', s=0)
            plt.tight_layout()
            plt.savefig('/home/brendan/runs/tf2-model-g_3d_vel_vector/tf2-model-g/plots/3D_video37/3D_video_velocity_' + str(c1) + '.png')

Error message on running code:

brendan@DL380pGen8:~/runs/tf2-model-g_3d_vel_vector/tf2-model-g$ python3 render_video.py /home/brendan/runs/tf2-model-g_2/tf2-model-g/nucleation_3D___3d_velocity_vector__1_seed__y0_x0_____R12_res160pVD20_OS1_ST20___TEST_8th_160.mp4 --params params/nucleation_3D.yaml
2021-04-22 16:44:29.517603: W tensorflow/stream_executor/platform/default/dso_loader.cc:60] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory
2021-04-22 16:44:29.517657: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
2021-04-22 16:44:43.078155: I tensorflow/compiler/jit/xla_cpu_device.cc:41] Not creating XLA devices, tf_xla_enable_xla_devices not set
2021-04-22 16:44:43.079068: W tensorflow/stream_executor/platform/default/dso_loader.cc:60] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory
2021-04-22 16:44:43.079095: W tensorflow/stream_executor/cuda/cuda_driver.cc:326] failed call to cuInit: UNKNOWN ERROR (303)
2021-04-22 16:44:43.079164: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (DL380pGen8): /proc/driver/nvidia/version does not exist
2021-04-22 16:44:43.081520: I tensorflow/compiler/jit/xla_gpu_device.cc:99] Not creating XLA devices, tf_xla_enable_xla_devices not set
Rendering 'Nucleation and Motion in G gradient in 3D'
Lattice constant dx = 0.15, time step dt = 0.041666666666666664
N/A% (0 of 480) |                                                                                             | Elapsed Time: 0:00:00 ETA:  --:--:--2021-04-22 16:44:46.312362: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:126] None of the MLIR optimization passes are enabled (registered 2)
2021-04-22 16:44:46.335468: I tensorflow/core/platform/profile_utils/cpu_utils.cc:112] CPU Frequency: 1994870000 Hz
Traceback (most recent call last):
  File "render_video.py", line 855, in <module>
    episodes[args.episode](writer, args)
  File "render_video.py", line 647, in nucleation_3D
    fluid_model_g.step()
  File "/home/brendan/runs/tf2-model-g_3d_vel_vector/tf2-model-g/fluid_model_g.py", line 284, in step
    np.savetxt("/home/brendan/runs/tf2-model-g_3d_vel_vector/tf2-model-g/arrays/quiver3D_array31/u.txt", self.u) # BJD 22.4.2021
  File "<__array_function__ internals>", line 5, in savetxt
  File "/usr/local/lib/python3.8/dist-packages/numpy/lib/npyio.py", line 1371, in savetxt
    raise ValueError(
ValueError: Expected 1D or 2D array, got 3D array instead
brendan@DL380pGen8:~/runs/tf2-model-g_3d_vel_vector/tf2-model-g$ 

Solution

  • I have had to record a 2D quiver plot a while back.

    The approach I used, was:

    1. Create the figure you want using.
    2. Convert the fig to an image (a numpy array)
    3. Use opencv-python (cv2), to write the output.

    Fig to arr

    import matplotlib.pyplot as plt
    import numpy as np
    import io
    
    
    def fig_to_numpy(fig, dpi=180):
        """
        Converts an input Figure, to a numpy array.
        If used by Matplotlib, this will close the figure.
    
        :param fig: plt.figure
            The input figure, with all items drawn onto it.
        :param dpi: int
            The resolution of the output image, keep in mind that larger
            takes longer.
        :return: np.ndarray
            Return a numpy array containing the figure images.
        """
    
        buf = io.BytesIO()
        fig.tight_layout()
        fig.savefig(buf, format="png", dpi=dpi)
        plt.close(fig=fig)
    
        buf.seek(0)
        img_arr = np.frombuffer(buf.getvalue(), dtype=np.uint8)
        buf.close()
        img = cv2.imdecode(img_arr, 1)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        return img
    

    Recording

    This is an example recording class, there are many alternative options (examples).

    class Record:
        def __init__(self, output, size, fps=20., format='mp4v'):
            fourcc = cv2.VideoWriter_fourcc(*format)
            self.out = cv2.VideoWriter(output, fourcc, fps, size[::-1])
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.close()
    
        def add(self, frame):
            if not self.out.isOpened():
                raise RuntimeError(f"Video is already closed.")
            self.out.write(frame)
    
        def show(self, frame, delay=1):
            cv2.imshow('Recording', frame)
            cv2.waitKey(delay)
    
        def close(self):
            self.out.release()
            cv2.destroyWindow('Recording')
    

    Test case

    This generates a (fake) test image.

    def quiver_plot(data, time: int):
        """ Create a fake quiver plot.  """
        fig = plt.figure(figsize=(10, 10))
        ax = fig.gca(projection='3d')
        ax.set_title("pivot='mid'; every 10th arrow; units='velocity vector' time=" + str(time))
        ax.scatter(*data, color='c', s=0)
        return fig
    
    def quiver_data(time):
        x, y, z = np.meshgrid(np.arange(0.2, 1, 0.2 * time), np.arange(0.2, 1, .2 * time), np.arange(0.2, 1, .8))
        u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)
        v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)
        w = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) * np.sin(np.pi * z))
        return u, v, w
    

    Test code

    if __name__ == '__main__':
        # Get size of figure
        data = quiver_data(time=1)
        fig = quiver_plot(data, time=1)
        arr = fig_to_numpy(fig)
    
        with Record(output='test.mp4', size=arr.shape[:2], fps=1) as writer:
            for time in tqdm.trange(1, 11):
                data = quiver_data(time)
                fig = quiver_plot(data, time)
                arr = fig_to_numpy(fig)
                writer.add(arr)
                writer.show(arr, delay=1)