def examineFiles(self, files):
        loadables = []
        for currentFile in files:
            dataset = pydicom.read_file(currentFile)

            uid = self.getDICOMValue(dataset, "SOPInstanceUID")
            if uid == "":
                return []

            seriesDescription = self.getDICOMValue(dataset,
                                                   "SeriesDescription",
                                                   "Unknown")

            if self.isQIICRX(dataset):
                loadable = DICOMLoadable()
                loadable.selected = True
                loadable.confidence = 0.95
                loadable.files = [currentFile]
                loadable.name = '{} - as a DICOM {} object'.format(
                    seriesDescription, self.TEMPLATE_ID)
                loadable.tooltip = loadable.name
                loadable.selected = True
                loadable.confidence = 0.95
                loadable.uids = [uid]
                loadables.append(loadable)

                logging.debug('DICOM SR {} modality found'.format(
                    self.TEMPLATE_ID))

        return loadables
  def examineFiles(self,files):
    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    Look for the special private creator tags that indicate
    a slicer data bundle
    Note that each data bundle is in a unique series, so
    if 'files' is a list of more than one element, then this
    is not a data bundle.
    """

    loadables = []
    if len(files) == 1:
      f = files[0]
      # get the series description to use as base for volume name
      name = slicer.dicomDatabase.fileValue(f, self.tags['seriesDescription'])
      if name == "":
        name = "Unknown"
      candygramValue = slicer.dicomDatabase.fileValue(f, self.tags['candygram'])

      if candygramValue:
        # default loadable includes all files for series
        loadable = DICOMLoadable()
        loadable.files = [f]
        loadable.name = name + ' - as Slicer Scene'
        loadable.selected = True
        loadable.tooltip = 'Appears to contain a slicer scene'
        loadables.append(loadable)
    return loadables
Пример #3
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
        corresponding to ways of interpreting the
        files parameter.
        """

        self.detailedLogging = slicer.util.settingsValue('DICOM/detailedLogging', False, converter=slicer.util.toBool)

        supportedSOPClassUIDs = [
            '1.2.840.10008.5.1.4.1.1.6.2',  # Enhanced US Volume Storage
        ]

        # The only sample data set that we received from GE LOGIQE10 (software version R1.5.1).
        # It added all volumes into a single series, even though they were acquired minutes apart.
        # Therefore, instead of loading the volumes into a sequence, we load each as a separate volume.

        loadables = []

        for filePath in files:
            # Quick check of SOP class UID without parsing the file...
            sopClassUID = slicer.dicomDatabase.fileValue(filePath, self.tags['sopClassUID'])
            if not (sopClassUID in supportedSOPClassUIDs):
                # Unsupported class
                continue

            instanceNumber = slicer.dicomDatabase.fileValue(filePath, self.tags['instanceNumber'])
            modality = slicer.dicomDatabase.fileValue(filePath, self.tags['modality'])
            seriesNumber = slicer.dicomDatabase.fileValue(filePath, self.tags['seriesNumber'])
            seriesDescription = slicer.dicomDatabase.fileValue(filePath, self.tags['seriesDescription'])
            photometricInterpretation = slicer.dicomDatabase.fileValue(filePath, self.tags['photometricInterpretation'])
            name = ''
            if seriesNumber:
                name = f'{seriesNumber}:'
            if modality:
                name = f'{name} {modality}'
            if seriesDescription:
                name = f'{name} {seriesDescription}'
            else:
                name = f'{name} volume'
            if instanceNumber:
                name = f'{name} [{instanceNumber}]'

            loadable = DICOMLoadable()
            loadable.singleSequence = False  # put each instance in a separate sequence
            loadable.files = [filePath]
            loadable.name = name.strip()  # remove leading and trailing spaces, if any
            loadable.warning = "Loading of this image type is experimental. Please verify image geometry and report any problem is found."
            loadable.tooltip = f"Ultrasound volume"
            loadable.selected = True
            # Confidence is slightly larger than default scalar volume plugin's (0.5)
            # and DICOMVolumeSequencePlugin (0.7)
            # but still leaving room for more specialized plugins.
            loadable.confidence = 0.8
            loadable.grayscale = ('MONOCHROME' in photometricInterpretation)
            loadables.append(loadable)

        return loadables
Пример #4
0
  def createLoadableAndAddReferences(self, datasets):
    loadable = DICOMLoadable()
    loadable.selected = True
    loadable.confidence = 0.95

    loadable.referencedSegInstanceUIDs = []
    # store lists of UIDs separately to avoid re-parsing later
    loadable.ReferencedSegmentationInstanceUIDs = {}
    loadable.ReferencedRWVMSeriesInstanceUIDs = []
    loadable.ReferencedOtherInstanceUIDs = []
    loadable.referencedInstanceUIDs = []

    segPlugin = slicer.modules.dicomPlugins["DICOMSegmentationPlugin"]()

    for dataset in datasets:
      uid = self.getDICOMValue(dataset, "SOPInstanceUID")
      loadable.ReferencedSegmentationInstanceUIDs[uid] = []
      if hasattr(dataset, "CurrentRequestedProcedureEvidenceSequence"):
        for refSeriesSequence in dataset.CurrentRequestedProcedureEvidenceSequence:
          for referencedSeriesSequence in refSeriesSequence.ReferencedSeriesSequence:
            for refSOPSequence in referencedSeriesSequence.ReferencedSOPSequence:
              if refSOPSequence.ReferencedSOPClassUID == self.UID_SegmentationStorage:
                logging.debug("Found referenced segmentation")
                loadable.ReferencedSegmentationInstanceUIDs[uid].append(referencedSeriesSequence.SeriesInstanceUID)

              elif refSOPSequence.ReferencedSOPClassUID == self.UID_RealWorldValueMappingStorage: # handle SUV mapping
                logging.debug("Found referenced RWVM")
                loadable.ReferencedRWVMSeriesInstanceUIDs.append(referencedSeriesSequence.SeriesInstanceUID)
              else:
                # TODO: those are not used at all
                logging.debug( "Found other reference")
                loadable.ReferencedOtherInstanceUIDs.append(refSOPSequence.ReferencedSOPInstanceUID)

      for segSeriesInstanceUID in loadable.ReferencedSegmentationInstanceUIDs[uid]:
        segLoadables = segPlugin.examine([slicer.dicomDatabase.filesForSeries(segSeriesInstanceUID)])
        for segLoadable in segLoadables:
          loadable.referencedInstanceUIDs += segLoadable.referencedInstanceUIDs

    loadable.referencedInstanceUIDs = list(set(loadable.referencedInstanceUIDs))

    if len(loadable.ReferencedSegmentationInstanceUIDs[uid])>1:
      logging.warning("SR references more than one SEG. This has not been tested!")
    for segUID in loadable.ReferencedSegmentationInstanceUIDs:
      loadable.referencedSegInstanceUIDs.append(segUID)

    if len(loadable.ReferencedRWVMSeriesInstanceUIDs)>1:
      logging.warning("SR references more than one RWVM. This has not been tested!")
    # not adding RWVM instances to referencedSeriesInstanceUIDs
    return loadable
Пример #5
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """
        loadables = []

        # just read the modality type; need to go to reporting logic, since DCMTK
        #   is not wrapped ...

        for file in files:

            uid = slicer.dicomDatabase.fileValue(file,
                                                 self.tags['instanceUID'])
            if uid == '':
                return []

            desc = slicer.dicomDatabase.fileValue(
                file, self.tags['seriesDescription'])
            if desc == "":
                name = "Unknown"

            number = slicer.dicomDatabase.fileValue(file,
                                                    self.tags['seriesNumber'])
            if number == '':
                number = "Unknown"

            reportingLogic = None
            reportingLogic = slicer.modules.reporting.logic()

            isDicomSeg = (slicer.dicomDatabase.fileValue(
                file, self.tags['modality']) == 'SEG')

            if isDicomSeg:
                loadable = DICOMLoadable()
                loadable.files = [file]
                loadable.name = desc + ' - as a DICOM SEG object'
                loadable.tooltip = loadable.name
                loadable.selected = True
                loadable.confidence = 0.95
                loadable.uid = uid
                self.addReferences(loadable)
                refName = self.referencedSeriesName(loadable)
                if refName != "":
                    loadable.name = refName + " " + desc + " - Segmentations"

                loadables.append(loadable)

                print('DICOM SEG modality found')

        return loadables
Пример #6
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
        corresponding to ways of interpreting the
        files parameter.
        Look for the special private creator tags that indicate
        a slicer data bundle
        Note that each data bundle is in a unique series, so
        if 'files' is a list of more than one element, then this
        is not a data bundle.
        """

        loadables = []
        if len(files) == 1:
            f = files[0]
            # get the series description to use as base for volume name
            name = slicer.dicomDatabase.fileValue(f, self.tags['seriesDescription'])
            if name == "":
                name = "Unknown"
            candygramValue = slicer.dicomDatabase.fileValue(f, self.tags['candygram'])

            if candygramValue:
                # default loadable includes all files for series
                loadable = DICOMLoadable()
                loadable.files = [f]
                loadable.name = name + ' - as Slicer Scene'
                loadable.selected = True
                loadable.tooltip = 'Contains a Slicer scene'
                loadable.confidence = 0.9
                loadables.append(loadable)
        return loadables
Пример #7
0
  def createLoadableAndAddReferences(self, dataset):
    loadable = DICOMLoadable()
    loadable.selected = True
    loadable.confidence = 0.95

    if hasattr(dataset, "CurrentRequestedProcedureEvidenceSequence"):
      loadable.referencedSeriesInstanceUIDs = []
      loadable.referencedSOPInstanceUIDs = []
      for refSeriesSequence in dataset.CurrentRequestedProcedureEvidenceSequence:
        for referencedSeriesSequence in refSeriesSequence.ReferencedSeriesSequence:
          for refSOPSequence in referencedSeriesSequence.ReferencedSOPSequence:
            if refSOPSequence.ReferencedSOPClassUID == self.UID_SegmentationStorage: # TODO: differentiate between SR, SEG and other volumes
              logging.debug("Found referenced segmentation")
              loadable.referencedSeriesInstanceUIDs.append(referencedSeriesSequence.SeriesInstanceUID)
            else:
              logging.debug( "Found other reference")
              for sopInstanceUID in slicer.dicomDatabase.fileForInstance(refSOPSequence.ReferencedSOPInstanceUID):
                loadable.referencedSOPInstanceUIDs.append(sopInstanceUID)
                # loadable.referencedSOPInstanceUID = refSOPSequence.ReferencedSOPInstanceUID
    return loadable
