using VTK to get a 3D .obj file from DICOM files

Hello everyone,

my goal is it to have a python code that reads DICOM files and then turns them into a 3D .obj file, the problem is that I don’t know how the values of vtkMarchingCubes work because I can’t seem to get all the data but only parts of it. So to say, sometimes I only get the Skull as a model or just the skin, the important thing for me would be the brain tho.

I have tried to render everything in python before, where nothing went missing and at least this part worked well but because of other problems with vtk not liking tk inter I made up my mind to use Godot for the UI stuff. Now using Godot I need the before mentioned .obj file to kind of import it into the 3D environment as a node or something like that.

(I’m a Godot noob so I also have no idea on how to implement the .obj but first I gotta find out how to solve my python problem)

My first full python try looked like this (the # are in german):

import tkinter as tk
from tkinter import filedialog
import vtk

class DICOMViewer:
    def __init__(self, root):
        self.root = root
        self.root.title("DICOM Viewer")

        # Initialisieren Sie die VTK-Variable
        self.vtk_window = None

        # Menü erstellen
        menu_bar = tk.Menu(root)
        root.config(menu=menu_bar)

        file_menu = tk.Menu(menu_bar, tearoff=0)
        menu_bar.add_cascade(label="File", menu=file_menu)
        file_menu.add_command(label="Open DICOM", command=self.open_dicom_folder)

    def open_dicom_folder(self):
        folder_path = filedialog.askdirectory(title="Open DICOM Folder")

        if folder_path:
            # Verzögere das Laden der VTK-Operationen im Hauptthread
            self.root.after(0, self.load_dicom_and_render, folder_path)

    def load_dicom_and_render(self, folder_path):
        # Laden aller DICOM-Dateien im ausgewählten Ordner
        dicom_reader = vtk.vtkDICOMImageReader()
        dicom_reader.SetDirectoryName(folder_path)
        dicom_reader.Update()
        dicom_volume = dicom_reader.GetOutput()

        # 3D-Rekonstruktion
        volume_mapper = vtk.vtkGPUVolumeRayCastMapper()
        volume_mapper.SetInputData(dicom_volume)

        volume_property = vtk.vtkVolumeProperty()
        volume_property.ShadeOn()
        volume_property.SetInterpolationTypeToLinear()

        volume_color = vtk.vtkColorTransferFunction()
        volume_color.AddRGBPoint(-1024, 0.0, 0.0, 0.0)
        volume_color.AddRGBPoint(0, 1.0, 0.5, 0.5)
        volume_color.AddRGBPoint(3071, 1.0, 1.0, 1.0)

        volume_scalar_opacity = vtk.vtkPiecewiseFunction()
        volume_scalar_opacity.AddPoint(-1024, 0.0)
        volume_scalar_opacity.AddPoint(0, 0.2)
        volume_scalar_opacity.AddPoint(3071, 0.2)

        volume_property.SetColor(volume_color)
        volume_property.SetScalarOpacity(volume_scalar_opacity)

        volume_actor = vtk.vtkVolume()
        volume_actor.SetMapper(volume_mapper)
        volume_actor.SetProperty(volume_property)

        # Rendering
        renderer = vtk.vtkRenderer()
        renderer.SetBackground(0.0, 0.0, 0.0)

        render_window = vtk.vtkRenderWindow()
        render_window.SetWindowName("DICOM 3D Viewer")
        render_window.SetSize(self.root.winfo_screenwidth() // 2, self.root.winfo_screenheight())
        render_window.AddRenderer(renderer)

        interactor = vtk.vtkRenderWindowInteractor()
        interactor.SetRenderWindow(render_window)

        renderer.AddVolume(volume_actor)
        renderer.ResetCamera()

        # Interaktionsstil erstellen
        interactor_style = vtk.vtkInteractorStyleTrackballCamera()
        interactor.SetInteractorStyle(interactor_style)

        # Ändere die Position des VTK-Fensters
        self.root.update_idletasks()
        render_window.SetPosition(self.root.winfo_width(), 0)

        # Speichern Sie die Referenz auf das VTK-Fenster
        self.vtk_window = render_window

        # Starte die Interaktion
        interactor.Start()

if __name__ == "__main__":
    root = tk.Tk()
    app = DICOMViewer(root)

    # Setze die Tkinter-GUI auf die gesamte linke Seite
    root.geometry("{}x{}+0+0".format(root.winfo_screenwidth() // 2, root.winfo_screenheight()))

    root.mainloop()

my current attempt looks more like this:

import vtk

def load_dicom_series(a):
    reader = vtk.vtkDICOMImageReader()
    reader.SetDirectoryName(a)
    reader.Update()
    return reader

def create_3d_model(b):
    # Apply Marching Cubes to extract the surface
    marching_cubes = vtk.vtkMarchingCubes()
    marching_cubes.SetInputConnection(b.GetOutputPort())
    marching_cubes.SetValue(10, 1500)  # Adjust this value to include more/less of the data
    marching_cubes.Update()
    return marching_cubes

def save_obj(c, d):
    # Convert to PolyData
    poly_data = d.GetOutput()

    # Write to OBJ file
    obj_writer = vtk.vtkOBJWriter()
    obj_writer.SetFileName(c)
    obj_writer.SetInputData(poly_data)
    obj_writer.Write()

# Pfad zur DICOM-Serie
dicom_path = 'C:\\Users\\User\\Desktop\\python\\privat\\Germanische\\Scans'
output_path = 'C:\\Users\\User\\Desktop\\model.obj'

# Laden der DICOM-Daten
dicom_reader = load_dicom_series(dicom_path)

# Erzeugung des 3D-Modells
e = create_3d_model(dicom_reader)

# Speichern des 3D-Modells in einer .obj-Datei
save_obj(output_path, e)

Have you tried to load your DICOM image into ParaView in order to figure out the filter settings manually? There are probably a few examples here in this site as well, such as this one, or this one or this one.

Hey there,

what I’m trying to achieve doesn’t work with doing things manually.
I tried to further explain my standpoint here Render 3D DICOM object with marching cube? · Issue #1012 · Kitware/vtk-js · GitHub
But instead of forming the .obj from the .vti files and then “importing” them into Godot I plan on getting them directly into Unity using this plugin 3dheart_public / VtkToUnity · GitLab
However I have no clue on how Unity works and will try some things in the following days.
(I also tried some Voxel plugin/addon for Godot to “import” the .vti files directly but without any luck so far :frowning: )