'Export .stl or .obj from 3D spectrogram using python

I am very new to python and programming in general. However, for an art project, I have spent the last week learning to program using python and found the plotly library. So amazing! It improved my code so much! I have written a code which lets me load in .wav files, creating a 3D spectrogram and exporting the 3D image to either a .png or html file. However, what I actually wanted to have is an .stl or .obj file of the 3D spectrogram (see image attached). I have already tried a few things, eg. I found a page which turns .png in .stl. It is a good alternative but it’s not what I was looking for. I had another idea where I would make x .png slices of the 3D spectrogram and then build a .stl from the slices.

I was wondering, maybe someone of you has another idea or advice to export the 3D plot into a .stl/.obj file or even plot a 3D spectrogram and immediately export it to .stl or .obj.

Thank you very much in advance for your help =) Bests, Sweetheart90 3D spectrogram plot



Solution 1:[1]

Not that easy. You need to connect your vertices to a triangle mesh (shape n, 3, 3) Then you can use mk_stl_mesh to generate and export_stl_mesh to save:

def export_stl_mesh(*args, fileName:str, **kwargs):
    """
        exports geoms mesh as stl file
        call like:  im_ex_port.export_stl_mesh( np.flip(d.vtx[0][d.geoms[0].mesh], -1 ), 
                                    fileName=f"{g.name}_{str(g.gId)}")
    """
    content = mk_stl_mesh(*args, **kwargs)
    exportPath = os.path.join(sts.mediaPath, 'stls', f"{fileName}.stl")
    with open(exportPath, 'wb') as f:
        f.write(content.encode('ascii'))
    print(f"stl saved to exportPath: \n{exportPath}")

def mk_stl_mesh(mesh, *args, **kwargs):
    bounds = f"solid Exported from geometries.py" + '\n'
    content = bounds
    for triangle in mesh.reshape(-1, 3, 3):
        content += ('facet normal 0.000000 0.000000 1.000000' + '\n')
        content += ('outer loop' + '\n')
        for point in triangle:
            content += (f"vertex {' '.join([f'{t:6f}' for t in point])}" + '\n')
        content += ('endloop' + '\n')
        content += ('endfacet' + '\n')
    content += ('end' + bounds)
    return content

I forgot to mention a couple of important points to complete the answer:

  1. There are some variables in the functions like mediaPath which you must change to whatever you want.
  2. The most critical part is not obviously to create the mesh. There is a coding challenge 25 from "the coding train" that can help you there. It's JavaScript but the idea is the same.
  3. You should use numpy to do this. There is also a numpy.stl library which might have what you need.

Have fun

Solution 2:[2]

I have further tried things. I found the mtri function in matplob lib which should plot a 3D surface with triangles. so far so good. this is my code

import matplotlib.pyplot as plt
from scipy.io import wavfile
import numpy as np
from scipy import signal # spectrogram function
from matplotlib import cm # colour map
import matplotlib.tri as mtri


filename="/Users/sebastiankuhz/Desktop/kunst/Assemblypart1.wav"
samplingFrequency, signalData = wavfile.read(filename)

# basic config
sample_rate = samplingFrequency
sig_len_secs = signalData.shape[0]/samplingFrequency

# generate the signal
timestamps_secs = np.arange(sample_rate*sig_len_secs) / sample_rate
mysignal = signalData

# extract the spectrum
freq_bins, timestamps, spec = signal.spectrogram(mysignal,sample_rate)

z=10.0*np.log10(spec)

tri = mtri.Triangulation(freq_bins,timestamps)


# 3d plot
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_surface(freq_bins[:, None], timestamps[None, :], z, cmap=cm.coolwarm)

plt.show()

Now, I receive the error: "ValueError: x and y must be equal-length 1D arrays".

Question: From a programming point of view, I understand the error, as the length of the arrays of the freq_bins and the timestamps are not the same. However in the plot I see that every timestamps value has "dedicated" freq_bins values and also z values. Thus, why do I then receive this error?

Solution 3:[3]

layout += [[sg.Column(col, scrollable=True, size=sz)]]

It means add a new row in layout, not a new column.

Try

layout = [[]]
...
layout[0] += [sg.Column(col, scrollable=True, size=sz)]

Demo code

import PySimpleGUI as sg

data = [(f'folder {j+1}', [f'File {i+1:0>2d}' for i in range(10)]) for j in range(5)]

layout = [[]]
for dir, files in data:
    column = [[sg.Text(dir)]] + [
        [sg.Checkbox(file, default=True)] for file in files
    ]
    layout[0] += [sg.Column(column, scrollable=True, vertical_scroll_only=True, size=(100, 200))]

sg.Window("test", layout, finalize=True).read(close=True)

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Jason Yang