Пример #8
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    Process is to look for 'known' private tags corresponding
    to the types of diffusion datasets that the DWIConvert utility
    should be able to process. We look at the first three
    headers because some vendors don't write gradient information
    in the B0.

    For testing:
    dv = slicer.modules.dicomPlugins['DICOMDiffusionVolumePlugin']()
    dv.examineForImport([['/media/extra650/data/DWI-examples/SiemensTrioTimB17-DWI/63000-000025-000001.dcm']])
    """

        # get the series description to use as base for volume name
        name = slicer.dicomDatabase.fileValue(files[0],
                                              self.tags['seriesDescription'])
        if name == "":
            name = "Unknown"

        validDWI = False
        loadables = []

        # The only valid modality is MR
        modality = slicer.dicomDatabase.fileValue(files[0],
                                                  self.tags['modality'])
        if (modality.upper() != "MR"):
            return loadables

        vendorName = ""
        matchingVendors = []
        for vendor in self.diffusionTags:
            for tag in self.diffusionTags[vendor]:
                # Check the first three files because some tags may
                # not be present in the b0 image (e.g. for Siemens)
                for i in range(0, 3 if (len(files) > 2) else len(files)):
                    value = slicer.dicomDatabase.fileValue(files[i], tag)
                    hasTag = value != ""
                    if hasTag and not vendor in matchingVendors:
                        matchingVendors.append(vendor)
            if len(matchingVendors) > 0:
                validDWI = True

        if validDWI:
            # default loadable includes all files for series
            loadable = DICOMLoadable()
            loadable.files = files
            loadable.name = name + ' - as DWI Volume'
            loadable.selected = True
            loadable.tooltip = "Matches DICOM tags for the following vendor(s): %s" % (
                ", ".join(matchingVendors))
            loadable.confidence = 0.6
            loadables = [loadable]
        return loadables
 def examine(self,fileLists):
   """ Returns a list of DICOMLoadable instances
   corresponding to ways of interpreting the
   fileLists parameter.
   """
   petCtStudies = self.__scanForValidPETCTStudies(fileLists)
   
   mainLoadable = DICOMLoadable()
   mainLoadable.tooltip = "PET/CT Studies for Longitudinal PET/CT Report"
   
   studyplrlsing = "Studies" if len(petCtStudies) != 1 else "Study" 
   mainLoadable.name = str(len(petCtStudies))+" PET/CT "+ studyplrlsing
   
   mainLoadable.selected = True
   mainLoadable.confidence = 1.0                
   
   petImageSeries = 0
   ctImageSeries = 0
   
   for fileList in fileLists:
     
     petSeries = self.__getSeriesInformation(fileList, self.tags['seriesModality']) == self.petTerm
     ctSeries = self.__getSeriesInformation(fileList, self.tags['seriesModality']) == self.ctTerm
     fromPetCtStudy = self.__getSeriesInformation(fileList, self.tags['studyInstanceUID']) in petCtStudies
     
     if (petSeries | ctSeries) & fromPetCtStudy:
       
       loadables = self.getCachedLoadables(fileList)
       
       if not loadables:
         loadables = self.scalarVolumePlugin.examineFiles(fileList)
         self.cacheLoadables(fileList, loadables)
       
       for ldbl in loadables:
         if ldbl.selected:
           imageType = ""
           if petSeries:
             petImageSeries += 1
             imageType = "PT"
           elif ctSeries:
             ctImageSeries += 1
             imageType = "CT"
           
           mainLoadable.files += ldbl.files
           
           ldbl.name = imageType +"_"+self.__getSeriesInformation(ldbl.files, self.tags['studyDate'])+"_"+self.__getSeriesInformation(ldbl.files, self.tags['seriesTime'])
           self.cacheLoadables(ldbl.files, [ldbl])   
           if ldbl.warning:
             mainLoadable.warning += ldbl.warning +" "
           
           break # break for loop because only one loadable needed  
   
   loadables = []
   
   if mainLoadable.files:
     mainLoadable.name = mainLoadable.name + " containing " + str(petImageSeries) + " PET and " + str(ctImageSeries) + " CT image series"
     loadables = [mainLoadable]
            
   return loadables
Пример #10
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """
        loadables = []

        # just read the modality type; need to go to reporting logic, since DCMTK
        #   is not wrapped ...

        for cFile in files:

            uid = slicer.dicomDatabase.fileValue(cFile,
                                                 self.tags['instanceUID'])
            if uid == '':
                return []

            desc = slicer.dicomDatabase.fileValue(
                cFile, self.tags['seriesDescription'])
            if desc == '':
                desc = "Unknown"

            isDicomPM = (slicer.dicomDatabase.fileValue(
                cFile, self.tags['classUID']) == '1.2.840.10008.5.1.4.1.1.30')

            if isDicomPM:
                loadable = DICOMLoadable()
                loadable.files = [cFile]
                loadable.name = desc + ' - as a DICOM Parametric Map object'
                loadable.tooltip = loadable.name
                loadable.selected = True
                loadable.confidence = 0.95
                loadable.uid = uid
                self.addReferences(loadable)
                refName = self.referencedSeriesName(loadable)
                if refName != "":
                    loadable.name = refName + " " + desc + " - ParametricMap"

                loadables.append(loadable)

                logging.debug('DICOM Parametric Map found')

        return loadables
  def examineFiles(self,files):

    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """
    loadables = []

    # just read the modality type; need to go to reporting logic, since DCMTK
    #   is not wrapped ...

    for file in files:

      uid = slicer.dicomDatabase.fileValue(file, self.tags['instanceUID'])
      if uid == '':
        return []

      desc = slicer.dicomDatabase.fileValue(file, self.tags['seriesDescription'])
      if desc == "":
        name = "Unknown"

      number = slicer.dicomDatabase.fileValue(file, self.tags['seriesNumber'])
      if number == '':
        number = "Unknown"

      isDicomSeg = (slicer.dicomDatabase.fileValue(file, self.tags['modality']) == 'SEG')

      if isDicomSeg:
        loadable = DICOMLoadable()
        loadable.files = [file]
        loadable.name = desc + ' - as a DICOM SEG object'
        loadable.tooltip = loadable.name
        loadable.selected = True
        loadable.confidence = 0.95
        loadable.uid = uid
        self.addReferences(loadable)
        refName = self.referencedSeriesName(loadable)
        if refName != "":
          loadable.name = refName + " " + desc + " - Segmentations"

        loadables.append(loadable)

        print('DICOM SEG modality found')

    return loadables
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    Process is to look for 'known' private tags corresponding
    to the types of diffusion datasets that the DicomToNrrd utility
    should be able to process.  Only need to look at one header
    in the series since all should be the same with respect
    to this check.

    For testing:
    dv = slicer.modules.dicomPlugins['DICOMDiffusionVolumePlugin']()
    dv.examineForImport([['/media/extra650/data/DWI-examples/SiemensTrioTimB17-DWI/63000-000025-000001.dcm']])
    """

        # get the series description to use as base for volume name
        name = slicer.dicomDatabase.fileValue(files[0],
                                              self.tags['seriesDescription'])
        if name == "":
            name = "Unknown"

        validDWI = False
        vendorName = ""
        for vendor in self.diffusionTags:
            matchesVendor = True
            for tag in self.diffusionTags[vendor]:
                # Check the first three files because some tags may
                # not be present in the b0 image (e.g. for Siemens)
                for i in xrange(0, 3 if (len(files) > 2) else len(files)):
                    value = slicer.dicomDatabase.fileValue(files[i], tag)
                    hasTag = value != ""
                    if hasTag:
                        matchesVendor &= hasTag
            if matchesVendor:
                validDWI = True
                vendorName = vendor

        loadables = []
        if validDWI:
            # default loadable includes all files for series
            loadable = DICOMLoadable()
            loadable.files = files
            loadable.name = name + ' - as DWI Volume'
            loadable.selected = True
            loadable.tooltip = "Appears to be DWI from vendor %s" % vendorName
            loadable.confidence = 0.4
            loadables = [loadable]
        return loadables
Пример #13
0
  def examineFiles(self,files):

    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """
    loadables = []

    # just read the modality type; need to go to reporting logic, since DCMTK
    #   is not wrapped ...

    for cFile in files:

      uid = slicer.dicomDatabase.fileValue(cFile, self.tags['instanceUID'])
      if uid == '':
        return []

      desc = slicer.dicomDatabase.fileValue(cFile, self.tags['seriesDescription'])
      if desc == '':
        desc = "Unknown"

      isDicomPM = (slicer.dicomDatabase.fileValue(cFile, self.tags['classUID']) == '1.2.840.10008.5.1.4.1.1.30')

      if isDicomPM:
        loadable = DICOMLoadable()
        loadable.files = [cFile]
        loadable.name = desc + ' - as a DICOM Parametric Map object'
        loadable.tooltip = loadable.name
        loadable.selected = True
        loadable.confidence = 0.95
        loadable.uid = uid
        self.addReferences(loadable)
        refName = self.referencedSeriesName(loadable)
        if refName != "":
          loadable.name = refName + " " + desc + " - ParametricMap"

        loadables.append(loadable)

        logging.debug('DICOM Parametric Map found')

    return loadables
Пример #14
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """
        loadables = []

        # just read the modality type; need to go to reporting logic, since DCMTK
        #   is not wrapped ...

        for cFile in files:

            uid = slicer.dicomDatabase.fileValue(cFile,
                                                 self.tags['instanceUID'])
            if uid == '':
                return []

            desc = slicer.dicomDatabase.fileValue(
                cFile, self.tags['seriesDescription'])
            if desc == '':
                desc = "Unknown"

            isDicomSeg = (slicer.dicomDatabase.fileValue(
                cFile, self.tags['modality']) == 'SEG')

            if isDicomSeg:
                loadable = DICOMLoadable()
                loadable.files = [cFile]
                loadable.name = desc
                loadable.tooltip = loadable.name + ' - as a DICOM SEG object'
                loadable.selected = True
                loadable.confidence = 0.95
                loadable.uid = uid
                self.addReferences(loadable)

                loadables.append(loadable)

                logging.debug('DICOM SEG modality found')

        return loadables
Пример #15
0
  def examineFiles(self,files):
    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    Process is to look for 'known' private tags corresponding
    to the types of diffusion datasets that the DicomToNrrd utility
    should be able to process.  Only need to look at one header
    in the series since all should be the same with respect
    to this check.

    For testing:
    dv = slicer.modules.dicomPlugins['DICOMDiffusionVolumePlugin']()
    dv.examineForImport([['/media/extra650/data/DWI-examples/SiemensTrioTimB17-DWI/63000-000025-000001.dcm']])
    """

    # get the series description to use as base for volume name
    name = slicer.dicomDatabase.fileValue(files[0], self.tags['seriesDescription'])
    if name == "":
      name = "Unknown"

    validDWI = False
    vendorName = ""
    for vendor in self.diffusionTags:
      matchesVendor = True
      for tag in self.diffusionTags[vendor]:
        # Check the first three files because some tags may
        # not be present in the b0 image (e.g. for Siemens)
        for i in xrange(0, 3 if (len(files) > 2) else len(files)):
          value = slicer.dicomDatabase.fileValue(files[i], tag)
          hasTag = value != ""
          if hasTag:
            matchesVendor &= hasTag
      if matchesVendor:
        validDWI = True
        vendorName = vendor

    loadables = []
    if validDWI:
      # default loadable includes all files for series
      loadable = DICOMLoadable()
      loadable.files = files
      loadable.name = name + ' - as DWI Volume'
      loadable.selected = True
      loadable.tooltip = "Appears to be DWI from vendor %s" % vendorName
      loadable.confidence = 0.4
      loadables = [loadable]
    return loadables
Пример #16
0
    def convertUltrasoundDicomToNrrd(self, inputDicomFilePath,
                                     outputNrrdFilePath):

        # Load from DICOM
        from DICOMLib import DICOMLoadable
        loadable = DICOMLoadable()
        loadable.files = [inputDicomFilePath]
        loadable.name = os.path.basename(inputDicomFilePath)
        loadable.tooltip = ''
        loadable.selected = True
        loadable.confidence = 1.
        loadable.createBrowserNode = False  # don't create browser nodes (it would clutter the scene when batch processing)
        dicomPlugin = slicer.modules.dicomPlugins['DicomUltrasoundPlugin']()
        sequenceNode = dicomPlugin.load(loadable)

        # Write to NRRD
        storageNode = sequenceNode.GetStorageNode()
        storageNode.SetFileName(outputNrrdFilePath)
        success = storageNode.WriteData(sequenceNode)

        # Clean up
        slicer.mrmlScene.RemoveNode(sequenceNode)

        return success
Пример #17
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    Process is to look for 'known' private tags corresponding
    to the types of diffusion datasets that the DicomToNrrd utility
    should be able to process.  Only need to look at one header
    in the series since all should be the same with respect
    to this check.

    For testing:
    dv = slicer.modules.dicomPlugins['DICOMTractographyPlugin']()
    dv.examineForImport([['/media/extra650/data/DWI-examples/SiemensTrioTimB17-DWI/63000-000025-000001.dcm']])
    """

        # get the series description to use as base for volume name
        name = slicer.dicomDatabase.fileValue(files[0],
                                              self.tags['seriesDescription'])
        if name == "":
            name = "Unknown"

        validTractObject = False
        for tag in self.tags.keys():
            value = slicer.dicomDatabase.fileValue(files[0], tag)
            hasTag = (value != "")
            validTractObject = True

        vendorName = slicer.dicomDatabase.fileValue(files[0], "0008,0070")

        loadables = []
        if validTractObject:
            # default loadable includes all files for series
            loadable = DICOMLoadable()
            loadable.files = files
            loadable.name = name + ' - as DWI Volume'
            loadable.selected = False
            loadable.tooltip = "Appears to be DWI from vendor %s" % vendorName
            loadable.confidence = 0.75
            loadables = [loadable]
        return loadables
  def convertUltrasoundDicomToNrrd(self, inputDicomFilePath, outputNrrdFilePath):

    # Load from DICOM
    from DICOMLib import DICOMLoadable
    loadable = DICOMLoadable()
    loadable.files = [inputDicomFilePath]
    loadable.name = os.path.basename(inputDicomFilePath)
    loadable.tooltip = ''
    loadable.selected = True
    loadable.confidence = 1.
    loadable.createBrowserNode = False # don't create browser nodes (it would clutter the scene when batch processing)
    dicomPlugin = slicer.modules.dicomPlugins['DicomUltrasoundPlugin']()        
    sequenceNode = dicomPlugin.load(loadable)
    
    # Write to NRRD
    storageNode = sequenceNode.GetStorageNode()
    storageNode.SetFileName(outputNrrdFilePath)
    success = storageNode.WriteData(sequenceNode)
    
    # Clean up
    slicer.mrmlScene.RemoveNode(sequenceNode)
    
    return success    
