def determineScannerID(self, cs): """ Tries to identify machine model, used filter, and energy presentation """ dicomfields = [ ["0008,1090", "ModelName"], ["0018,7050", "FilterMaterialLT"], ["0019,10C1", "MICRODOSE IMAGE CONTENT"] #---: SUM FOR PRESENTATION ] # 1. Try to id Scanner cs.scannername = lit.stUnknown dicomvalue = wadwrapper_lib.readDICOMtag( dicomfields[0][0], cs.dcmInfile) # Manufacturer's Model Name dicomvalue = str(dicomvalue).lower() if dicomvalue.find("l50") > -1: cs.scannername = lit.stL50 elif dicomvalue.find("lorad selenia") > -1: cs.guessScanner = lit.stSelenia elif dicomvalue.find("dimensions") > -1: cs.guessScanner = lit.stDimensions # 2. Try to id Filter cs.filtername = wadwrapper_lib.readDICOMtag(dicomfields[1][0], cs.dcmInfile) # Filtername # 3. Try to id EnergyPresentation cs.energypresentation = wadwrapper_lib.readDICOMtag( dicomfields[2][0], cs.dcmInfile)
def determineScannerID(self,cs): """ Tries to identify machine model, used filter, and energy presentation """ dicomfields = [ ["0008,1090", "ModelName"], ["0018,7050", "FilterMaterialLT"], ["0019,10C1", "MICRODOSE IMAGE CONTENT"] #---: SUM FOR PRESENTATION ] # 1. Try to id Scanner cs.scannername = lit.stUnknown dicomvalue = wadwrapper_lib.readDICOMtag(dicomfields[0][0],cs.dcmInfile) # Manufacturer's Model Name dicomvalue = str(dicomvalue).lower() if dicomvalue.find("l50")>-1: cs.scannername = lit.stL50 elif dicomvalue.find("lorad selenia")>-1: cs.guessScanner = lit.stSelenia elif dicomvalue.find("dimensions")>-1: cs.guessScanner = lit.stDimensions # 2. Try to id Filter cs.filtername = wadwrapper_lib.readDICOMtag(dicomfields[1][0],cs.dcmInfile) # Filtername # 3. Try to id EnergyPresentation cs.energypresentation = wadwrapper_lib.readDICOMtag(dicomfields[2][0],cs.dcmInfile)
def DICOMInfo(cs, info='dicom', imslice=0): """ Return some characteristic information from the dicom headers """ if info == "dicom": dicomfields = [ ["0010,0010", "Patients Name"], # PIQT ["0018,1030", "Protocol Name"], # QA1S:MS,SE ["0008,0021", "Series Date"], ["0008,0031", "Series Time"], # no ScanTime 0008,0032 in EnhancedDicom ["0018,1250", "Receive Coil Name"], # Q-Body ["0018,1251", "Transmit Coil Name"], # B ["0018,0095", "Pixel Bandwidth"], # 219 ["0018,0020", "Scanning Sequence"], # SE ["0018,0021", "Scanning Variant"], # SS ["2005,1011", "Image_Type"], # M ["0018,0081", "Echo Time"], # 50 ["0020,0012", "Acquisition Number"], # 5 ["0018,0086", "Echo Number(s)"], # 1 ["2001,1081", "Dyn_Scan_No"], # ?1 ["0020,0013", "Instance Number"], # 1 slice no? ["2001,105f,2005,1079", "Dist_sel"], # -16.32 ["2001,1083", "Central_freq"], # 63.895241 (MHz) ["0018,1020", "SoftwareVersions"], ] results = [] for df in dicomfields: key = df[0] value = wadwrapper_lib.readDICOMtag(key, cs.dcmInfile, imslice) if key == "0018,1020" and len(value) > 1: value = '_'.join(value) results.append((df[1], value)) return results
def DICOMInfo(self,cs,info='dicom'): """ Extract some info from the DICOM header. <info> is either 'dicom' (extensive list) or 'qc' (short list). Returns list of <tag description>,<tag value> """ # Different from ImageJ version; tags "0008","0104" and "0054","0220" # appear to be part of sequences. This gives problems (cannot be found # or returning whole sequence blocks) # Possibly this can be solved by using if(type(value) == type(dicom.sequence.Sequence())) # but I don't see the relevance of these tags anymore, so set them to NO self.determineScannerID(cs) if(info == "dicom"): dicomfields = [ ["0008,0021", "Series Date"], ["0008,0031", "Series Time"], ["0008,0070", "Manufacturer"], ["0008,0080", "InstitutionName"], ["0008,1010", "StationName"], ["0008,1030", "StudyDescription"], ["0008,103E", "SeriesDescription"], ["0008,1070", "Operator"], ["0010,0020", "PatientID"], ["0018,0060", "kVp"], ["0018,1000", "DeviceSerialNumber"], ["0018,1020", "SoftwareVersions"], ["0018,1110", "DistanceSourceToDetector"], ["0018,1111", "DistanceSourceToPatient"], ["0018,1150", "ExposureTime_(ms)"], ["0018,1151", "TubeCurrent_(mA)"], ["0018,1153", "muAs"], ["0018,1164", "ImagerPixelSpacing"], ["0018,1166", "Grid"], ["0018,1190", "FocalSpot"], ["0018,1191", "AnodeTargetMaterial"], ["0018,11A0", "BodyPartThickness"], ["0018,11A2", "CompressionForce"], ["0018,1405", "RelativeXRayExposure"], ["0018,700A", "DetectorID"], ["0018,700C", "DateOfLastDetectorCalibration"], ["0018,7050", "FilterMaterialLT"], ["0019,1029", "---"], ["0028,0101", "BitsStored"], ["0028,1040", "PixelensityRelationship"], ["0028,1052", "Rescaleercept"], ["0028,1053", "RescaleSlope"], ["0028,1054", "RescaleType"], ["0040,0302", "EntranceDose"], ["0040,0314", "HalfValueLayer_(mm)"], ["0040,0316", "OrganDose"], ["0040,8302", "EntranceDose_(mGy)"], ["0000,0000", "NOViewCodeSequence"], ["0000,0000", "NOViewCodeMeaning"]] if cs.scannername == lit.stL50: dicomfields[0] = ["0008,0022", "Acquisition Date"] dicomfields[1] = ["0008,0030", "Acquisition Time"] elif(info == "qc"): dicomfields = [["0008,0021", "Series Date"], ["0008,1010", "StationName"], ["0008,1070", "Operator"], ["0018,0060", "kVp"], ["0018,1020", "SoftwareVersions"], ["0018,1030", "ProtocolName"], ["0018,1110", "DistanceSourceToDetector"], ["0018,1111", "DistanceSourceToPatient"], ["0018,1153", "muAs"], ["0018,1166", "Grid"], ["0018,1190", "FocalSpot"], ["0018,1191", "AnodeTargetMaterial"], ["0018,11A0", "BodyPartThickness"], ["0018,11A2", "CompressionForce"], ["0018,700A", "DetectorID"], ["0018,700C", "DateOfLastDetectorCalibration"], ["0018,7050", "FilterMaterialLT"], ["0040,0314", "HalfValueLayer_(mm)"], ["0040,0316", "OrganDose"], ["0040,8302", "EntranceDose_(mGy)"]] if cs.scannername == lit.stL50: dicomfields[0] = ["0008,0022", "Acquisition Date"] dicomfields.append( ["0019,10c1", "EnergyComponent"] ) results = [] for df in dicomfields: key = df[0] value = "" try: value = wadwrapper_lib.readDICOMtag(key,cs.dcmInfile) except: value = "" results.append( (df[1],value) ) return results
def DICOMInfo(self, cs, info='dicom'): """ Extract some info from the DICOM header. <info> is either 'dicom' (extensive list) or 'qc' (short list). Returns list of <tag description>,<tag value> """ # Different from ImageJ version; tags "0008","0104" and "0054","0220" # appear to be part of sequences. This gives problems (cannot be found # or returning whole sequence blocks) # Possibly this can be solved by using if(type(value) == type(dicom.sequence.Sequence())) # but I don't see the relevance of these tags anymore, so set them to NO self.determineScannerID(cs) if (info == "dicom"): dicomfields = [["0008,0021", "Series Date"], ["0008,0031", "Series Time"], ["0008,0070", "Manufacturer"], ["0008,0080", "InstitutionName"], ["0008,1010", "StationName"], ["0008,1030", "StudyDescription"], ["0008,103E", "SeriesDescription"], ["0008,1070", "Operator"], ["0010,0020", "PatientID"], ["0018,0060", "kVp"], ["0018,1000", "DeviceSerialNumber"], ["0018,1020", "SoftwareVersions"], ["0018,1110", "DistanceSourceToDetector"], ["0018,1111", "DistanceSourceToPatient"], ["0018,1150", "ExposureTime_(ms)"], ["0018,1151", "TubeCurrent_(mA)"], ["0018,1153", "muAs"], ["0018,1164", "ImagerPixelSpacing"], ["0018,1166", "Grid"], ["0018,1190", "FocalSpot"], ["0018,1191", "AnodeTargetMaterial"], ["0018,11A0", "BodyPartThickness"], ["0018,11A2", "CompressionForce"], ["0018,1405", "RelativeXRayExposure"], ["0018,700A", "DetectorID"], ["0018,700C", "DateOfLastDetectorCalibration"], ["0018,7050", "FilterMaterialLT"], ["0019,1029", "---"], ["0028,0101", "BitsStored"], ["0028,1040", "PixelensityRelationship"], ["0028,1052", "Rescaleercept"], ["0028,1053", "RescaleSlope"], ["0028,1054", "RescaleType"], ["0040,0302", "EntranceDose"], ["0040,0314", "HalfValueLayer_(mm)"], ["0040,0316", "OrganDose"], ["0040,8302", "EntranceDose_(mGy)"], ["0000,0000", "NOViewCodeSequence"], ["0000,0000", "NOViewCodeMeaning"]] if cs.scannername == lit.stL50: dicomfields[0] = ["0008,0022", "Acquisition Date"] dicomfields[1] = ["0008,0030", "Acquisition Time"] elif (info == "qc"): dicomfields = [["0008,0021", "Series Date"], ["0008,1010", "StationName"], ["0008,1070", "Operator"], ["0018,0060", "kVp"], ["0018,1020", "SoftwareVersions"], ["0018,1030", "ProtocolName"], ["0018,1110", "DistanceSourceToDetector"], ["0018,1111", "DistanceSourceToPatient"], ["0018,1153", "muAs"], ["0018,1166", "Grid"], ["0018,1190", "FocalSpot"], ["0018,1191", "AnodeTargetMaterial"], ["0018,11A0", "BodyPartThickness"], ["0018,11A2", "CompressionForce"], ["0018,700A", "DetectorID"], ["0018,700C", "DateOfLastDetectorCalibration"], ["0018,7050", "FilterMaterialLT"], ["0040,0314", "HalfValueLayer_(mm)"], ["0040,0316", "OrganDose"], ["0040,8302", "EntranceDose_(mGy)"]] if cs.scannername == lit.stL50: dicomfields[0] = ["0008,0022", "Acquisition Date"] dicomfields.append(["0019,10c1", "EnergyComponent"]) results = [] for df in dicomfields: key = df[0] value = "" try: value = wadwrapper_lib.readDICOMtag(key, cs.dcmInfile) except: value = "" results.append((df[1], value)) return results
def sfnrTest(data,results,params): """ MRI_fBIRN Checks: fBIRN QA Signal-to-Fluctuation-Noise Ratio (SFNR) ... (more tests to come eventually) Workflow: 1. Read image or sequence 2. Run test 3. Build output """ #(1) Reads input file(s) list as a series (scan) single DICOM object # returns DICOM header, raw pizelData object scaled and type of current DICOM object { 2D, 3D, ... } #dcmInfile,pixeldataIn,dicomMode = wadwrapper_lib.prepareInput(data.series_filelist[0],headers_only=False,logTag=logTag()) #------------------------------------------------------------------ pixeldataIn = None if ( len(data.series_filelist[0]) > 1 ) or ( len(data.series_filelist[0]) == 1 and os.path.isdir(data.series_filelist[0][0]) ): fileList = data.series_filelist[0] if len(data.series_filelist[0]) == 1 : fileList = data.series_filelist[0][0] # read/load a list of DICOM files seriesDataList = pydicom_series.read_files(fileList,showProgress=True, readPixelData=True,skipNonImageFiles=True) # check number of series in the array/list if len(seriesDataList) != 1: raise ValueError("{} Such test is supposed to apply solely to a single series/scan. Something went wrong...".format(logTag)) seriesData = seriesDataList[0] nTemporalPositions = int(seriesData.info["0020","0105"].value) # Image pixeldata seems to be transposed when read using wadwrapper_lib methods... pixeldataIn = seriesData.get_pixel_array() pixeldataIn = np.transpose(pixeldataIn) new3rdDimension = int(pixeldataIn.shape[2])/nTemporalPositions print '[Debug] Image dimensions: ' + str(pixeldataIn.shape) print '[Debug] New dimensions: (%s, %s, %s, %s)' %(str(pixeldataIn.shape[0]),str(pixeldataIn.shape[1]),str(new3rdDimension),str(nTemporalPositions)) pixeldataIn = np.reshape(pixeldataIn, (pixeldataIn.shape[0],pixeldataIn.shape[1],new3rdDimension,nTemporalPositions)) elif ( len(data.series_filelist[0]) == 1) and ( wadwrapper_lib.testIfEnhancedDICOM(data.series_filelist[0][0]) ): # read/load a single DICOM file dcmInfile,pixeldataIn,dicomMode = wadwrapper_lib.prepareEnhancedInput(data.series_filelist[0][0],headers_only=False) # DICOM keeps NumberOfTemporalPositions nested in sequence items/subitems. And DCM4CHEE seems not to keep them at all (!?). # Workaround: Use Philips private tag, should be read as a bit string and converted to string/integer/whatever if 'PHILIPS' not in (wadwrapper_lib.readDICOMtag("0008,0070",dcmInfile)).upper(): raise ValueError("{} Input enhanced dataset type not suitable --> no dynamics/temporal information found".format(logTag)) nSlicesRaw = wadwrapper_lib.readDICOMtag("2001,1018",dcmInfile) try: nSlices = struct.unpack("<L",nSlicesRaw)[0] except: nSlices = nSlicesRaw nTemporalPositions = int(pixeldataIn.shape[0])/int(nSlices) # Image pixeldata seems to be transposed when read using wadwrapper_lib methods... pixeldataIn = np.transpose(pixeldataIn) new3rdDimension = int(pixeldataIn.shape[2])/nTemporalPositions print '[Debug] Image dimensions: ' + str(pixeldataIn.shape) print '[Debug] New dimensions: (%s, %s, %s, %s)' %(str(pixeldataIn.shape[0]),str(pixeldataIn.shape[1]),str(new3rdDimension),str(nTemporalPositions)) pixeldataIn = np.reshape(pixeldataIn, (pixeldataIn.shape[0],pixeldataIn.shape[1],new3rdDimension,nTemporalPositions)) else: raise ValueError("{} Input dataset type cannot be determined or is not compatible".format(logTag)) #(2) #if 'BIRN' not in options['seriesDesc'] or 'BIRN' not in options['protocolName']: # raise ValueError("{} Input dataset type not suitable".format(logTag)) # OR # print '[Warning] Not an fBIRN scan --> do nothing' #Check if pixeldataIn is actually a numpy array if type(pixeldataIn).__module__ != np.__name__ : raise ValueError("{} Unable to pull out the pixel data of the incomming DICOM file(s)".format(logTag)) output = fBIRN_lib.fBIRN_SFNR(pixeldataIn,plot_data=False) #(3) results.addFloat('mean_SNR', np.mean(output['imgsnr']), quantity='SNR', level=2) #quantity is actually magnitude in the WAD-QC app results.addFloat('mean_SFNR', output['meansfnr'], quantity='SFNR', level=2) #quantity is actually magnitude in the WAD-QC app #print '[info] SNR, %f'%np.mean(output['imgsnr']) #print '[info] SFNR, %f'%output['meansfnr'] #print '[info] drift, %f'%output['trend'].params[1] #if len(output['spikes']) > 0: #print '[info] nspikes,%d'%len(output['spikes']) if len(output['spikes']) > 0: results.addBool('spikes', True, level=2) #Spikes detected else : results.addBool('spikes', False, level=2) #No spikes detected
def sfnrTest(data, results, params): """ MRI_fBIRN Checks: fBIRN QA Signal-to-Fluctuation-Noise Ratio (SFNR) ... (more tests to come eventually) Workflow: 1. Read image or sequence 2. Run test 3. Build output """ #(1) Reads input file(s) list as a series (scan) single DICOM object # returns DICOM header, raw pizelData object scaled and type of current DICOM object { 2D, 3D, ... } #dcmInfile,pixeldataIn,dicomMode = wadwrapper_lib.prepareInput(data.series_filelist[0],headers_only=False,logTag=logTag()) #------------------------------------------------------------------ pixeldataIn = None if (len(data.series_filelist[0]) > 1) or (len(data.series_filelist[0]) == 1 and os.path.isdir( data.series_filelist[0][0])): fileList = data.series_filelist[0] if len(data.series_filelist[0]) == 1: fileList = data.series_filelist[0][0] # read/load a list of DICOM files seriesDataList = pydicom_series.read_files(fileList, showProgress=True, readPixelData=True, skipNonImageFiles=True) # check number of series in the array/list if len(seriesDataList) != 1: raise ValueError( "{} Such test is supposed to apply solely to a single series/scan. Something went wrong..." .format(logTag)) seriesData = seriesDataList[0] nTemporalPositions = int(seriesData.info["0020", "0105"].value) # Image pixeldata seems to be transposed when read using wadwrapper_lib methods... pixeldataIn = seriesData.get_pixel_array() pixeldataIn = np.transpose(pixeldataIn) new3rdDimension = int(pixeldataIn.shape[2]) / nTemporalPositions print '[Debug] Image dimensions: ' + str(pixeldataIn.shape) print '[Debug] New dimensions: (%s, %s, %s, %s)' % ( str(pixeldataIn.shape[0]), str(pixeldataIn.shape[1]), str(new3rdDimension), str(nTemporalPositions)) pixeldataIn = np.reshape(pixeldataIn, (pixeldataIn.shape[0], pixeldataIn.shape[1], new3rdDimension, nTemporalPositions)) elif (len(data.series_filelist[0]) == 1) and (wadwrapper_lib.testIfEnhancedDICOM( data.series_filelist[0][0])): # read/load a single DICOM file dcmInfile, pixeldataIn, dicomMode = wadwrapper_lib.prepareEnhancedInput( data.series_filelist[0][0], headers_only=False) # DICOM keeps NumberOfTemporalPositions nested in sequence items/subitems. And DCM4CHEE seems not to keep them at all (!?). # Workaround: Use Philips private tag, should be read as a bit string and converted to string/integer/whatever if 'PHILIPS' not in (wadwrapper_lib.readDICOMtag( "0008,0070", dcmInfile)).upper(): raise ValueError( "{} Input enhanced dataset type not suitable --> no dynamics/temporal information found" .format(logTag)) nSlicesRaw = wadwrapper_lib.readDICOMtag("2001,1018", dcmInfile) try: nSlices = struct.unpack("<L", nSlicesRaw)[0] except: nSlices = nSlicesRaw nTemporalPositions = int(pixeldataIn.shape[0]) / int(nSlices) # Image pixeldata seems to be transposed when read using wadwrapper_lib methods... pixeldataIn = np.transpose(pixeldataIn) new3rdDimension = int(pixeldataIn.shape[2]) / nTemporalPositions print '[Debug] Image dimensions: ' + str(pixeldataIn.shape) print '[Debug] New dimensions: (%s, %s, %s, %s)' % ( str(pixeldataIn.shape[0]), str(pixeldataIn.shape[1]), str(new3rdDimension), str(nTemporalPositions)) pixeldataIn = np.reshape(pixeldataIn, (pixeldataIn.shape[0], pixeldataIn.shape[1], new3rdDimension, nTemporalPositions)) else: raise ValueError( "{} Input dataset type cannot be determined or is not compatible". format(logTag)) #(2) #if 'BIRN' not in options['seriesDesc'] or 'BIRN' not in options['protocolName']: # raise ValueError("{} Input dataset type not suitable".format(logTag)) # OR # print '[Warning] Not an fBIRN scan --> do nothing' #Check if pixeldataIn is actually a numpy array if type(pixeldataIn).__module__ != np.__name__: raise ValueError( "{} Unable to pull out the pixel data of the incomming DICOM file(s)" .format(logTag)) output = fBIRN_lib.fBIRN_SFNR(pixeldataIn, plot_data=False) #(3) results.addFloat( 'mean_SNR', np.mean(output['imgsnr']), quantity='SNR', level=2) #quantity is actually magnitude in the WAD-QC app results.addFloat( 'mean_SFNR', output['meansfnr'], quantity='SFNR', level=2) #quantity is actually magnitude in the WAD-QC app #print '[info] SNR, %f'%np.mean(output['imgsnr']) #print '[info] SFNR, %f'%output['meansfnr'] #print '[info] drift, %f'%output['trend'].params[1] #if len(output['spikes']) > 0: #print '[info] nspikes,%d'%len(output['spikes']) if len(output['spikes']) > 0: results.addBool('spikes', True, level=2) #Spikes detected else: results.addBool('spikes', False, level=2) #No spikes detected
def readDICOMtag(self, cs_mam, key, imslice=0): # slice=2 is image 3 value = wadwrapper_lib.readDICOMtag(key, cs_mam.dcmInfile, imslice) return value
def readDICOMtag(self,cs_mam,key,imslice=0): # slice=2 is image 3 value = wadwrapper_lib.readDICOMtag(key,cs_mam.dcmInfile,imslice) return value