Source code for apps.DicomViewer

#
##
##  SPDX-FileCopyrightText: © 2007-2024 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.5  (Thu Feb  8 19:11:13 CET 2024)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
"""DicomViewer

This application reads a stack of DICOM images and lets you interactively
navigate through the images.

Select a single image from a DICOM stack to read all the images.
Then use the slider to traverse the images.
"""
import numpy as np
import pyformex as pf
from pyformex.gui.widgets import _I
from pyformex.plugins.dicomstack import DicomStack


[docs]def allcloseV(a, **kargs): """Check that all values in a have the same value a is 1D or 2D array. If 2D: checks that all rows are the same. Values inside a row may differ. """ if a.ndim == 1: return np.allclose(a[1:], a[0], **kargs) elif a.ndim == 2: for i in range(a.shape[-1]): if not np.allclose(a[1:, i], a[0, i], **kargs): return False return True else: raise ValueError("a should be 1- or 2-dim array")
[docs]def changeColorDirect(i): """Change the displayed color by directly changing color attribute.""" global FA F = FA.object color = F.getField('slice_%s' % i).data FA.drawable[0].changeVertexColor(color) pf.canvas.update()
def setFrame(item): i = item.value() if dialog.validate: # res = dialog.results changeColorDirect(i) def run(): global width, height, FA, dialog, dotsize pf.clear() pf.smooth() pf.lights(False) pf.view('front') # reading image files path = pf.askFilename() if not path: return print("You selected %s" % path) ext = path.suffix if ext: filepat = ['.*[.]%s$' % ext[1:]] else: filepat = None files = path.parent.listTree(sorted=True, include=filepat) print("Number of files: %s" % len(files)) with pf.busyCursor(): DS = DicomStack(files) # print("z-value of the slices:]n",DS.zvalues()) spacing = DS.spacing() print("spacing: %s" % spacing) if spacing is None: return raw = pf.ack("Use RAW values instead of Hounsfield?") with pf.busyCursor(): pixar = DS.pixar(raw).astype(pf.Float) # TODO: Set in DicomStack print(pixar.shape) print("Data: %s (%s..%s)" % (pixar.dtype, pixar.min(), pixar.max())) scale = DS.pspacing() print(scale.shape) origin = DS.origin() print(origin.shape) zscale = origin[1:, 2] - origin[:-1, 2] print(zscale.shape) if allcloseV(scale) and allcloseV(origin[:, :2]) and allcloseV(zscale): spacing[:2] = scale[0] spacing[2] = zscale[0] print("Scale: %s " % spacing) else: print("Scale/origin is not uniform!") print(DS.pspacing()) print(DS.zvalues()) # pixar = pixar[:,:,:600] maxval = pixar.max() nx, ny, nz = pixar.shape # Free some memory del DS # Create a 2D grid of nx*ny elements model = 'Dots' dotsize = 3 if model == 'Dots': base = '1:0' else: # Do not use: does not work well for large data sets base = '4:0123' # TODO: Image rows are top down, should change in DicomStack F = pf.Formex(base).replic2(nx, ny).toMesh().reflect(1) for iz in range(nz): color = pixar[:, :, iz] / pf.Float(maxval) color = np.repeat(color[..., np.newaxis], 3).reshape(-1, 3) F.addField('elemc', color, 'slice_%s' % iz) FA = pf.draw(F, color='fld:slice_0', colormap=None, marksize=dotsize, name='image') nframes = nz frame = 0 dialog = pf.Dialog([ _I('frame', frame, itemtype='slider', min=0, max=nframes-1, func=setFrame), ]) dialog['frame'].setMinimumWidth(800) dialog.show() if __name__ == '__draw__': run() # End