Пример #19
0
  def examineFiles(self,files):
    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """
    loadables = []

    # just read the modality type; need to go to reporting logic, since DCMTK
    #   is not wrapped ...

    for cFile in files:

      uid = slicer.dicomDatabase.fileValue(cFile, self.tags['instanceUID'])
      if uid == '':
        return []

      desc = slicer.dicomDatabase.fileValue(cFile, self.tags['seriesDescription'])
      if desc == '':
        desc = "Unknown"

      isDicomSeg = (slicer.dicomDatabase.fileValue(cFile, self.tags['modality']) == 'SEG')

      if isDicomSeg:
        loadable = DICOMLoadable()
        loadable.files = [cFile]
        loadable.name = desc
        loadable.tooltip = loadable.name + ' - as a DICOM SEG object'
        loadable.selected = True
        loadable.confidence = 0.95
        loadable.uid = uid
        self.addReferences(loadable)

        loadables.append(loadable)

        logging.debug('DICOM SEG modality found')

    return loadables
  def examineFiles(self,files):
    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    Process is to look for 'known' private tags corresponding
    to the types of diffusion datasets that the DicomToNrrd utility
    should be able to process.  Only need to look at one header
    in the series since all should be the same with respect
    to this check.

    For testing:
    dv = slicer.modules.dicomPlugins['DICOMTractographyPlugin']()
    dv.examineForImport([['/media/extra650/data/DWI-examples/SiemensTrioTimB17-DWI/63000-000025-000001.dcm']])
    """

    # get the series description to use as base for volume name
    name = slicer.dicomDatabase.fileValue(files[0], self.tags['seriesDescription'])
    if name == "":
      name = "Unknown"

    validTractObject = False
    for tag in self.tags.keys():
      value = slicer.dicomDatabase.fileValue(files[0], tag)
      hasTag = (value != "")
      validTractObject = True

    vendorName = slicer.dicomDatabase.fileValue(files[0], "0008,0070")

    loadables = []
    if validTractObject:
      # default loadable includes all files for series
      loadable = DICOMLoadable()
      loadable.files = files
      loadable.name = name + ' - as DWI Volume'
      loadable.selected = False
      loadable.tooltip = "Appears to be DWI from vendor %s" % vendorName
      loadable.confidence = 0.75
      loadables = [loadable]
    return loadables
    def createLoadableAndAddReferences(self, dataset):
        loadable = DICOMLoadable()
        loadable.selected = True
        loadable.confidence = 0.95

        if hasattr(dataset, "CurrentRequestedProcedureEvidenceSequence"):
            loadable.referencedSeriesInstanceUIDs = []
            loadable.referencedSOPInstanceUIDs = []
            for refSeriesSequence in dataset.CurrentRequestedProcedureEvidenceSequence:
                for referencedSeriesSequence in refSeriesSequence.ReferencedSeriesSequence:
                    for refSOPSequence in referencedSeriesSequence.ReferencedSOPSequence:
                        if refSOPSequence.ReferencedSOPClassUID == self.UID_SegmentationStorage:  # TODO: differentiate between SR, SEG and other volumes
                            logging.debug("Found referenced segmentation")
                            loadable.referencedSeriesInstanceUIDs.append(
                                referencedSeriesSequence.SeriesInstanceUID)
                        else:
                            logging.debug("Found other reference")
                            for sopInstanceUID in slicer.dicomDatabase.fileForInstance(
                                    refSOPSequence.ReferencedSOPInstanceUID):
                                loadable.referencedSOPInstanceUIDs.append(
                                    sopInstanceUID)
                                # loadable.referencedSOPInstanceUID = refSOPSequence.ReferencedSOPInstanceUID
        return loadable
Пример #22
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """

        seriesUID = slicer.dicomDatabase.fileValue(files[0],
                                                   self.tags['seriesUID'])
        seriesName = self.defaultSeriesNodeName(seriesUID)

        # default loadable includes all files for series
        loadable = DICOMLoadable()
        loadable.files = files
        loadable.name = seriesName
        loadable.tooltip = "%d files, first file: %s" % (len(
            loadable.files), loadable.files[0])
        loadable.selected = True
        # add it to the list of loadables later, if pixel data is available in at least one file

        # make subseries volumes based on tag differences
        subseriesTags = [
            "seriesInstanceUID",
            "imageOrientationPatient",
            "diffusionGradientOrientation",
        ]

        if self.allowLoadingByTime():
            subseriesTags.append("contentTime")
            subseriesTags.append("triggerTime")

        #
        # first, look for subseries within this series
        # - build a list of files for each unique value
        #   of each tag
        #
        subseriesFiles = {}
        subseriesValues = {}
        for file in loadable.files:
            # check for subseries values
            for tag in subseriesTags:
                value = slicer.dicomDatabase.fileValue(file, self.tags[tag])
                value = value.replace(
                    ",", "_")  # remove commas so it can be used as an index
                if tag not in subseriesValues:
                    subseriesValues[tag] = []
                if not subseriesValues[tag].__contains__(value):
                    subseriesValues[tag].append(value)
                if (tag, value) not in subseriesFiles:
                    subseriesFiles[tag, value] = []
                subseriesFiles[tag, value].append(file)

        loadables = []

        # Pixel data is available, so add the default loadable to the output
        loadables.append(loadable)

        #
        # second, for any tags that have more than one value, create a new
        # virtual series
        #
        for tag in subseriesTags:
            if len(subseriesValues[tag]) > 1:
                for valueIndex, value in enumerate(subseriesValues[tag]):
                    # default loadable includes all files for series
                    loadable = DICOMLoadable()
                    loadable.files = subseriesFiles[tag, value]
                    # value can be a long string (and it will be used for generating node name)
                    # therefore use just an index instead
                    loadable.name = seriesName + " - %s %d" % (tag,
                                                               valueIndex + 1)
                    loadable.tooltip = "%d files, grouped by %s = %s. First file: %s" % (
                        len(loadable.files), tag, value, loadable.files[0])
                    loadable.selected = False
                    loadables.append(loadable)

        # remove any files from loadables that don't have pixel data (no point sending them to ITK for reading)
        # also remove DICOM SEG, since it is not handled by ITK readers
        newLoadables = []
        for loadable in loadables:
            newFiles = []
            excludedLoadable = False
            for file in loadable.files:
                if slicer.dicomDatabase.fileValue(
                        file, self.tags['pixelData']) != '':
                    newFiles.append(file)
                if slicer.dicomDatabase.fileValue(
                        file, self.tags['sopClassUID']
                ) == '1.2.840.10008.5.1.4.1.1.66.4':
                    excludedLoadable = True
                    logging.error(
                        'Please install Quantitative Reporting extension to enable loading of DICOM Segmentation objects'
                    )
                elif slicer.dicomDatabase.fileValue(
                        file, self.tags['sopClassUID']
                ) == '1.2.840.10008.5.1.4.1.1.481.3':
                    excludedLoadable = True
                    logging.error(
                        'Please install SlicerRT extension to enable loading of DICOM RT Structure Set objects'
                    )
            if len(newFiles) > 0 and not excludedLoadable:
                loadable.files = newFiles
                newLoadables.append(loadable)
            elif excludedLoadable:
                continue
            else:
                # here all files in have no pixel data, so they might be
                # secondary capture images which will read, so let's pass
                # them through with a warning and low confidence
                loadable.warning += "There is no pixel data attribute for the DICOM objects, but they might be readable as secondary capture images.  "
                loadable.confidence = 0.2
                newLoadables.append(loadable)
        loadables = newLoadables

        #
        # now for each series and subseries, sort the images
        # by position and check for consistency
        #
        for loadable in loadables:
            loadable.files, distances, loadable.warning = DICOMUtils.getSortedImageFiles(
                loadable.files, self.epsilon)

        return loadables
  def examineFiles(self,files):
    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """
    loadables = []

    if len(files) > 1:
      # there should only be one instance per 4D volume
      return []

    filePath = files[0]
    
    # currently only this one (bogus, non-standard) Philips 4D US format is supported
    supportedSOPClassUID = '1.2.840.113543.6.6.1.3.10002'
    
    # Quick check of SOP class UID without parsing the file...
    try:
      sopClassUID = slicer.dicomDatabase.fileValue(filePath,self.tags['sopClassUID'])
      if sopClassUID != supportedSOPClassUID:
        # Unsupported class
        return []
    except Exception as e:
      # Quick check could not be completed (probably Slicer DICOM database is not initialized).
      # No problem, we'll try to parse the file and check the SOP class UID then.
      pass 
    
    try:
      ds = dicom.read_file(filePath, stop_before_pixels=True)
    except Exception as e:
      logging.debug("Failed to parse DICOM file: {0}".format(e.message))
      return [] 

    if ds.SOPClassUID != supportedSOPClassUID:
      # Unsupported class
      return []
      
    if ds.PhotometricInterpretation != 'MONOCHROME2':
      logging.warning('Warning: unsupported PhotometricInterpretation')
      loadable.confidence = .4

    if ds.BitsAllocated != 8 or ds.BitsStored != 8 or ds.HighBit != 7:
      logging.warning('Warning: Bad scalar type (not unsigned byte)')
      loadable.confidence = .4

    if ds.PhysicalUnitsXDirection != 3 or ds.PhysicalUnitsYDirection != 3:
      logging.warning('Warning: Units not in centimeters')
      loadable.confidence = .4

    if ds.SamplesPerPixel != 1:
      logging.warning('Warning: multiple samples per pixel')
      loadable.confidence = .4

    name = ''
    if hasattr(ds,'SeriesNumber') and ds.SeriesNumber:
      name = '{0}:'.format(ds.SeriesNumber)
    if hasattr(ds,'Modality') and ds.Modality:
      name = '{0} {1}'.format(name, ds.Modality)
    if hasattr(ds,'SeriesDescription') and ds.SeriesDescription:
      name = '{0} {1}'.format(name, ds.SeriesDescription)
    if hasattr(ds,'InstanceNumber') and ds.InstanceNumber:
      name = '{0} [{1}]'.format(name, ds.InstanceNumber)

    loadable = DICOMLoadable()
    loadable.files = files
    loadable.name = name.strip() # remove leading and trailing spaces, if any
    loadable.tooltip = "Philips 4D Ultrasound"
    loadable.selected = True
    loadable.confidence = 1.
    loadables.append(loadable)

    return loadables
