Slicing a MHD simulation with field lines

[1]:
from pathlib import Path
import imageio

import numpy as np


import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
from matplotlib.colors import LinearSegmentedColormap

from tqdm.auto import tqdm

import astro3d
from astro3d.image_stack import makeslice
from astro3d.image_stack import VeroC_sRGB
from astro3d.image_stack import VeroM_sRGB
from astro3d.image_stack import VeroY_sRGB
from astro3d.image_stack import C_sRGB
from astro3d.image_stack import M_sRGB
from astro3d.image_stack import Y_sRGB

plt.style.use([{'image.cmap':'gray_r'}])

Setup

Read data

[2]:
with np.load(astro3d.get_data('diskdata/rho.npz')) as f:
    data = f['arr_0'][()]

with np.load(astro3d.get_output('streamlines.npz'), allow_pickle=True) as f:
    streamlines = list(f['pathes'][()])
[3]:
x = np.linspace(0, 1, data.shape[0])
y = np.linspace(0, 1, data.shape[1])
z = np.linspace(0, 1, data.shape[2])

Printer settings

printer specific and layer thickness can be chosen to be different)

[4]:
# these are the values for the J850 Prime
#dpi_x = 600
#dpi_y = 600
#dpi_z = 940  # 0.027 mm layer thickness

# these are the values used in alphacams TEILEFABRIK, where we ordered some of our prints
dpi_x = 600
dpi_y = 300
dpi_z = 940 # 0.027 mm layer thickness = 2.54 / dpi_z

Choose the height of the print, the rest should rescale accordingly

[5]:
height = 5.0 # this should be the total height of the printed cube in cm

We distinguish between Vivid colors and the Rigid Colors. The Vivid colors are transparent and need to be mixed with white or black to make them intransparent.

[6]:
vivid = True

Output folder

We store the images in the path set by output_dir.

[7]:
output_dir = 'slices_mhd' + vivid * '_vivid'
[8]:
path = Path(astro3d.get_output()) / output_dir

if not path.is_dir():
    path.mkdir()
else:
    files = list(path.glob('slice*.png'))
    if len(files)>0:
        print('directory exists, deleting old files')
        for file in files:
            file.unlink()
directory exists, deleting old files

New coordinates

calculate the new grids in x, y, z

[9]:
#n_z = int(height / layer_thickness)
n_z = int(height * dpi_z / 2.54)

n_x = int(n_z * len(x) / len(z) / dpi_z * dpi_x)
n_y = int(n_z * len(y) / len(z) / dpi_z * dpi_y)

n_x += n_x%2 # add 1 to make it even if it isn't
n_y += n_y%2 # add 1 to make it even if it isn't

x2 = np.linspace(x[0], x[-1], n_x)
y2 = np.linspace(y[0], y[-1], n_y)
z2 = np.linspace(z[0], z[-1], n_z)

create an interpolation function for the non-normalized 3D data

[10]:
def f_interp(coords):
    return astro3d._fortran.fmodule.interpolate(x, y, z, data, coords)

set the coordinates-tuple which is passed to the interpolation. x and y entries will stay the same and only the z entry will change.

Note that coords will not be passed to the interpolation function in this form, but will be transformed to a shape (N, 3), where the number of points N = len(x2) * len(y2).

[11]:
coords = (x2, y2, z2)

Normalization & color choices

Find the largest magnitude of the data values and define a logarithmic norm

[12]:
dyn_range = 1e-4
vmax = data.max()
norm = LogNorm(dyn_range * vmax, vmax, clip=True)

define the density levels, their width, their colors, and the filling factors of the colors

[13]:
levels = np.array([0.1, 0.4, 0.92])
# sigmas = np.array([0.03, 0.05, 0.03])
# clip = np.array([3.0, 5.0, 5.0])

sigmas = np.array([0.04, 0.04, 0.03])
clip = np.array([2.25, 4.0, 5.0])

# fill = np.array([0.1, 0.2, 1])     # this seemed too full
# fill = np.array([0.03, 0.06, 1.0]) # this seemed too empty
fill = np.array([0.08, 0.14, 0.5]) # also making the disk slightly less solid

We distinguish between Vivid colors and the Rigid Colors. The Vivid colors are transparent and need to be mixed with white or black to make them intransparent.

[14]:
MidGreen = [1, 154, 106]
[15]:
from astro3d.image_stack import rgb_to_cmyk
mg = rgb_to_cmyk(MidGreen)
mg[-1] = 1 - mg[-1]
mg /= mg.sum()
mg = np.round(mg * 100) / 100
[16]:
# we add one extra color for the streamlines
White = np.ones(3)
if vivid:
    colors = [[Y_sRGB, White], [C_sRGB, White], [Y_sRGB, M_sRGB, White], [C_sRGB, Y_sRGB, White]]
    fmix   = [[0.8,    0.2  ], [0.8,    0.2  ], [0.4,    0.4,    0.2  ], [0.58,   0.22,   0.2  ]]
else:
    colors = [VeroY_sRGB, VeroC_sRGB, [VeroY_sRGB, VeroM_sRGB], VeroM_sRGB]
    fmix = [1, 1, [0.5, 0.5], 1]
[17]:
# show the mixed colors
mix = [(np.array(c, ndmin=2) * np.array(_f, ndmin=2).T).sum(0) for c, _f in zip(colors, fmix)]
plt.imshow([mix]).axes.axis('off');
../../_images/3Dprinting_3_mhd_2_link_31_0.png

Show a histogram of the data values

[18]:
astro3d.image_stack.show_histogram(data, norm, colors=colors, levels=levels, sigmas=sigmas, clips=clip, f=fmix, fill=fill)
../../_images/3Dprinting_3_mhd_2_link_33_0.png

Example slice

select which index in the new z-grid to process

[19]:
iz = int(np.ceil(n_z / 2))

Process one slice and check the results

[20]:
data_d, img = makeslice(iz, z2, f_interp, coords, norm, path,
                levels=levels, sigmas=sigmas, fill=fill,
                colors=colors, f=fmix, streamlines=streamlines, bg=0.8)
[21]:
fig, axs = plt.subplots(2, 4, figsize=(16, 8), dpi=300)

axs[0,0].imshow(data[:, :, z.searchsorted(z2[iz])].T,norm=norm)
axs[0,0].text(0.05, 0.95, 'density', va='top', transform=axs[0,0].transAxes)

im = imageio.v2.imread(path / f'slice_{iz:04d}.png')
axs[0,1].imshow(im,norm=norm)
axs[0,1].set_aspect(dpi_x/dpi_y)
axs[0,1].text(0.05, 0.95, 'dithered image', va='top', transform=axs[0,1].transAxes)

axs[0,2].set_visible(False)
axs[0,3].set_visible(False)

for i, ax in enumerate(axs[1,:]):
    cmap = LinearSegmentedColormap.from_list('my', [[1,1,1], mix[i]])
    ax.imshow(data_d[:, :, i], origin='lower', cmap=cmap)
    ax.set_aspect(dpi_x/dpi_y)
    ax.text(0.05, 0.95, f'color {i+1}', va='top', transform=ax.transAxes)
../../_images/3Dprinting_3_mhd_2_link_39_0.png

Iteration

[22]:
iz = [0]  # Here we just want to print the first slice
# iz = np.arange(n_z)
[23]:
for _iz in tqdm(iz):
    makeslice(_iz, z2, f_interp, coords, norm, path,
                    levels=levels, sigmas=sigmas, fill=fill,
                    colors=colors, f=fmix, streamlines=streamlines, bg=0.5)