def main(self): """Creates a subwindow that displays a DICOM image's metadata. """ try: logger.info("ViewMetaData.viewMetadata called") QApplication.setOverrideCursor(QCursor(QtCore.Qt.WaitCursor)) if treeView.isAnImageSelected(self): imagePath = self.selectedImagePath imageName = self.selectedImageName dataset = readDICOM_Image.getDicomDataset(imagePath) displayMetaDataSubWindow(self, "Metadata for image {}".format(imageName), dataset) elif treeView.isASeriesSelected(self): studyID = self.selectedStudy seriesID = self.selectedSeries imageList = self.objXMLReader.getImagePathList(studyID, seriesID) firstImagePath = imageList[0] dataset = readDICOM_Image.getDicomDataset(firstImagePath) displayMetaDataSubWindow(self, "Metadata for series {}".format(seriesID), dataset) QApplication.restoreOverrideCursor() except (IndexError, AttributeError): QApplication.restoreOverrideCursor() msgBox = QMessageBox() msgBox.setWindowTitle("View DICOM Metadata") msgBox.setText("Select either a series or an image") msgBox.exec() except Exception as e: print('Error in ViewMetaData.viewMetadata: ' + str(e)) logger.error('Error in ViewMetaData.viewMetadata: ' + str(e))
def saveDicomNewSeries(derivedImagePathList, imagePathList, pixelArrayList, suffix, series_id=None, series_uid=None, series_name=None, parametric_map=None, colourmap=None, list_refs_path=None): """This method saves the pixelArrayList into DICOM files with metadata pointing to the same series""" # What if it's a map with less files than original? Think about iterating the first elements and sort path list by SliceLocation - see T2* algorithm # Think of a way to choose a select a new FilePath or Folder try: if os.path.exists(imagePathList[0]): # Series ID and UID if (series_id is None) and (series_uid is None): ids = generateUIDs(readDICOM_Image.getDicomDataset(imagePathList[0])) series_id = ids[0] series_uid = ids[1] elif (series_id is not None) and (series_uid is None): series_uid = generateUIDs(readDICOM_Image.getDicomDataset(imagePathList[0]), seriesNumber=series_id)[1] elif (series_id is None) and (series_uid is not None): series_id = int(str(readDICOM_Image.getDicomDataset(imagePathList[0]).SeriesNumber) + str(random.randint(0, 9999))) refs = None for index, newFilePath in enumerate(derivedImagePathList): # Extra references, besides the main one, which is imagePathList if list_refs_path is not None: if len(np.shape(list_refs_path)) == 1: refs = list_refs_path[index] else: refs = [] for individualRef in list_refs_path: refs.append(individualRef[index]) saveNewSingleDicomImage(newFilePath, imagePathList[index], pixelArrayList[index], suffix, series_id=series_id, series_uid=series_uid, series_name=series_name, image_number=index, parametric_map=parametric_map, colourmap=colourmap, list_refs_path=refs) del series_id, series_uid, refs return else: return None except Exception as e: print('Error in function saveDICOM_Image.saveDicomNewSeries: ' + str(e))
def insertNewSeriesInXMLFile(self, origImageList, newImageList, suffix, newSeriesName=None): """Creates a new series to hold the series of New images""" try: logger.info("InterfaceDICOMXMLFile insertNewSeriesInXMLFile called") #Get current study & series IDs #studyID = self.selectedStudy #seriesID = self.selectedSeries # Get a new series ID by default (subjectID, studyID, seriesID) = treeView.getPathParentNode(self, origImageList[0]) dataset = readDICOM_Image.getDicomDataset(newImageList[0]) newSeriesID = getNewSeriesName( self, studyID, dataset, suffix, newSeriesName=newSeriesName) # If developer sets seriesName self.objXMLReader.insertNewSeriesInXML(origImageList, newImageList, studyID, newSeriesID, seriesID, suffix) self.statusBar.showMessage('New series created: - ' + newSeriesID) return newSeriesID except Exception as e: print('Error in InterfaceDICOMXMLFile.insertNewSeriesInXMLFile: ' + str(e)) logger.error( 'Error in InterfaceDICOMXMLFile.insertNewSeriesInXMLFile: ' + str(e))
def renameSeriesinXMLFile(self, imageList, series_id=None, series_name=None): """Removes a whole series from the DICOM XML file""" try: logger.info("InterfaceDICOMXMLFile renameSeriesinXMLFile called") (subjectID, studyID, seriesID) = treeView.getPathParentNode(self, imageList[0]) seriesNumber = str( readDICOM_Image.getDicomDataset(imageList[0]).SeriesNumber ) if series_id is None else str(series_id) newName = str( readDICOM_Image.getDicomDataset(imageList[0]).SeriesDescription ) if series_name is None else str(series_name) xmlSeriesName = seriesNumber + "_" + newName self.objXMLReader.renameSeriesinXMLFile(studyID, seriesID, xmlSeriesName) except Exception as e: print('Error in InterfaceDICOMXMLFile removeSeriesFromXMLFile: ' + str(e)) logger.error( 'Error in InterfaceDICOMXMLFile removeSeriesFromXMLFile: ' + str(e))
def returnPixelArray(imagePath, func): """Applies the algorithm in the function, func to an image and returns the resulting PixelArray""" try: if os.path.exists(imagePath): dataset = readDICOM_Image.getDicomDataset(imagePath) pixelArray = readDICOM_Image.returnPixelArray(imagePath) derivedImage = func(pixelArray, dataset) return derivedImage else: return None except Exception as e: print('Error in function #.returnPixelArray: ' + str(e))
def saveNewSingleDicomImage(newFilePath, imagePath, pixelArray, suffix, series_id=None, series_uid=None, series_name=None, image_number=None, parametric_map=None, colourmap=None, list_refs_path=None): """This method saves the new pixelArray into DICOM in the given newFilePath""" try: if os.path.exists(imagePath): dataset = readDICOM_Image.getDicomDataset(imagePath) if list_refs_path is not None: refs = [] for individualRef in list_refs_path: refs.append(readDICOM_Image.getDicomDataset(individualRef)) else: refs = None newDataset = createNewSingleDicom(dataset, pixelArray, series_id=series_id, series_uid=series_uid, series_name=series_name, comment=suffix, parametric_map=parametric_map, colourmap=colourmap, list_refs=refs) if (image_number is not None) and (len(np.shape(pixelArray)) < 3): newDataset.InstanceNumber = image_number newDataset.ImageNumber = image_number saveDicomToFile(newDataset, output_path=newFilePath) del dataset, newDataset, refs, image_number return else: return None except Exception as e: print('Error in function saveDICOM_Image.saveNewSingleDicomImage: ' + str(e))
def saveROI(self, regionName, graphicsView): try: # Save Current ROI logger.info("DisplayImageDrawROI.saveROI called") maskList = graphicsView.dictROIs.dictMasks[ regionName] # Will return a list of boolean masks maskList = [np.array(mask, dtype=np.int) for mask in maskList ] # Convert each 2D boolean to 0s and 1s suffix = str("_ROI_" + regionName) if len(maskList) > 1: inputPath = self.imageList else: inputPath = [self.selectedImagePath] # Saving Progress message messageWindow.displayMessageSubWindow( self, "<H4>Saving ROIs into a new DICOM Series ({} files)</H4>".format( len(inputPath)), "Export ROIs") messageWindow.setMsgWindowProgBarMaxValue(self, len(inputPath)) ids = saveDICOM_Image.generateUIDs( readDICOM_Image.getDicomDataset(inputPath[0])) seriesID = ids[0] seriesUID = ids[1] #outputPath = [] #for image in inputPath: for index, path in enumerate(inputPath): #outputPath.append(saveDICOM_Image.returnFilePath(image, suffix)) messageWindow.setMsgWindowProgBarValue(self, index) outputPath = saveDICOM_Image.returnFilePath(path, suffix) saveDICOM_Image.saveNewSingleDicomImage(outputPath, path, maskList[index], suffix, series_id=seriesID, series_uid=seriesUID, parametric_map="SEG") treeSeriesID = interfaceDICOMXMLFile.insertNewImageInXMLFile( self, path, outputPath, suffix) #saveDICOM_Image.saveDicomNewSeries(outputPath, inputPath, maskList, suffix, parametric_map="SEG") # Consider Enhanced DICOM for parametric_map #seriesID = interfaceDICOMXMLFile.insertNewSeriesInXMLFile(self, inputPath, outputPath, suffix) messageWindow.setMsgWindowProgBarValue(self, len(inputPath)) messageWindow.closeMessageSubWindow(self) treeView.refreshDICOMStudiesTreeView(self, newSeriesName=treeSeriesID) QMessageBox.information(self, "Export ROIs", "Image Saved") except Exception as e: print('Error in DisplayImageDrawROI.saveROI: ' + str(e)) logger.error('Error in DisplayImageDrawROI.saveROI: ' + str(e))
def updateSingleDicomImage(objWeasel, spinBoxIntensity, spinBoxContrast, imagePath='', seriesID='', studyID='', colourmap=None, lut=None): try: logger.info("In saveDICOM_Image.updateSingleDicomImage") messageWindow.displayMessageSubWindow(objWeasel, "<H4>Updating 1 DICOM file</H4>", "Updating DICOM images") messageWindow.setMsgWindowProgBarMaxValue(objWeasel,1) messageWindow.setMsgWindowProgBarValue(objWeasel,0) dataset = readDICOM_Image.getDicomDataset(imagePath) levels = [spinBoxIntensity.value(), spinBoxContrast.value()] updatedDataset = updateSingleDicom(dataset, colourmap=colourmap, levels=levels, lut=lut) saveDicomToFile(updatedDataset, output_path=imagePath) messageWindow.setMsgWindowProgBarValue(objWeasel,1) messageWindow.closeMessageSubWindow(objWeasel) except Exception as e: print('Error in saveDICOM_Image.updateSingleDicomImage: ' + str(e))
def overwriteDicomFileTag(imagePath, dicomTag, newValue): try: if isinstance(imagePath, list): datasetList = readDICOM_Image.getSeriesDicomDataset(imagePath) for index, dataset in enumerate(datasetList): if isinstance(dicomTag, str): try: dataset.data_element(dicomTag).value = newValue except: dataset.add_new(dicomTag, dictionary_VR(dicomTag), newValue) else: try: dataset[hex(dicomTag)].value = newValue except: dataset.add_new(hex(dicomTag), dictionary_VR(hex(dicomTag)), newValue) saveDicomToFile(dataset, output_path=imagePath[index]) else: dataset = readDICOM_Image.getDicomDataset(imagePath) if isinstance(dicomTag, str): try: dataset.data_element(dicomTag).value = newValue except: dataset.add_new(dicomTag, dictionary_VR(dicomTag), newValue) else: try: dataset[hex(dicomTag)].value = newValue except: dataset.add_new(hex(dicomTag), dictionary_VR(hex(dicomTag)), newValue) saveDicomToFile(dataset, output_path=imagePath) return except Exception as e: print('Error in saveDICOM_Image.overwriteDicomFileTag: ' + str(e))
def loadROI(self, cmbROIs, graphicsView): try: logger.info("DisplayImageDrawROI.loadROI called") # The following workflow is assumed: # 1. The user first loads a series of DICOM images # 2. Then the user loads the series of ROIs that are superimposed upon the images # Prompt Windows to select Series # paramDict = {"Series":"dropdownlist"} paramDict = {"Series": "listview"} helpMsg = "Select a Series with ROI" studyID = self.selectedStudy study = self.objXMLReader.getStudy(studyID) listSeries = [series.attrib['id'] for series in study] # if 'ROI' in series.attrib['id']] inputDlg = inputDialog.ParameterInputDialog(paramDict, title="Load ROI", helpText=helpMsg, lists=[listSeries]) listParams = inputDlg.returnListParameterValues() if inputDlg.closeInputDialog() == False: # for series ID in listParams[0]: # more than 1 ROI may be selected seriesID = listParams[0][0] # Temporary, only the first ROI imagePathList = self.objXMLReader.getImagePathList( studyID, seriesID) maskList = [] # Consider DICOM Tag SegmentSequence[:].SegmentLabel as some 3rd software do if hasattr(readDICOM_Image.getDicomDataset(imagePathList[0]), "ContentDescription"): region = readDICOM_Image.getSeriesTagValues( imagePathList, "ContentDescription")[0][0] else: region = "new_region_number" # Affine re-adjustment # It takes longer to load with this, so we could do an if/else involving Affine for dicomFile in self.imageList: dataset_original = readDICOM_Image.getDicomDataset(dicomFile) tempArray = np.zeros( np.shape(readDICOM_Image.getPixelArray(dataset_original))) for maskFile in imagePathList: dataset = readDICOM_Image.getDicomDataset(maskFile) maskArray = readDICOM_Image.getPixelArray(dataset) maskArray[maskArray != 0] = 1 affineResults = readDICOM_Image.mapMaskToImage( maskArray, dataset, dataset_original) for coordinates in affineResults: tempArray[coordinates] = 1 #tempArray = np.add(tempArray, np.transpose(affineResults)) #np.where(tempArray > 1, tempArray, 1) maskList.append(tempArray) # First populate the ROI_Storage data structure in a loop for imageNumber in range(len(maskList)): graphicsView.dictROIs.addRegion( region, np.array(maskList[imageNumber]).astype(bool), imageNumber + 1) # Second populate the dropdown list of region names cmbROIs.blockSignals(True) #remove previous contents of ROI dropdown list cmbROIs.clear() cmbROIs.addItems(graphicsView.dictROIs.getListOfRegions()) cmbROIs.blockSignals(False) # Redisplay the current image to show the mask mask = graphicsView.dictROIs.getMask(region, 1) graphicsView.graphicsItem.reloadMask(mask) except Exception as e: print('Error in DisplayImageDrawROI.loadROI: ' + str(e)) logger.error('Error in DisplayImageDrawROI.loadROI: ' + str(e))
def insertNewImageInXML(self, imageName, newImageFileName, studyID, seriesID, suffix, newSeriesName=None): #First determine if a series with parentID=seriesID exists #and typeID=suffix try: dataset = readDICOM_Image.getDicomDataset(newImageFileName) if newSeriesName: newSeriesID = str(dataset.SeriesNumber) + "_" + newSeriesName else: newSeriesID = str(dataset.SeriesNumber) + "_" + dataset.SeriesDescription series = self.getSeriesOfSpecifiedType( studyID, seriesID, newSeriesID, suffix) #Get image label, date & time imageLabel = self.getImageLabel(studyID, seriesID, imageName) imageTime = self.getImageTime(studyID, seriesID)#, imageName) imageDate = self.getImageDate(studyID, seriesID)#, imageName) if series is None: #Need to create a new series to hold this new image #Get study branch currentStudy = self.getStudy(studyID) newAttributes = {'id':newSeriesID, 'parentID':seriesID, 'typeID':suffix} #Add new series to study to hold new images newSeries = ET.SubElement(currentStudy, 'series', newAttributes) comment = ET.Comment('This series holds new images') newSeries.append(comment) #print("image time {}, date {}".format(imageTime, imageDate)) #Now add image element newImage = ET.SubElement(newSeries,'image') #Add child nodes of the image element labelNewImage = ET.SubElement(newImage, 'label') labelNewImage.text = imageLabel nameNewImage = ET.SubElement(newImage, 'name') nameNewImage.text = newImageFileName timeNewImage = ET.SubElement(newImage, 'time') timeNewImage.text = imageTime dateNewImage = ET.SubElement(newImage, 'date') dateNewImage.text = imageDate self.tree.write(self.fullFilePath) return newSeriesID else: #A series already exists to hold new images from #the current parent series newImage = ET.SubElement(series,'image') #Add child nodes of the image element labelNewImage = ET.SubElement(newImage, 'label') labelNewImage.text = imageLabel nameNewImage = ET.SubElement(newImage, 'name') nameNewImage.text = newImageFileName timeNewImage = ET.SubElement(newImage, 'time') timeNewImage.text = imageTime dateNewImage = ET.SubElement(newImage, 'date') dateNewImage.text = imageDate self.tree.write(self.fullFilePath) return series.attrib['id'] except Exception as e: print('Error in WeaselXMLReader.insertNewImageInXML: ' + str(e)) logger.error('Error in WeaselXMLReader.insertNewImageInXML: ' + str(e))
def readLevelsFromDICOMImage(self, pixelArray): """Reads levels directly from the DICOM image Input Parmeters *************** self - an object reference to the WEASEL interface. pixelArray - pixel array to be displayed Output Parameters ***************** centre - Image intensity width - Image contrast maximumValue - Maximum pixel value in the image minimumValue - Minimum pixel value in the image """ try: logger.info("DisplayImageCommon.readLevelsFromDICOMImage called") #set default values centre = -1 width = -1 maximumValue = -1 minimumValue = -1 dataset = readDICOM_Image.getDicomDataset(self.selectedImagePath) if dataset and hasattr(dataset, 'WindowCenter') and hasattr( dataset, 'WindowWidth'): slope = float(getattr(dataset, 'RescaleSlope', 1)) intercept = float(getattr(dataset, 'RescaleIntercept', 0)) centre = dataset.WindowCenter # * slope + intercept width = dataset.WindowWidth # * slope maximumValue = centre + width / 2 minimumValue = centre - width / 2 elif dataset and hasattr(dataset, 'PerFrameFunctionalGroupsSequence'): # In Enhanced MRIs, this display will retrieve the centre and width values of the first slice slope = dataset.PerFrameFunctionalGroupsSequence[ 0].PixelValueTransformationSequence[0].RescaleSlope intercept = dataset.PerFrameFunctionalGroupsSequence[ 0].PixelValueTransformationSequence[0].RescaleIntercept centre = dataset.PerFrameFunctionalGroupsSequence[ 0].FrameVOILUTSequence[0].WindowCenter # * slope + intercept width = dataset.PerFrameFunctionalGroupsSequence[ 0].FrameVOILUTSequence[0].WindowWidth # * slope maximumValue = centre + width / 2 minimumValue = centre - width / 2 else: minimumValue = np.amin(pixelArray) if ( np.median(pixelArray) - iqr(pixelArray, rng=(1, 99)) / 2) < np.amin(pixelArray) else np.median(pixelArray) - iqr( pixelArray, rng=(1, 99)) / 2 maximumValue = np.amax(pixelArray) if ( np.median(pixelArray) + iqr(pixelArray, rng=(1, 99)) / 2) > np.amax(pixelArray) else np.median(pixelArray) + iqr( pixelArray, rng=(1, 99)) / 2 centre = minimumValue + (abs(maximumValue) - abs(minimumValue)) / 2 width = maximumValue - abs(minimumValue) return centre, width, maximumValue, minimumValue except Exception as e: print('Error in DisplayImageCommon.readLevelsFromDICOMImage: ' + str(e)) logger.error('Error in DisplayImageCommon.readLevelsFromDICOMImage: ' + str(e))