Пример #24
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """

        # get the series description to use as base for volume name
        name = slicer.dicomDatabase.fileValue(files[0],
                                              self.tags['seriesDescription'])
        if name == "":
            name = "Unknown"
        num = slicer.dicomDatabase.fileValue(files[0],
                                             self.tags['seriesNumber'])
        if num != "":
            name = num + ": " + name

        # default loadable includes all files for series
        loadable = DICOMLoadable()
        loadable.files = files
        loadable.name = name
        loadable.tooltip = "%d files, first file: %s" % (len(
            loadable.files), loadable.files[0])
        loadable.selected = True
        # add it to the list of loadables later, if pixel data is available in at least one file

        # while looping through files, keep track of their
        # position and orientation for later use
        positions = {}
        orientations = {}

        # make subseries volumes based on tag differences
        subseriesTags = [
            "seriesInstanceUID",
            "contentTime",
            "triggerTime",
            "diffusionGradientOrientation",
            "imageOrientationPatient",
        ]

        # it will be set to true if pixel data is found in any of the files
        pixelDataAvailable = False

        #
        # first, look for subseries within this series
        # - build a list of files for each unique value
        #   of each tag
        #
        subseriesFiles = {}
        subseriesValues = {}
        for file in loadable.files:

            # save position and orientation
            positions[file] = slicer.dicomDatabase.fileValue(
                file, self.tags['position'])
            if positions[file] == "":
                positions[file] = None
            orientations[file] = slicer.dicomDatabase.fileValue(
                file, self.tags['orientation'])
            if orientations[file] == "":
                orientations[file] = None

            # check for subseries values
            for tag in subseriesTags:
                value = slicer.dicomDatabase.fileValue(file, self.tags[tag])
                value = value.replace(
                    ",", "_")  # remove commas so it can be used as an index
                if not subseriesValues.has_key(tag):
                    subseriesValues[tag] = []
                if not subseriesValues[tag].__contains__(value):
                    subseriesValues[tag].append(value)
                if not subseriesFiles.has_key((tag, value)):
                    subseriesFiles[tag, value] = []
                subseriesFiles[tag, value].append(file)

        loadables = []

        # Pixel data is available, so add the default loadable to the output
        loadables.append(loadable)

        #
        # second, for any tags that have more than one value, create a new
        # virtual series
        #
        for tag in subseriesTags:
            if len(subseriesValues[tag]) > 1:
                for value in subseriesValues[tag]:
                    # default loadable includes all files for series
                    loadable = DICOMLoadable()
                    loadable.files = subseriesFiles[tag, value]
                    loadable.name = name + " for %s of %s" % (tag, value)
                    loadable.tooltip = "%d files, first file: %s" % (len(
                        loadable.files), loadable.files[0])
                    loadable.selected = False
                    loadables.append(loadable)

        # remove any files from loadables that don't have pixel data (no point sending them to ITK for reading)
        newLoadables = []
        for loadable in loadables:
            newFiles = []
            for file in loadable.files:
                if slicer.dicomDatabase.fileValue(
                        file, self.tags['pixelData']) != '':
                    newFiles.append(file)
            if len(newFiles) > 0:
                loadable.files = newFiles
                newLoadables.append(loadable)
            else:
                # here all files in have no pixel data, so they might be
                # secondary capture images which will read, so let's pass
                # them through with a warning and low confidence
                loadable.warning += "There is no pixel data attribute for the DICOM objects, but they might be readable as secondary capture images.  "
                loadable.confidence = 0.2
                newLoadables.append(loadable)
        loadables = newLoadables

        #
        # now for each series and subseries, sort the images
        # by position and check for consistency
        #

        # TODO: more consistency checks:
        # - is there gantry tilt?
        # - are the orientations the same for all slices?
        for loadable in loadables:
            #
            # use the first file to get the ImageOrientationPatient for the
            # series and calculate the scan direction (assumed to be perpendicular
            # to the acquisition plane)
            #
            value = slicer.dicomDatabase.fileValue(loadable.files[0],
                                                   self.tags['numberOfFrames'])
            if value != "":
                loadable.warning += "Multi-frame image. If slice orientation or spacing is non-uniform then the image may be displayed incorrectly. Use with caution.  "

            validGeometry = True
            ref = {}
            for tag in [self.tags['position'], self.tags['orientation']]:
                value = slicer.dicomDatabase.fileValue(loadable.files[0], tag)
                if not value or value == "":
                    loadable.warning += "Reference image in series does not contain geometry information.  Please use caution.  "
                    validGeometry = False
                    loadable.confidence = 0.2
                    break
                ref[tag] = value

            if not validGeometry:
                continue

            # get the geometry of the scan
            # with respect to an arbitrary slice
            sliceAxes = [
                float(zz) for zz in ref[self.tags['orientation']].split('\\')
            ]
            x = sliceAxes[:3]
            y = sliceAxes[3:]
            scanAxis = self.cross(x, y)
            scanOrigin = [
                float(zz) for zz in ref[self.tags['position']].split('\\')
            ]

            #
            # for each file in series, calculate the distance along
            # the scan axis, sort files by this
            #
            sortList = []
            missingGeometry = False
            for file in loadable.files:
                if not positions[file]:
                    missingGeometry = True
                    break
                position = [float(zz) for zz in positions[file].split('\\')]
                vec = self.difference(position, scanOrigin)
                dist = self.dot(vec, scanAxis)
                sortList.append((file, dist))

            if missingGeometry:
                loadable.warning += "One or more images is missing geometry information.  "
            else:
                sortedFiles = sorted(sortList, key=lambda x: x[1])
                distances = {}
                loadable.files = []
                for file, dist in sortedFiles:
                    loadable.files.append(file)
                    distances[file] = dist

                #
                # confirm equal spacing between slices
                # - use variable 'epsilon' to determine the tolerance
                #
                spaceWarnings = 0
                if len(loadable.files) > 1:
                    file0 = loadable.files[0]
                    file1 = loadable.files[1]
                    dist0 = distances[file0]
                    dist1 = distances[file1]
                    spacing0 = dist1 - dist0
                    n = 1
                    for fileN in loadable.files[1:]:
                        fileNminus1 = loadable.files[n - 1]
                        distN = distances[fileN]
                        distNminus1 = distances[fileNminus1]
                        spacingN = distN - distNminus1
                        spaceError = spacingN - spacing0
                        if abs(spaceError) > self.epsilon:
                            spaceWarnings += 1
                            loadable.warning += "Images are not equally spaced (a difference of %g in spacings was detected).  Slicer will load this series as if it had a spacing of %g.  Please use caution.  " % (
                                spaceError, spacing0)
                            break
                        n += 1

                if spaceWarnings != 0:
                    logging.warning(
                        "Geometric issues were found with %d of the series.  Please use caution."
                        % spaceWarnings)

        return loadables
Пример #25
0
  def examineFiles(self,files):
    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """

    seriesUID = slicer.dicomDatabase.fileValue(files[0],self.tags['seriesUID'])
    seriesName = self.defaultSeriesNodeName(seriesUID)

    # default loadable includes all files for series
    loadable = DICOMLoadable()
    loadable.files = files
    loadable.name = seriesName
    loadable.tooltip = "%d files, first file: %s" % (len(loadable.files), loadable.files[0])
    loadable.selected = True
    # add it to the list of loadables later, if pixel data is available in at least one file

    # make subseries volumes based on tag differences
    subseriesTags = [
        "seriesInstanceUID",
        "imageOrientationPatient",
        "diffusionGradientOrientation",
    ]

    if self.allowLoadingByTime():
      subseriesTags.append("contentTime")
      subseriesTags.append("triggerTime")

    #
    # first, look for subseries within this series
    # - build a list of files for each unique value
    #   of each tag
    #
    subseriesFiles = {}
    subseriesValues = {}
    for file in loadable.files:
      # check for subseries values
      for tag in subseriesTags:
        value = slicer.dicomDatabase.fileValue(file,self.tags[tag])
        value = value.replace(",","_") # remove commas so it can be used as an index
        if tag not in subseriesValues:
          subseriesValues[tag] = []
        if not subseriesValues[tag].__contains__(value):
          subseriesValues[tag].append(value)
        if (tag,value) not in subseriesFiles:
          subseriesFiles[tag,value] = []
        subseriesFiles[tag,value].append(file)

    loadables = []

    # Pixel data is available, so add the default loadable to the output
    loadables.append(loadable)

    #
    # second, for any tags that have more than one value, create a new
    # virtual series
    #
    for tag in subseriesTags:
      if len(subseriesValues[tag]) > 1:
        for valueIndex, value in enumerate(subseriesValues[tag]):
          # default loadable includes all files for series
          loadable = DICOMLoadable()
          loadable.files = subseriesFiles[tag,value]
          # value can be a long string (and it will be used for generating node name)
          # therefore use just an index instead
          loadable.name = seriesName + " - %s %d" % (tag,valueIndex+1)
          loadable.tooltip = "%d files, grouped by %s = %s. First file: %s" % (len(loadable.files), tag, value, loadable.files[0])
          loadable.selected = False
          loadables.append(loadable)

    # remove any files from loadables that don't have pixel data (no point sending them to ITK for reading)
    # also remove DICOM SEG, since it is not handled by ITK readers
    newLoadables = []
    for loadable in loadables:
      newFiles = []
      excludedLoadable = False
      for file in loadable.files:
        if slicer.dicomDatabase.fileValue(file,self.tags['pixelData'])!='':
          newFiles.append(file)
        if slicer.dicomDatabase.fileValue(file,self.tags['sopClassUID'])=='1.2.840.10008.5.1.4.1.1.66.4':
          excludedLoadable = True
          logging.error('Please install Quantitative Reporting extension to enable loading of DICOM Segmentation objects')
        elif slicer.dicomDatabase.fileValue(file,self.tags['sopClassUID'])=='1.2.840.10008.5.1.4.1.1.481.3':
          excludedLoadable = True
          logging.error('Please install SlicerRT extension to enable loading of DICOM RT Structure Set objects')
      if len(newFiles) > 0 and not excludedLoadable:
        loadable.files = newFiles
        newLoadables.append(loadable)
      elif excludedLoadable:
        continue
      else:
        # here all files in have no pixel data, so they might be
        # secondary capture images which will read, so let's pass
        # them through with a warning and low confidence
        loadable.warning += "There is no pixel data attribute for the DICOM objects, but they might be readable as secondary capture images.  "
        loadable.confidence = 0.2
        newLoadables.append(loadable)
    loadables = newLoadables

    #
    # now for each series and subseries, sort the images
    # by position and check for consistency
    #
    for loadable in loadables:
      loadable.files, distances, loadable.warning = DICOMUtils.getSortedImageFiles(loadable.files, self.epsilon)

    return loadables
Пример #26
0
    def examineEigenArtemis3DUS(self, filePath):
        supportedSOPClassUID = '1.2.840.10008.5.1.4.1.1.3.1'  # UltrasoundMultiframeImageStorage

        # Quick check of SOP class UID without parsing the file...
        try:
            sopClassUID = slicer.dicomDatabase.fileValue(
                filePath, self.tags['sopClassUID'])
            if sopClassUID != supportedSOPClassUID:
                # Unsupported class
                #logging.debug("Not EigenArtemis3DUS: sopClassUID "+sopClassUID+" != supportedSOPClassUID "+supportedSOPClassUID)
                return []
        except Exception as e:
            # Quick check could not be completed (probably Slicer DICOM database is not initialized).
            # No problem, we'll try to parse the file and check the SOP class UID then.
            pass

        try:
            ds = dicom.read_file(filePath, stop_before_pixels=True)
        except Exception as e:
            logging.debug("Failed to parse DICOM file: {0}".format(str(e)))
            return []

        if ds.SOPClassUID != supportedSOPClassUID:
            # Unsupported class
            logging.debug("Not EigenArtemis3DUS: sopClassUID " +
                          ds.SOPClassUID + " != supportedSOPClassUID " +
                          supportedSOPClassUID)
            return []

        if not (ds.Manufacturer == "Eigen"
                and ds.ManufacturerModelName == "Artemis"):
            return []

        confidence = 0.9

        if ds.PhotometricInterpretation != 'MONOCHROME2':
            logging.warning('Warning: unsupported PhotometricInterpretation')
            confidence = .4

        if ds.BitsAllocated != 8 or ds.BitsStored != 8 or ds.HighBit != 7:
            logging.warning('Warning: Bad scalar type (not unsigned byte)')
            confidence = .4

        if ds.SamplesPerPixel != 1:
            logging.warning('Warning: multiple samples per pixel')
            confidence = .4
        """Use manufacturer-specific conventions, per communication
    from Rajesh Venkateraman (email to Andrey Fedorov et al on Feb 10, 2020):

    > please take the PixelAspectRatio tag divide by 1000 and that would be your isotropic resolution for display in all 3 dimensions.
    >
    > Image Patient Orientation would be [1 0 0; 0 1 0] for each frame
    >
    > The origin of the volume is the center of the 3D cube and the span would be
    >
    > For X: [-0.5*Rows*PixelAspectRatio[0]/1000, -0.5*Rows*PixelAspectRatio[0]/1000 ]
    >
    > For Y: [-0.5*Columns*PixelAspectRatio[0]/1000, -0.5*Columns*PixelAspectRatio[0]/1000 ]
    >
    > For Z: [-0.5*NumberOfSlices*PixelAspectRatio[0]/1000, -0.5* NumberOfSlices *PixelAspectRatio[0]/1000 ]

    Also, cross-check with the private attributes, if those are available.
    """

        pixelSpacingPrivate = None
        pixelSpacingPublic = None

        try:
            pixelSpacingPrivateTag = findPrivateTag(ds, 0x1129, 0x16,
                                                    "Eigen, Inc")
            if pixelSpacingPrivateTag is None:
                pixelSpacingPrivateTag = findPrivateTag(
                    ds, 0x1129, 0x16, "Eigen Artemis")
            if pixelSpacingPrivateTag is not None:
                pixelSpacingPrivate = float(ds[pixelSpacingPrivateTag].value)
        except KeyError:
            logging.warning(
                "examineEigenArtemis3DUS: spacing not available in private tag"
            )

        if hasattr(ds, "PixelAspectRatio"):
            if ds.PixelAspectRatio[0] != ds.PixelAspectRatio[1]:
                logging.warning(
                    "examineEigenArtemis3DUS: PixelAspectRatio items are not equal!"
                )
            pixelSpacingPublic = float(ds.PixelAspectRatio[0]) / 1000.
            if pixelSpacingPrivate is not None and pixelSpacingPrivate != pixelSpacingPublic:
                logging.warning(
                    "examineEigenArtemis3DUS: private tag based spacing does not match computed spacing"
                )
        else:
            if pixelSpacingPrivate is None:
                logging.debug(
                    "examineEigenArtemis3DUS: unable to find spacing information"
                )
                return []

        # prefer private, if available
        outputSpacing = pixelSpacingPrivate if pixelSpacingPrivate is not None else pixelSpacingPublic
        outputOrigin = [
            0.5 * float(ds.Rows) * outputSpacing,
            0.5 * float(ds.Columns) * outputSpacing,
            -0.5 * float(ds.NumberOfSlices) * outputSpacing
        ]

        logging.debug("examineEigenArtemis3DUS: assumed pixel spacing: %s" %
                      str(outputSpacing))

        name = ''
        if hasattr(ds, 'SeriesNumber') and ds.SeriesNumber:
            name = '{0}:'.format(ds.SeriesNumber)
        if hasattr(ds, 'Modality') and ds.Modality:
            name = '{0} {1}'.format(name, ds.Modality)
        if hasattr(ds, 'SeriesDescription') and ds.SeriesDescription:
            name = '{0} {1}'.format(name, ds.SeriesDescription)
        else:
            name = name + " Eigen Artemis 3D US"
        if hasattr(ds, 'InstanceNumber') and ds.InstanceNumber:
            name = '{0} [{1}]'.format(name, ds.InstanceNumber)

        loadable = DICOMLoadable()
        loadable.files = [filePath]
        loadable.name = name.strip(
        )  # remove leading and trailing spaces, if any
        loadable.tooltip = "Eigen Artemis 3D ultrasound"
        loadable.selected = True
        loadable.confidence = confidence
        loadable.spacing = outputSpacing
        loadable.origin = outputOrigin

        return [loadable]
