33import vtk , qt , ctk , slicer , vtkITK
44from DICOMLib import DICOMPlugin
55from DICOMLib import DICOMLoadable
6+ from DICOMLib import DICOMUtils
67from DICOMLib import DICOMExportScalarVolume
78import logging
89
@@ -160,11 +161,6 @@ def examineFiles(self,files):
160161 loadable .selected = True
161162 # add it to the list of loadables later, if pixel data is available in at least one file
162163
163- # while looping through files, keep track of their
164- # position and orientation for later use
165- positions = {}
166- orientations = {}
167-
168164 # make subseries volumes based on tag differences
169165 subseriesTags = [
170166 "seriesInstanceUID" ,
@@ -184,15 +180,6 @@ def examineFiles(self,files):
184180 subseriesFiles = {}
185181 subseriesValues = {}
186182 for file in loadable .files :
187-
188- # save position and orientation
189- positions [file ] = slicer .dicomDatabase .fileValue (file ,self .tags ['position' ])
190- if positions [file ] == "" :
191- positions [file ] = None
192- orientations [file ] = slicer .dicomDatabase .fileValue (file ,self .tags ['orientation' ])
193- if orientations [file ] == "" :
194- orientations [file ] = None
195-
196183 # check for subseries values
197184 for tag in subseriesTags :
198185 value = slicer .dicomDatabase .fileValue (file ,self .tags [tag ])
@@ -250,100 +237,8 @@ def examineFiles(self,files):
250237 # now for each series and subseries, sort the images
251238 # by position and check for consistency
252239 #
253-
254- # TODO: more consistency checks:
255- # - is there gantry tilt?
256- # - are the orientations the same for all slices?
257240 for loadable in loadables :
258- #
259- # use the first file to get the ImageOrientationPatient for the
260- # series and calculate the scan direction (assumed to be perpendicular
261- # to the acquisition plane)
262- #
263- value = slicer .dicomDatabase .fileValue (loadable .files [0 ], self .tags ['numberOfFrames' ])
264- if value != "" :
265- loadable .warning += "Multi-frame image. If slice orientation or spacing is non-uniform then the image may be displayed incorrectly. Use with caution. "
266-
267- validGeometry = True
268- ref = {}
269- for tag in [self .tags ['position' ], self .tags ['orientation' ]]:
270- value = slicer .dicomDatabase .fileValue (loadable .files [0 ], tag )
271- if not value or value == "" :
272- loadable .warning += "Reference image in series does not contain geometry information. Please use caution. "
273- validGeometry = False
274- loadable .confidence = 0.2
275- break
276- ref [tag ] = value
277-
278- if not validGeometry :
279- continue
280-
281- # get the geometry of the scan
282- # with respect to an arbitrary slice
283- sliceAxes = [float (zz ) for zz in ref [self .tags ['orientation' ]].split ('\\ ' )]
284- x = sliceAxes [:3 ]
285- y = sliceAxes [3 :]
286- scanAxis = self .cross (x ,y )
287- scanOrigin = [float (zz ) for zz in ref [self .tags ['position' ]].split ('\\ ' )]
288-
289- acquisitionGeometryRegularizationEnabled = self .acquisitionGeometryRegularizationEnabled ()
290-
291- #
292- # for each file in series, calculate the distance along
293- # the scan axis, sort files by this
294- #
295- sortList = []
296- missingGeometry = False
297- for file in loadable .files :
298- if not positions [file ]:
299- missingGeometry = True
300- break
301- position = [float (zz ) for zz in positions [file ].split ('\\ ' )]
302- vec = self .difference (position , scanOrigin )
303- dist = self .dot (vec , scanAxis )
304- sortList .append ((file , dist ))
305-
306- if missingGeometry :
307- loadable .warning += "One or more images is missing geometry information. "
308- else :
309- sortedFiles = sorted (sortList , key = lambda x : x [1 ])
310- distances = {}
311- loadable .files = []
312- for file ,dist in sortedFiles :
313- loadable .files .append (file )
314- distances [file ] = dist
315-
316- #
317- # confirm equal spacing between slices
318- # - use variable 'epsilon' to determine the tolerance
319- #
320- spaceWarnings = 0
321- if len (loadable .files ) > 1 :
322- file0 = loadable .files [0 ]
323- file1 = loadable .files [1 ]
324- dist0 = distances [file0 ]
325- dist1 = distances [file1 ]
326- spacing0 = dist1 - dist0
327- n = 1
328- for fileN in loadable .files [1 :]:
329- fileNminus1 = loadable .files [n - 1 ]
330- distN = distances [fileN ]
331- distNminus1 = distances [fileNminus1 ]
332- spacingN = distN - distNminus1
333- spaceError = spacingN - spacing0
334- if abs (spaceError ) > self .epsilon :
335- spaceWarnings += 1
336- loadable .warning += "Images are not equally spaced (a difference of %g vs %g in spacings was detected)." % (spaceError , spacing0 )
337- if acquisitionGeometryRegularizationEnabled :
338- loadable .warning += " Slicer apply a transform to this series trying to regularize the volume. Please use caution. "
339- else :
340- loadable .warning += (" If loaded image appears distorted, enable 'Acquisition geometry regularization'"
341- " in Application settins / DICOM / DICOMScalarVolumePlugin. Please use caution. " )
342- break
343- n += 1
344-
345- if spaceWarnings != 0 :
346- logging .warning ("Geometric issues were found with %d of the series. Please use caution." % spaceWarnings )
241+ loadable .files , distances , loadable .warning = DICOMUtils .getSortedImageFiles (loadable .files , self .epsilon )
347242
348243 return loadables
349244
@@ -363,22 +258,6 @@ def seriesSorter(self,x,y):
363258 cmp = xNumber - yNumber
364259 return cmp
365260
366- #
367- # math utilities for processing dicom volumes
368- # TODO: there must be good replacements for these
369- #
370- def cross (self , x , y ):
371- return [x [1 ] * y [2 ] - x [2 ] * y [1 ],
372- x [2 ] * y [0 ] - x [0 ] * y [2 ],
373- x [0 ] * y [1 ] - x [1 ] * y [0 ]]
374-
375- def difference (self , x , y ):
376- return [x [0 ] - y [0 ], x [1 ] - y [1 ], x [2 ] - y [2 ]]
377-
378- def dot (self , x , y ):
379- return x [0 ] * y [0 ] + x [1 ] * y [1 ] + x [2 ] * y [2 ]
380-
381-
382261 #
383262 # different ways to load a set of dicom files:
384263 # - Logic: relies on the same loading mechanism used
0 commit comments