Пример #27
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """

        self.detailedLogging = slicer.util.settingsValue(
            'DICOM/detailedLogging', False, converter=slicer.util.toBool)

        supportedSOPClassUIDs = [
            '1.2.840.10008.5.1.4.1.1.12.1',  # X-Ray Angiographic Image Storage
            '1.2.840.10008.5.1.4.1.1.12.2',  # X-Ray Fluoroscopy Image Storage
            '1.2.840.10008.5.1.4.1.1.3.1',  # Ultrasound Multiframe Image Storage
            '1.2.840.10008.5.1.4.1.1.6.1',  # Ultrasound Image Storage
            '1.2.840.10008.5.1.4.1.1.7',  # Secondary Capture Image Storage (only accepted for modalities that typically acquire 2D image sequences)
            '1.2.840.10008.5.1.4.1.1.4',  # MR Image Storage (will be only accepted if cine-MRI)
        ]

        # Modalities that typically acquire 2D image sequences:
        suppportedSecondaryCaptureModalities = ['US', 'XA', 'RF', 'ES']

        # Each instance will be a loadable, that will result in one sequence browser node
        # and usually one sequence (except simultaneous biplane acquisition, which will
        # result in two sequences).
        # Each pedal press on the XA/RF acquisition device creates a new instance number,
        # but if the device has two imaging planes (biplane) then two sequences
        # will be acquired, which have the same instance number. These two sequences
        # are synchronized in time, therefore they have to be assigned to the same
        # browser node.
        instanceNumberToLoadableIndex = {}

        loadables = []

        canBeCineMri = True
        cineMriTriggerTimes = set()
        cineMriInstanceNumberToFilenameIndex = {}

        for filePath in files:
            # Quick check of SOP class UID without parsing the file...
            try:
                sopClassUID = slicer.dicomDatabase.fileValue(
                    filePath, self.tags['sopClassUID'])
                if not (sopClassUID in supportedSOPClassUIDs):
                    # Unsupported class
                    continue

                # Only accept MRI if it looks like cine-MRI
                if sopClassUID != '1.2.840.10008.5.1.4.1.1.4':  # MR Image Storage (will be only accepted if cine-MRI)
                    canBeCineMri = False
                if not canBeCineMri and sopClassUID == '1.2.840.10008.5.1.4.1.1.4':  # MR Image Storage
                    continue

            except Exception as e:
                # Quick check could not be completed (probably Slicer DICOM database is not initialized).
                # No problem, we'll try to parse the file and check the SOP class UID then.
                pass

            instanceNumber = slicer.dicomDatabase.fileValue(
                filePath, self.tags['instanceNumber'])
            if sopClassUID == '1.2.840.10008.5.1.4.1.1.4':  # MR Image Storage
                if not instanceNumber:
                    # no instance number, probably not cine-MRI
                    canBeCineMri = False
                    if self.detailedLogging:
                        logging.debug(
                            "No instance number attribute found, the series will not be considered as a cine MRI"
                        )
                    continue
                cineMriInstanceNumberToFilenameIndex[int(
                    instanceNumber)] = filePath
                cineMriTriggerTimes.add(
                    slicer.dicomDatabase.fileValue(filePath,
                                                   self.tags['triggerTime']))

            else:
                modality = slicer.dicomDatabase.fileValue(
                    filePath, self.tags['modality'])
                if sopClassUID == '1.2.840.10008.5.1.4.1.1.7':  # Secondary Capture Image Storage
                    if modality not in suppportedSecondaryCaptureModalities:
                        # practice of dumping secondary capture images into the same series
                        # is only prevalent in US and XA/RF modalities
                        continue

                if not (instanceNumber
                        in instanceNumberToLoadableIndex.keys()):
                    # new instance number
                    seriesNumber = slicer.dicomDatabase.fileValue(
                        filePath, self.tags['seriesNumber'])
                    seriesDescription = slicer.dicomDatabase.fileValue(
                        filePath, self.tags['seriesDescription'])
                    photometricInterpretation = slicer.dicomDatabase.fileValue(
                        filePath, self.tags['photometricInterpretation'])
                    name = ''
                    if seriesNumber:
                        name = f'{seriesNumber}:'
                    if modality:
                        name = f'{name} {modality}'
                    if seriesDescription:
                        name = f'{name} {seriesDescription}'
                    if instanceNumber:
                        name = f'{name} [{instanceNumber}]'

                    loadable = DICOMLoadable()
                    loadable.singleSequence = False  # put each instance in a separate sequence
                    loadable.files = [filePath]
                    loadable.name = name.strip(
                    )  # remove leading and trailing spaces, if any
                    loadable.warning = "Image spacing may need to be calibrated for accurate size measurements."
                    loadable.tooltip = f"{modality} image sequence"
                    loadable.selected = True
                    # Confidence is slightly larger than default scalar volume plugin's (0.5)
                    # but still leaving room for more specialized plugins.
                    loadable.confidence = 0.7
                    loadable.grayscale = ('MONOCHROME'
                                          in photometricInterpretation)

                    # Add to loadables list
                    loadables.append(loadable)
                    instanceNumberToLoadableIndex[instanceNumber] = len(
                        loadables) - 1
                else:
                    # existing instance number, add this file
                    loadableIndex = instanceNumberToLoadableIndex[
                        instanceNumber]
                    loadables[loadableIndex].files.append(filePath)
                    loadable.tooltip = f"{modality} image sequence ({len(loadables[loadableIndex].files)} planes)"

        if canBeCineMri and len(cineMriInstanceNumberToFilenameIndex) > 1:
            # Get description from first
            ds = dicom.read_file(cineMriInstanceNumberToFilenameIndex[next(
                iter(cineMriInstanceNumberToFilenameIndex))],
                                 stop_before_pixels=True)
            name = ''
            if hasattr(ds, 'SeriesNumber') and ds.SeriesNumber:
                name = f'{ds.SeriesNumber}:'
            if hasattr(ds, 'Modality') and ds.Modality:
                name = f'{name} {ds.Modality}'
            if hasattr(ds, 'SeriesDescription') and ds.SeriesDescription:
                name = f'{name} {ds.SeriesDescription}'

            loadable = DICOMLoadable()
            loadable.singleSequence = True  # put all instances in a single sequence
            loadable.instanceNumbers = sorted(
                cineMriInstanceNumberToFilenameIndex)
            loadable.files = [
                cineMriInstanceNumberToFilenameIndex[instanceNumber]
                for instanceNumber in loadable.instanceNumbers
            ]
            loadable.name = name.strip(
            )  # remove leading and trailing spaces, if any
            loadable.tooltip = f"{ds.Modality} image sequence"
            loadable.selected = True
            if len(cineMriTriggerTimes) > 3:
                if self.detailedLogging:
                    logging.debug("Several different trigger times found (" +
                                  repr(cineMriTriggerTimes) +
                                  ") - assuming this series is a cine MRI")
                # This is likely a cardiac cine acquisition.
                # Multivolume importer sets confidence=1.0, so we need to set a bit higher confidence to be selected by default
                loadable.confidence = 1.05
            else:
                # This may be a 3D acquisition,so set lower confidence than scalar volume's default (0.5)
                if self.detailedLogging:
                    logging.debug(
                        "Only one or few different trigger times found (" +
                        repr(cineMriTriggerTimes) +
                        ") - assuming this series is not a cine MRI")
                loadable.confidence = 0.4
            loadable.grayscale = ('MONOCHROME' in ds.PhotometricInterpretation)

            # Add to loadables list
            loadables.append(loadable)

        return loadables
Пример #28
0
  def examineFiles(self,files):
    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """

    seriesUID = slicer.dicomDatabase.fileValue(files[0],self.tags['seriesUID'])
    seriesName = self.defaultSeriesNodeName(seriesUID)

    # default loadable includes all files for series
    loadable = DICOMLoadable()
    loadable.files = files
    loadable.name = seriesName
    loadable.tooltip = "%d files, first file: %s" % (len(loadable.files), loadable.files[0])
    loadable.selected = True
    # add it to the list of loadables later, if pixel data is available in at least one file

    # while looping through files, keep track of their
    # position and orientation for later use
    positions = {}
    orientations = {}

    # make subseries volumes based on tag differences
    subseriesTags = [
        "seriesInstanceUID",
        "contentTime",
        "triggerTime",
        "diffusionGradientOrientation",
        "imageOrientationPatient",
    ]

    #
    # first, look for subseries within this series
    # - build a list of files for each unique value
    #   of each tag
    #
    subseriesFiles = {}
    subseriesValues = {}
    for file in loadable.files:

      # save position and orientation
      positions[file] = slicer.dicomDatabase.fileValue(file,self.tags['position'])
      if positions[file] == "":
        positions[file] = None
      orientations[file] = slicer.dicomDatabase.fileValue(file,self.tags['orientation'])
      if orientations[file] == "":
        orientations[file] = None

      # check for subseries values
      for tag in subseriesTags:
        value = slicer.dicomDatabase.fileValue(file,self.tags[tag])
        value = value.replace(",","_") # remove commas so it can be used as an index
        if not subseriesValues.has_key(tag):
          subseriesValues[tag] = []
        if not subseriesValues[tag].__contains__(value):
          subseriesValues[tag].append(value)
        if not subseriesFiles.has_key((tag,value)):
          subseriesFiles[tag,value] = []
        subseriesFiles[tag,value].append(file)

    loadables = []

    # Pixel data is available, so add the default loadable to the output
    loadables.append(loadable)

    #
    # second, for any tags that have more than one value, create a new
    # virtual series
    #
    for tag in subseriesTags:
      if len(subseriesValues[tag]) > 1:
        for value in subseriesValues[tag]:
          # default loadable includes all files for series
          loadable = DICOMLoadable()
          loadable.files = subseriesFiles[tag,value]
          loadable.name = seriesName + " for %s of %s" % (tag,value)
          loadable.tooltip = "%d files, first file: %s" % (len(loadable.files), loadable.files[0])
          loadable.selected = False
          loadables.append(loadable)

    # remove any files from loadables that don't have pixel data (no point sending them to ITK for reading)
    newLoadables = []
    for loadable in loadables:
      newFiles = []
      for file in loadable.files:
        if slicer.dicomDatabase.fileValue(file,self.tags['pixelData'])!='':
          newFiles.append(file)
      if len(newFiles) > 0:
        loadable.files = newFiles
        newLoadables.append(loadable)
      else:
        # here all files in have no pixel data, so they might be
        # secondary capture images which will read, so let's pass
        # them through with a warning and low confidence
        loadable.warning += "There is no pixel data attribute for the DICOM objects, but they might be readable as secondary capture images.  "
        loadable.confidence = 0.2
        newLoadables.append(loadable)
    loadables = newLoadables

    #
    # now for each series and subseries, sort the images
    # by position and check for consistency
    #

    # TODO: more consistency checks:
    # - is there gantry tilt?
    # - are the orientations the same for all slices?
    for loadable in loadables:
      #
      # use the first file to get the ImageOrientationPatient for the
      # series and calculate the scan direction (assumed to be perpendicular
      # to the acquisition plane)
      #
      value = slicer.dicomDatabase.fileValue(loadable.files[0], self.tags['numberOfFrames'])
      if value != "":
        loadable.warning += "Multi-frame image. If slice orientation or spacing is non-uniform then the image may be displayed incorrectly. Use with caution.  "

      validGeometry = True
      ref = {}
      for tag in [self.tags['position'], self.tags['orientation']]:
        value = slicer.dicomDatabase.fileValue(loadable.files[0], tag)
        if not value or value == "":
          loadable.warning += "Reference image in series does not contain geometry information.  Please use caution.  "
          validGeometry = False
          loadable.confidence = 0.2
          break
        ref[tag] = value

      if not validGeometry:
        continue

      # get the geometry of the scan
      # with respect to an arbitrary slice
      sliceAxes = [float(zz) for zz in ref[self.tags['orientation']].split('\\')]
      x = sliceAxes[:3]
      y = sliceAxes[3:]
      scanAxis = self.cross(x,y)
      scanOrigin = [float(zz) for zz in ref[self.tags['position']].split('\\')]

      #
      # for each file in series, calculate the distance along
      # the scan axis, sort files by this
      #
      sortList = []
      missingGeometry = False
      for file in loadable.files:
        if not positions[file]:
          missingGeometry = True
          break
        position = [float(zz) for zz in positions[file].split('\\')]
        vec = self.difference(position, scanOrigin)
        dist = self.dot(vec, scanAxis)
        sortList.append((file, dist))

      if missingGeometry:
        loadable.warning += "One or more images is missing geometry information.  "
      else:
        sortedFiles = sorted(sortList, key=lambda x: x[1])
        distances = {}
        loadable.files = []
        for file,dist in sortedFiles:
          loadable.files.append(file)
          distances[file] = dist

        #
        # confirm equal spacing between slices
        # - use variable 'epsilon' to determine the tolerance
        #
        spaceWarnings = 0
        if len(loadable.files) > 1:
          file0 = loadable.files[0]
          file1 = loadable.files[1]
          dist0 = distances[file0]
          dist1 = distances[file1]
          spacing0 = dist1 - dist0
          n = 1
          for fileN in loadable.files[1:]:
            fileNminus1 = loadable.files[n-1]
            distN = distances[fileN]
            distNminus1 = distances[fileNminus1]
            spacingN = distN - distNminus1
            spaceError = spacingN - spacing0
            if abs(spaceError) > self.epsilon:
              spaceWarnings += 1
              loadable.warning += "Images are not equally spaced (a difference of %g vs %g in spacings was detected).  Slicer will try to load this series with a transform to compensate.  Please use caution.  " % (spaceError, spacing0)
              break
            n += 1

        if spaceWarnings != 0:
          logging.warning("Geometric issues were found with %d of the series.  Please use caution." % spaceWarnings)

    return loadables
Пример #29
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """

        seriesUID = slicer.dicomDatabase.fileValue(files[0],
                                                   self.tags['seriesUID'])
        seriesName = self.defaultSeriesNodeName(seriesUID)

        # default loadable includes all files for series
        allFilesLoadable = DICOMLoadable()
        allFilesLoadable.files = files
        allFilesLoadable.name = self.cleanNodeName(seriesName)
        allFilesLoadable.tooltip = "%d files, first file: %s" % (len(
            allFilesLoadable.files), allFilesLoadable.files[0])
        allFilesLoadable.selected = True
        # add it to the list of loadables later, if pixel data is available in at least one file

        # make subseries volumes based on tag differences
        subseriesTags = [
            "seriesInstanceUID",
            "acquisitionNumber",
            # GE volume viewer and Siemens Axiom CBCT systems put an overview (localizer) slice and all the reconstructed slices
            # in one series, using two different image types. Splitting based on image type allows loading of these volumes
            # (loading the series without localizer).
            "imageType",
            "imageOrientationPatient",
            "diffusionGradientOrientation",
        ]

        if self.allowLoadingByTime():
            subseriesTags.append("contentTime")
            subseriesTags.append("triggerTime")

        # Values for these tags will only be enumerated (value itself will not be part of the loadable name)
        # because the vale itself is usually too long and complicated to be displayed to users
        subseriesTagsToEnumerateValues = [
            "seriesInstanceUID",
            "imageOrientationPatient",
            "diffusionGradientOrientation",
        ]

        #
        # first, look for subseries within this series
        # - build a list of files for each unique value
        #   of each tag
        #
        subseriesFiles = {}
        subseriesValues = {}
        for file in allFilesLoadable.files:
            # check for subseries values
            for tag in subseriesTags:
                value = slicer.dicomDatabase.fileValue(file, self.tags[tag])
                value = value.replace(
                    ",", "_")  # remove commas so it can be used as an index
                if tag not in subseriesValues:
                    subseriesValues[tag] = []
                if not subseriesValues[tag].__contains__(value):
                    subseriesValues[tag].append(value)
                if (tag, value) not in subseriesFiles:
                    subseriesFiles[tag, value] = []
                subseriesFiles[tag, value].append(file)

        loadables = []

        # Pixel data is available, so add the default loadable to the output
        loadables.append(allFilesLoadable)

        #
        # second, for any tags that have more than one value, create a new
        # virtual series
        #
        subseriesCount = 0
        # List of loadables that look like subseries that contain the full series except a single frame
        probableLocalizerFreeLoadables = []
        for tag in subseriesTags:
            if len(subseriesValues[tag]) > 1:
                subseriesCount += 1
                for valueIndex, value in enumerate(subseriesValues[tag]):
                    # default loadable includes all files for series
                    loadable = DICOMLoadable()
                    loadable.files = subseriesFiles[tag, value]
                    # value can be a long string (and it will be used for generating node name)
                    # therefore use just an index instead
                    if tag in subseriesTagsToEnumerateValues:
                        loadable.name = seriesName + " - %s %d" % (
                            tag, valueIndex + 1)
                    else:
                        loadable.name = seriesName + " - %s %s" % (tag, value)
                    loadable.name = self.cleanNodeName(loadable.name)
                    loadable.tooltip = "%d files, grouped by %s = %s. First file: %s. %s = %s" % (
                        len(loadable.files), tag, value, loadable.files[0],
                        tag, value)
                    loadable.selected = False
                    loadables.append(loadable)
                    if len(subseriesValues[tag]) == 2:
                        otherValue = subseriesValues[tag][1 - valueIndex]
                        if len(subseriesFiles[tag, value]) > 1 and len(
                                subseriesFiles[tag, otherValue]) == 1:
                            # this looks like a subseries without a localizer image
                            probableLocalizerFreeLoadables.append(loadable)

        # remove any files from loadables that don't have pixel data (no point sending them to ITK for reading)
        # also remove DICOM SEG, since it is not handled by ITK readers
        newLoadables = []
        for loadable in loadables:
            newFiles = []
            excludedLoadable = False
            for file in loadable.files:
                if slicer.dicomDatabase.fileValueExists(
                        file, self.tags['pixelData']):
                    newFiles.append(file)
                if slicer.dicomDatabase.fileValue(
                        file, self.tags['sopClassUID']
                ) == '1.2.840.10008.5.1.4.1.1.66.4':
                    excludedLoadable = True
                    if 'DICOMSegmentationPlugin' not in slicer.modules.dicomPlugins:
                        logging.warning(
                            'Please install Quantitative Reporting extension to enable loading of DICOM Segmentation objects'
                        )
                elif slicer.dicomDatabase.fileValue(
                        file, self.tags['sopClassUID']
                ) == '1.2.840.10008.5.1.4.1.1.481.3':
                    excludedLoadable = True
                    if 'DicomRtImportExportPlugin' not in slicer.modules.dicomPlugins:
                        logging.warning(
                            'Please install SlicerRT extension to enable loading of DICOM RT Structure Set objects'
                        )
            if len(newFiles) > 0 and not excludedLoadable:
                loadable.files = newFiles
                loadable.grayscale = (
                    'MONOCHROME' in slicer.dicomDatabase.fileValue(
                        newFiles[0], self.tags['photometricInterpretation']))
                newLoadables.append(loadable)
            elif excludedLoadable:
                continue
            else:
                # here all files in have no pixel data, so they might be
                # secondary capture images which will read, so let's pass
                # them through with a warning and low confidence
                loadable.warning += "There is no pixel data attribute for the DICOM objects, but they might be readable as secondary capture images.  "
                loadable.confidence = 0.2
                loadable.grayscale = (
                    'MONOCHROME' in slicer.dicomDatabase.fileValue(
                        loadable.files[0],
                        self.tags['photometricInterpretation']))
                newLoadables.append(loadable)
        loadables = newLoadables

        #
        # now for each series and subseries, sort the images
        # by position and check for consistency
        # then adjust confidence values based on warnings
        #
        for loadable in loadables:
            loadable.files, distances, loadable.warning = DICOMUtils.getSortedImageFiles(
                loadable.files, self.epsilon)

        loadablesBetterThanAllFiles = []
        if allFilesLoadable.warning != "":
            for probableLocalizerFreeLoadable in probableLocalizerFreeLoadables:
                if probableLocalizerFreeLoadable.warning == "":
                    # localizer-free loadables are better then all files, if they don't have warning
                    loadablesBetterThanAllFiles.append(
                        probableLocalizerFreeLoadable)
            if not loadablesBetterThanAllFiles and subseriesCount == 1:
                # there was a sorting warning and
                # only one kind of subseries, so it's probably correct
                # to have lower confidence in the default all-files version.
                for loadable in loadables:
                    if loadable != allFilesLoadable and loadable.warning == "":
                        loadablesBetterThanAllFiles.append(loadable)

        # if there are loadables that are clearly better then all files, then use those (otherwise use all files loadable)
        preferredLoadables = loadablesBetterThanAllFiles if loadablesBetterThanAllFiles else [
            allFilesLoadable
        ]
        # reduce confidence and deselect all non-preferred loadables
        for loadable in loadables:
            if loadable in preferredLoadables:
                loadable.selected = True
            else:
                loadable.selected = False
                if loadable.confidence > .45:
                    loadable.confidence = .45

        return loadables
Пример #30
0
    def examinePhilips4DUS(self, filePath):
        # currently only this one (bogus, non-standard) Philips 4D US format is supported
        supportedSOPClassUID = '1.2.840.113543.6.6.1.3.10002'

        # Quick check of SOP class UID without parsing the file...
        try:
            sopClassUID = slicer.dicomDatabase.fileValue(
                filePath, self.tags['sopClassUID'])
            if sopClassUID != supportedSOPClassUID:
                # Unsupported class
                return []
        except Exception as e:
            # Quick check could not be completed (probably Slicer DICOM database is not initialized).
            # No problem, we'll try to parse the file and check the SOP class UID then.
            pass

        try:
            ds = dicom.read_file(filePath, stop_before_pixels=True)
        except Exception as e:
            logging.debug("Failed to parse DICOM file: {0}".format(e.msg))
            return []

        if ds.SOPClassUID != supportedSOPClassUID:
            # Unsupported class
            return []

        confidence = 0.9

        if ds.PhotometricInterpretation != 'MONOCHROME2':
            logging.warning('Warning: unsupported PhotometricInterpretation')
            confidence = .4

        if ds.BitsAllocated != 8 or ds.BitsStored != 8 or ds.HighBit != 7:
            logging.warning('Warning: Bad scalar type (not unsigned byte)')
            confidence = .4

        if ds.PhysicalUnitsXDirection != 3 or ds.PhysicalUnitsYDirection != 3:
            logging.warning('Warning: Units not in centimeters')
            confidence = .4

        if ds.SamplesPerPixel != 1:
            logging.warning('Warning: multiple samples per pixel')
            confidence = .4

        name = ''
        if hasattr(ds, 'SeriesNumber') and ds.SeriesNumber:
            name = '{0}:'.format(ds.SeriesNumber)
        if hasattr(ds, 'Modality') and ds.Modality:
            name = '{0} {1}'.format(name, ds.Modality)
        if hasattr(ds, 'SeriesDescription') and ds.SeriesDescription:
            name = '{0} {1}'.format(name, ds.SeriesDescription)
        if hasattr(ds, 'InstanceNumber') and ds.InstanceNumber:
            name = '{0} [{1}]'.format(name, ds.InstanceNumber)

        loadable = DICOMLoadable()
        loadable.files = [filePath]
        loadable.name = name.strip(
        )  # remove leading and trailing spaces, if any
        loadable.tooltip = "Philips 4D Ultrasound"
        loadable.selected = True
        loadable.confidence = confidence

        return [loadable]
Пример #31
0
  def createLoadableAndAddReferences(self, datasets):
    loadable = DICOMLoadable()
    loadable.selected = True
    loadable.confidence = 0.95

    loadable.referencedSegInstanceUIDs = []
    # store lists of UIDs separately to avoid re-parsing later
    loadable.ReferencedSegmentationInstanceUIDs = {}
    loadable.ReferencedRWVMSeriesInstanceUIDs = []
    loadable.ReferencedOtherInstanceUIDs = []
    loadable.referencedInstanceUIDs = []

    segPlugin = slicer.modules.dicomPlugins["DICOMSegmentationPlugin"]()

    for dataset in datasets:
      uid = self.getDICOMValue(dataset, "SOPInstanceUID")
      loadable.ReferencedSegmentationInstanceUIDs[uid] = []
      if hasattr(dataset, "CurrentRequestedProcedureEvidenceSequence"):
        for refSeriesSequence in dataset.CurrentRequestedProcedureEvidenceSequence:
          for referencedSeriesSequence in refSeriesSequence.ReferencedSeriesSequence:
            for refSOPSequence in referencedSeriesSequence.ReferencedSOPSequence:
              if refSOPSequence.ReferencedSOPClassUID == self.UID_SegmentationStorage:
                logging.debug("Found referenced segmentation")
                loadable.ReferencedSegmentationInstanceUIDs[uid].append(referencedSeriesSequence.SeriesInstanceUID)

              elif refSOPSequence.ReferencedSOPClassUID == self.UID_RealWorldValueMappingStorage: # handle SUV mapping
                logging.debug("Found referenced RWVM")
                loadable.ReferencedRWVMSeriesInstanceUIDs.append(referencedSeriesSequence.SeriesInstanceUID)
              else:
                # TODO: those are not used at all
                logging.debug( "Found other reference")
                loadable.ReferencedOtherInstanceUIDs.append(refSOPSequence.ReferencedSOPInstanceUID)

      for segSeriesInstanceUID in loadable.ReferencedSegmentationInstanceUIDs[uid]:
        segLoadables = segPlugin.examine([slicer.dicomDatabase.filesForSeries(segSeriesInstanceUID)])
        for segLoadable in segLoadables:
          loadable.referencedInstanceUIDs += segLoadable.referencedInstanceUIDs

    loadable.referencedInstanceUIDs = list(set(loadable.referencedInstanceUIDs))

    if len(loadable.ReferencedSegmentationInstanceUIDs[uid])>1:
      logging.warning("SR references more than one SEG. This has not been tested!")
    for segUID in loadable.ReferencedSegmentationInstanceUIDs:
      loadable.referencedSegInstanceUIDs.append(segUID)

    if len(loadable.ReferencedRWVMSeriesInstanceUIDs)>1:
      logging.warning("SR references more than one RWVM. This has not been tested!")
    # not adding RWVM instances to referencedSeriesInstanceUIDs
    return loadable
Пример #32
0
    def examineGeUSMovie(self, filePath):
        supportedSOPClassUIDs = [
            '1.2.840.10008.5.1.4.1.1.6.1'  # UltrasoundImageStorage
        ]

        # Quick check of SOP class UID without parsing the file...
        try:
            sopClassUID = slicer.dicomDatabase.fileValue(
                filePath, self.tags['sopClassUID'])
            if not sopClassUID in supportedSOPClassUIDs:
                # Unsupported class
                return []
        except Exception as e:
            # Quick check could not be completed (probably Slicer DICOM database is not initialized).
            # No problem, we'll try to parse the file and check the SOP class UID then.
            pass

        try:
            ds = dicom.read_file(
                filePath,
                defer_size=30)  # use defer_size to not load large fields
        except Exception as e:
            logging.debug("Failed to parse DICOM file: {0}".format(e.msg))
            return []

        if not ds.SOPClassUID in supportedSOPClassUIDs:
            # Unsupported class
            return []

        geUsMovieGroupRootTag = dicom.tag.Tag('0x7fe1', '0x0010')
        geUsMovieGroupRootItem = ds.get(geUsMovieGroupRootTag)

        if not geUsMovieGroupRootItem:
            return []
        if geUsMovieGroupRootItem.name != 'Private Creator':
            return []
        if geUsMovieGroupRootItem.value != 'GEMS_Ultrasound_MovieGroup_001':
            return []

        confidence = 0.8

        name = ''
        if hasattr(ds, 'SeriesNumber') and ds.SeriesNumber:
            name = '{0}:'.format(ds.SeriesNumber)
        if hasattr(ds, 'Modality') and ds.Modality:
            name = '{0} {1}'.format(name, ds.Modality)
        if hasattr(ds, 'SeriesDescription') and ds.SeriesDescription:
            name = '{0} {1}'.format(name, ds.SeriesDescription)
        if hasattr(ds, 'InstanceNumber') and ds.InstanceNumber:
            name = '{0} [{1}]'.format(name, ds.InstanceNumber)

        loadable = DICOMLoadable()
        loadable.files = [filePath]
        loadable.name = name.strip(
        )  # remove leading and trailing spaces, if any
        loadable.tooltip = "GE ultrasound image sequence"
        loadable.warning = "Importing of this file format is experimental: images may be distorted, size measurements may be inaccurate."
        loadable.selected = True
        loadable.confidence = confidence

        return [loadable]
Пример #33
0
    def examineGeKretzUS(self, filePath):
        # E Kretz uses 'Ultrasound Image Storage' SOP class UID
        supportedSOPClassUID = '1.2.840.10008.5.1.4.1.1.6.1'

        # Quick check of SOP class UID without parsing the file...
        try:
            sopClassUID = slicer.dicomDatabase.fileValue(
                filePath, self.tags['sopClassUID'])
            if sopClassUID != supportedSOPClassUID:
                # Unsupported class
                return []
        except Exception as e:
            # Quick check could not be completed (probably Slicer DICOM database is not initialized).
            # No problem, we'll try to parse the file and check the SOP class UID then.
            pass

        try:
            ds = dicom.read_file(
                filePath,
                defer_size=50)  # use defer_size to not load large fields
        except Exception as e:
            logging.debug("Failed to parse DICOM file: {0}".format(e.msg))
            return []

        if ds.SOPClassUID != supportedSOPClassUID:
            # Unsupported class
            return []

        # Check if these expected DICOM tags are available:
        # (7fe1,0011) LO [KRETZ_US]                               #   8, 1 PrivateCreator
        # (7fe1,1101) OB 4b\52\45\54\5a\46\49\4c\45\20\31\2e\30\20\20\20\00\00\01\00\02\00... # 3471038, 1 Unknown Tag & Data
        kretzUsTag = dicom.tag.Tag('0x7fe1', '0x11')
        kretzUsDataTag = dicom.tag.Tag('0x7fe1', '0x1101')

        if kretzUsTag not in ds.keys():
            return []
        if kretzUsDataTag not in ds.keys():
            return []

        confidence = 0.9

        # These manufacturers values have been found in successfully loadable files:
        # - Kretztechnik
        # - GE Healthcare
        # - GE Healthcare Austria GmbH & Co OG
        if (ds.Manufacturer != 'Kretztechnik') and (
                ds.Manufacturer.find('GE Healthcare') < 0):
            logging.warning('Warning: unknown manufacturer: ' +
                            ds.Manufacturer)
            confidence = .4

        name = ''
        if hasattr(ds, 'SeriesNumber') and ds.SeriesNumber:
            name = '{0}:'.format(ds.SeriesNumber)
        if hasattr(ds, 'Modality') and ds.Modality:
            name = '{0} {1}'.format(name, ds.Modality)
        if hasattr(ds, 'SeriesDescription') and ds.SeriesDescription:
            name = '{0} {1}'.format(name, ds.SeriesDescription)
        if hasattr(ds, 'InstanceNumber') and ds.InstanceNumber:
            name = '{0} [{1}]'.format(name, ds.InstanceNumber)
        name = name.strip()  # remove leading and trailing spaces, if any

        loadable = DICOMLoadable()
        loadable.files = [filePath]
        loadable.name = name
        loadable.tooltip = "GE Kretz 3D Ultrasound"
        loadable.warning = "Importing of this file format is experimental: images may be distorted, size measurements may be inaccurate."
        loadable.selected = True
        loadable.confidence = confidence

        loadableHighRes1 = DICOMLoadable()
        loadableHighRes1.files = loadable.files
        loadableHighRes1.name = loadable.name + " (LR)"
        loadableHighRes1.tooltip = loadable.tooltip + " (low-resolution)"
        loadableHighRes1.warning = loadable.warning
        loadableHighRes1.selected = False
        loadableHighRes1.confidence = confidence

        loadableHighRes2 = DICOMLoadable()
        loadableHighRes2.files = loadable.files
        loadableHighRes2.name = loadable.name + " (HR)"
        loadableHighRes2.tooltip = loadable.tooltip + " (high-resolution)"
        loadableHighRes2.warning = loadable.warning
        loadableHighRes2.selected = False
        loadableHighRes2.confidence = confidence

        return [loadable, loadableHighRes1, loadableHighRes2]
Пример #34
0
    def examinePhilipsAffinity3DUS(self, filePath):
        supportedSOPClassUID = '1.2.840.10008.5.1.4.1.1.3.1'  # UltrasoundMultiframeImageStorage

        # Quick check of SOP class UID without parsing the file...
        try:
            sopClassUID = slicer.dicomDatabase.fileValue(
                filePath, self.tags['sopClassUID'])
            if sopClassUID != supportedSOPClassUID:
                # Unsupported class
                logging.error("sopClassUID " + sopClassUID +
                              " != supportedSOPClassUID " +
                              supportedSOPClassUID)
                return []
        except Exception as e:
            # Quick check could not be completed (probably Slicer DICOM database is not initialized).
            # No problem, we'll try to parse the file and check the SOP class UID then.
            pass

        try:
            ds = dicom.read_file(filePath, stop_before_pixels=True)
        except Exception as e:
            logging.debug("Failed to parse DICOM file: {0}".format(e.msg))
            return []

        if ds.SOPClassUID != supportedSOPClassUID:
            # Unsupported class
            logging.error("sopClassUID " + ds.SOPClassUID +
                          " != supportedSOPClassUID " + supportedSOPClassUID)
            return []

        voxelSpacingTag = findPrivateTag(ds, 0x200d, 0x03,
                                         "Philips US Imaging DD 036")
        if not voxelSpacingTag:
            # this is most likely not a PhilipsAffinity image
            return []
        if voxelSpacingTag not in ds.keys():
            return []

        confidence = 0.9

        if ds.PhotometricInterpretation != 'MONOCHROME2':
            logging.warning('Warning: unsupported PhotometricInterpretation')
            confidence = .4

        if ds.BitsAllocated != 8 or ds.BitsStored != 8 or ds.HighBit != 7:
            logging.warning('Warning: Bad scalar type (not unsigned byte)')
            confidence = .4

        if ds.SamplesPerPixel != 1:
            logging.warning('Warning: multiple samples per pixel')
            confidence = .4

        name = ''
        if hasattr(ds, 'SeriesNumber') and ds.SeriesNumber:
            name = '{0}:'.format(ds.SeriesNumber)
        if hasattr(ds, 'Modality') and ds.Modality:
            name = '{0} {1}'.format(name, ds.Modality)
        if hasattr(ds, 'SeriesDescription') and ds.SeriesDescription:
            name = '{0} {1}'.format(name, ds.SeriesDescription)
        if hasattr(ds, 'InstanceNumber') and ds.InstanceNumber:
            name = '{0} [{1}]'.format(name, ds.InstanceNumber)

        loadable = DICOMLoadable()
        loadable.files = [filePath]
        loadable.name = name.strip(
        )  # remove leading and trailing spaces, if any
        loadable.tooltip = "Philips Affinity 3D ultrasound"
        loadable.selected = True
        loadable.confidence = confidence

        return [loadable]
Пример #35
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """
        loadables = []

        import tempfile
        from subprocess import Popen, PIPE, CalledProcessError
        with tempfile.TemporaryDirectory(
                dir=slicer.app.temporaryPath) as tempDir:

            # write DICOM file list to text file
            inputDicomFileListFilename = os.path.join(tempDir,
                                                      "input-dicom-files.txt")
            with open(inputDicomFileListFilename, 'w') as fp:
                for filename in files:
                    fp.write("{0}\n".format(filename))

            # examine using dcm2niix
            cmdPath = os.path.join(
                os.path.dirname(slicer.modules.dcm2niixgui.path), "Resources",
                "bin", "dcm2niix")
            command_line = [
                cmdPath,
                "-n",
                "-1",  # examine only, do not write output files
                "-s",
                "y",  # input is a single filelist
                "-f",
                "%p_%t_%s",  # output file name (difference compared to the default is that folder name is removed)
                "-i",
                "y",  # ignore derived, localizer and 2D images
                "-o",
                str(tempDir),  # set output folder
                str(inputDicomFileListFilename)
            ]
            logging.debug(repr(command_line))

            try:
                # launch dcm2niix
                proc = slicer.util.launchConsoleProcess(command_line)

                # process output
                loadableInitialized = False
                for line in proc.stdout:
                    line = line.rstrip()
                    if not loadableInitialized:
                        # Default scalar volume reader has confidence of 0.5, so this value will not
                        # make dcm2niix used by default but the user has to select it
                        confidence = 0.45
                        warningMessages = []
                        infoMessages = []
                        loadableInitialized = True
                    logging.debug(line)
                    #slicer.app.processEvents()  # give a chance the application to refresh GUI
                    if (line.startswith("Compression will be faster with pigz")
                            or line.startswith("Chris Rorden's dcm2niiX")
                            or line.startswith("Conversion required")):
                        # general information, ignore
                        pass
                    elif (line.startswith(" ") or line.startswith("dx=")
                          or line.startswith("instance=")):
                        # debug message, ignore
                        pass
                    elif (line.startswith("Warning:")
                          or line.startswith("Unsupported transfer syntax")
                          or line.startswith("Unable to determine")):
                        if line not in warningMessages:
                            warningMessages.append(line)
                        # Reduce confidence if there was a warning.
                        # This value is higher than scalar volume's confidence of 0.2 when no pixel data is found.
                        confidence = 0.3
                    elif line.startswith("\t"):
                        # found a loadable file
                        [dummy, seriesNumber, filepath] = line.split('\t')
                        loadable = DICOMLoadable()
                        loadable.files = files
                        seriesUID = slicer.dicomDatabase.fileValue(
                            files[0], self.tags['seriesUID'])
                        seriesName = self.defaultSeriesNodeName(seriesUID)
                        loadable.name = seriesName
                        loadable.warning = ('\n').join(warningMessages)
                        dcm2niixGeneratedName = os.path.basename(filepath)
                        loadable.tooltip = (
                            '\n').join([dcm2niixGeneratedName] + infoMessages)
                        loadable.selected = True
                        loadable.confidence = confidence
                        loadable.seriesNumber = seriesNumber
                        loadables.append(loadable)
                        loadableInitialized = False
                    else:
                        infoMessages.append(line)

                proc.wait()
                retcode = proc.returncode
                if retcode != 0:
                    raise CalledProcessError(retcode,
                                             proc.args,
                                             output=proc.stdout,
                                             stderr=proc.stderr)
            except CalledProcessError as e:
                logging.debug(
                    "Failed to examine files using dcm2niix: {0}".format(
                        e.message))

        return loadables
Пример #36
0
    def examineFiles(self, files):
        """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """

        detailedLogging = self.isDetailedLogging()

        supportedSOPClassUIDs = [
            '1.2.840.10008.5.1.4.1.1.3.1',  # Ultrasound Multiframe Image Storage
        ]

        loadables = []

        for filePath in files:
            # Quick check of SOP class UID without parsing the file...
            try:
                sopClassUID = slicer.dicomDatabase.fileValue(
                    filePath, self.tags['sopClassUID'])
                if not (sopClassUID in supportedSOPClassUIDs):
                    # Unsupported class
                    continue

                manufacturerModelName = slicer.dicomDatabase.fileValue(
                    filePath, self.tags['manufacturerModelName'])
                if manufacturerModelName != "Invenia":
                    if detailedLogging:
                        logging.debug(
                            "ManufacturerModelName is not Invenia, the series will not be considered as an ABUS image"
                        )
                    continue

            except Exception as e:
                # Quick check could not be completed (probably Slicer DICOM database is not initialized).
                # No problem, we'll try to parse the file and check the SOP class UID then.
                pass

            try:
                ds = dicom.read_file(filePath, stop_before_pixels=True)
            except Exception as e:
                logging.debug("Failed to parse DICOM file: {0}".format(str(e)))
                continue

            # check if probeCurvatureRadius is available
            probeCurvatureRadiusFound = False
            for privateCreator in self.privateCreators:
                if self.findPrivateTag(ds, 0x0021, 0x40, privateCreator):
                    probeCurvatureRadiusFound = True
                    break

            if not probeCurvatureRadiusFound:
                if detailedLogging:
                    logging.debug(
                        "Probe curvature radius is not found, the series will not be considered as an ABUS image"
                    )
                continue

            name = ''
            if hasattr(ds, 'SeriesNumber') and ds.SeriesNumber:
                name = '{0}:'.format(ds.SeriesNumber)
            if hasattr(ds, 'Modality') and ds.Modality:
                name = '{0} {1}'.format(name, ds.Modality)
            if hasattr(ds, 'SeriesDescription') and ds.SeriesDescription:
                name = '{0} {1}'.format(name, ds.SeriesDescription)
            if hasattr(ds, 'InstanceNumber') and ds.InstanceNumber:
                name = '{0} [{1}]'.format(name, ds.InstanceNumber)

            loadable = DICOMLoadable()
            loadable.files = [filePath]
            loadable.name = name.strip(
            )  # remove leading and trailing spaces, if any
            loadable.tooltip = "GE Invenia ABUS"
            loadable.warning = "Loading of this image type is experimental. Please verify image size and orientation and report any problem is found."
            loadable.selected = True
            loadable.confidence = 0.9  # this has to be higher than 0.7 (ultrasound sequence)

            # Add to loadables list
            loadables.append(loadable)

        return loadables
Пример #37
0
  def examineFiles(self,files):
    """ Returns a list of DICOMLoadable instances
    corresponding to ways of interpreting the
    files parameter.
    """

    supportedSOPClassUIDs = [
      '1.2.840.10008.5.1.4.1.1.12.1',  # X-Ray Angiographic Image Storage
      '1.2.840.10008.5.1.4.1.1.3.1',  # Ultrasound Multiframe Image Storage
      ]

    # Each instance will be a loadable, that will result in one sequence browser node
    # and usually one sequence (except simultaneous biplane acquisition, which will
    # result in two sequences).
    # Each pedal press on the XA acquisition device creates a new instance number,
    # but if the device has two imaging planes (biplane) then two sequences
    # will be acquired, which have the same instance number. These two sequences
    # are synchronized in time, therefore they have to be assigned to the same
    # browser node.
    instanceNumberToLoadableIndex = {}

    loadables = []

    # Confidence is slightly larger than default scalar volume plugin's (0.5)
    # but still leaving room for more specialized plugins.
    confidence = 0.7

    for filePath in files:
      # Quick check of SOP class UID without parsing the file...
      try:
        sopClassUID = slicer.dicomDatabase.fileValue(filePath, self.tags['sopClassUID'])
        if not (sopClassUID in supportedSOPClassUIDs):
          # Unsupported class
          return []
      except Exception as e:
        # Quick check could not be completed (probably Slicer DICOM database is not initialized).
        # No problem, we'll try to parse the file and check the SOP class UID then.
        pass

      try:
        ds = dicom.read_file(filePath, stop_before_pixels=True)
      except Exception as e:
        logging.debug("Failed to parse DICOM file: {0}".format(str(e)))
        return []

      if not (ds.SOPClassUID in supportedSOPClassUIDs):
        # Unsupported class
        return []

      if not (ds.InstanceNumber in instanceNumberToLoadableIndex.keys()):
        # new instance number
        name = ''
        if hasattr(ds, 'SeriesNumber') and ds.SeriesNumber:
          name = '{0}:'.format(ds.SeriesNumber)
        if hasattr(ds, 'Modality') and ds.Modality:
          name = '{0} {1}'.format(name, ds.Modality)
        if hasattr(ds, 'SeriesDescription') and ds.SeriesDescription:
          name = '{0} {1}'.format(name, ds.SeriesDescription)
        if hasattr(ds, 'InstanceNumber') and ds.InstanceNumber:
          name = '{0} [{1}]'.format(name, ds.InstanceNumber)

        loadable = DICOMLoadable()
        loadable.files = [filePath]
        loadable.name = name.strip()  # remove leading and trailing spaces, if any
        loadable.warning = "Image spacing may need to be calibrated for accurate size measurements."
        loadable.tooltip = "{0} image sequence".format(ds.Modality)
        loadable.selected = True
        loadable.confidence = confidence
        loadable.grayscale = ('MONOCHROME' in ds.PhotometricInterpretation)

        # Add to loadables list
        loadables.append(loadable)
        instanceNumberToLoadableIndex[ds.InstanceNumber] = len(loadables)-1
      else:
        # existing instance number, add this file
        loadableIndex = instanceNumberToLoadableIndex[ds.InstanceNumber]
        loadables[loadableIndex].files.append(filePath)
        loadable.tooltip = "{0} image sequence ({1} planes)".format(ds.Modality, len(loadables[loadableIndex].files))

    return loadables
Пример #38
0
    def examineGeImage3dApi(self, filePath):
        if not hasattr(slicer.modules, 'ultrasoundimage3dreader'):
            return []

        supportedSOPClassUIDs = [
            '1.2.840.10008.5.1.4.1.1.3.1',  # Ultrasound Multi-frame Image IOD
            '1.2.840.10008.5.1.4.1.1.6.1',  # Ultrasound Image IOD
        ]

        # Quick check of SOP class UID without parsing the file...
        try:
            sopClassUID = slicer.dicomDatabase.fileValue(
                filePath, self.tags['sopClassUID'])
            if not sopClassUID in supportedSOPClassUIDs:
                # Unsupported class
                return []
        except Exception as e:
            # Quick check could not be completed (probably Slicer DICOM database is not initialized).
            # No problem, we'll try to parse the file and check the SOP class UID then.
            pass

        try:
            ds = dicom.read_file(
                filePath,
                defer_size=30)  # use defer_size to not load large fields
        except Exception as e:
            logging.debug("Failed to parse DICOM file: {0}".format(str(e)))
            return []

        if not ds.SOPClassUID in supportedSOPClassUIDs:
            # Unsupported class
            return []

        try:
            # Check if '3D' data type is present (for example "Trace+3D")
            #
            # (7fe1,0010) LO [GEMS_Ultrasound_MovieGroup_001]         #  30, 1 PrivateCreator
            # (7fe1,1001) SQ (Sequence with explicit length #=1)      # 76006288, 1 Unknown Tag & Data
            #   (fffe,e000) na (Item with explicit length #=6)          # 76006280, 1 Item
            #     (7fe1,0010) LO [GEMS_Ultrasound_MovieGroup_001]         #  30, 1 PrivateCreator
            #     (7fe1,1002) LO [Trace+3D]                               #   8, 1 Unknown Tag & Data

            # Get private tags
            movieGroupTag = findPrivateTag(ds, 0x7fe1, 0x01,
                                           'GEMS_Ultrasound_MovieGroup_001')
            imageTypeTag = findPrivateTag(ds, 0x7fe1, 0x02,
                                          'GEMS_Ultrasound_MovieGroup_001')
            contains3D = False
            movieGroup = ds[movieGroupTag].value
            for movieGroupItem in movieGroup:
                if imageTypeTag in movieGroupItem:
                    if '3D' in movieGroupItem[imageTypeTag].value:
                        contains3D = True
                        break
            if not contains3D:
                # Probably 2D file
                return []
        except:
            # Not a GE MovieGroup file
            return []

        # It looks like a GE 3D ultrasound file
        import UltrasoundImage3dReader
        reader = UltrasoundImage3dReader.UltrasoundImage3dReaderFileReader(
            None)
        try:
            reader.getLoader(filePath)
        except Exception as e:
            logging.debug("3D ultrasound loader not found error: {0}".format(
                str(e)))
            logging.info(
                "File {0} looks like a GE 3D ultrasound file. Installing Image3dAPI reader may make the file loadable (https://github.com/MedicalUltrasound/Image3dAPI)."
            )
            return []

        # GE generic moviegroup reader has confidence=0.8
        # this one is much better than that, so use much higher value
        confidence = 0.90

        name = ''
        if hasattr(ds, 'SeriesNumber') and ds.SeriesNumber:
            name = '{0}:'.format(ds.SeriesNumber)
        if hasattr(ds, 'Modality') and ds.Modality:
            name = '{0} {1}'.format(name, ds.Modality)
        if hasattr(ds, 'SeriesDescription') and ds.SeriesDescription:
            name = '{0} {1}'.format(name, ds.SeriesDescription)
        if hasattr(ds, 'InstanceNumber') and ds.InstanceNumber:
            name = '{0} [{1}]'.format(name, ds.InstanceNumber)

        loadable = DICOMLoadable()
        loadable.files = [filePath]
        loadable.name = name.strip(
        )  # remove leading and trailing spaces, if any
        loadable.tooltip = "GE 3D ultrasound image sequence (using Image3dAPI)"
        loadable.selected = True
        loadable.confidence = confidence

        return [loadable]