// =============================================================== // DICOM Loading: /*! Starts loading the given DICOM, if available. * \note If slice is negative, this loads the entire volume! * \return true if loading process is started, false if the loader is currently busy. */ public bool startLoading(DICOMSeries toLoad, int slice = 0) { if (!isBusy && toLoad != null) { // Lock: isBusy = true; seriesToLoad = toLoad; sliceToLoad = slice; ThreadUtil t = new ThreadUtil(load, loadCallback); t.Run(); // Let event system know what we're currently doing: PatientEventSystem.triggerEvent(PatientEventSystem.Event.LOADING_AddLoadingJob, "Loading DICOM series " + toLoad.seriesUID); if (slice >= 0) { PatientEventSystem.triggerEvent(PatientEventSystem.Event.DICOM_StartLoadingSlice); } else { PatientEventSystem.triggerEvent(PatientEventSystem.Event.DICOM_StartLoadingVolume); } return(true); } else { return(false); } }
public void selectedNewVolumetric(DICOMSeries series) { if (DICOMLoader.instance.currentDICOMVolume != null && DICOMLoader.instance.currentDICOMVolume.seriesInfo == series) { DICOMLoader.instance.unloadVolume(); } else { DICOMLoader.instance.startLoadingVolume(series); } }
public void selectedNewDicom(DICOMSeries series) { // Check if we previously loaded an image from this series. If so, figure out what // the last shown layer was: int previousLayer = DicomImage.GetComponent <DicomDisplayImage> ().savedLayerForSeriesUID(series.seriesUID); // Load this layer: DICOMLoader.instance.startLoading(series, previousLayer); StatusText.gameObject.SetActive(true); StatusText.text = "Loading DICOM ..."; ImageScreen.SetActive(true); ListScreen.SetActive(false); //DicomImage.gameObject.SetActive (false); }
public DICOM3D(DICOMSeries seriesInfo) : base(seriesInfo) { dimensions = 3; Image firstSlice = seriesInfo.firstSlice; Image lastSlice = seriesInfo.lastSlice; VectorDouble o1 = firstSlice.GetOrigin(); if (o1.Count < 3) { throw(new System.Exception("Invalid origins found in first image.")); } origin = new Vector3((float)o1 [0], (float)o1 [1], (float)o1 [2]); // Load the pixel spacing: // NOTE: It seems that the the first value is the spacing between rows (i.e. y direction), // the second value is the spacing between columns (i.e. x direction). // I was not able to verify this so far, since all test dicoms we had have the same spacing in // x and y direction... VectorDouble spacing = firstSlice.GetSpacing(); if (spacing.Count < 2) { throw(new System.Exception("Invalid pixel spacing found in images.")); } pixelSpacing = new Vector2((float)spacing [1], (float)spacing [0]); // DICOMSeries already calculated these values, so get them from there: directionCosineX = seriesInfo.directionCosineX; directionCosineY = seriesInfo.directionCosineY; // Generate the transformation matrices which can later be used to translate pixels to // 3D positions and vice versa. setupTransformationMatrices(); loadVolumeData(); Vector3 corner1 = transformPixelToPatientPos(Vector2.zero, 0f); Vector2 imgDimensions = new Vector2(firstSlice.GetWidth(), firstSlice.GetHeight()); Vector3 corner2 = transformPixelToPatientPos(imgDimensions, seriesInfo.numberOfSlices - 1); //boundingBox = new Bounds ((max - min) / 2 + min, (max - min)); boundingBox = new Bounds((corner2 - corner1) / 2 + corner1, (corner2 - corner1)); Debug.Log("bounding Box: " + boundingBox.center + " " + boundingBox.size); }
void eventNewDicomList(object obj = null) { eventClear(); // Clear the list // Make sure a patient is loaded: Patient p = Patient.getLoadedPatient(); if (p == null) { Text newEntryText = ListEntryButton.transform.GetComponentInChildren <Text> (); newEntryText.text = "No patient loaded."; ListEntryButton.SetActive(true); return; } // Make sure at least one series was found: List <DICOMSeries> series = DICOMLoader.instance.availableSeries; if (series.Count <= 0) { Text newEntryText = ListEntryButton.transform.GetComponentInChildren <Text> (); newEntryText.text = "No series found."; ListEntryButton.SetActive(true); return; } // Deactivate default button: ListEntryButton.SetActive(false); foreach (DICOMSeries s in series) { //customNames.Add (p.getDICOMNameForSeriesUID (uid)); GameObject newEntry = Instantiate(ListEntryButton) as GameObject; newEntry.SetActive(true); Text newEntryText = newEntry.transform.GetComponentInChildren <Text> (); newEntryText.text = s.getDescription(); newEntry.transform.SetParent(ListEntryButton.transform.parent, false); // Make the button load the DICOM: Button newButton = newEntry.GetComponent <Button> (); DICOMSeries captured = s; newButton.onClick.AddListener(() => selectedNewDicom(captured)); } DicomImage.gameObject.SetActive(false); }
/*! Constructor, loads the DICOM image data from file. * The constructor starts the loading of pixel data from the files (filenames are * taken from the seriesInfo). If slice is zero or positive, only the single file * will be read. If slice is negative, the entire volume (i.e. all files - and thus * all slices) will be read. * \note Since the constructor does so much work, the Object should be created in * a background thread and then passed to the main thread. */ public DICOM(DICOMSeries seriesInfo, int slice = -1) { // Remember, we will need it later: this.seriesInfo = seriesInfo; this.slice = slice; if (slice == -1) // load the entire volume: { dimensions = 3; loadVolumeData(); } else { // Make sure that 'slice' is a valid slice number: slice = Mathf.Clamp(slice, 0, seriesInfo.filenames.Count - 1); dimensions = 2; loadImageData(slice); } }
// =============================================================== // DICOM Loading: /*! Starts loading the given DICOM, if available. * \note If slice is negative, this loads the entire volume! * \return true if loading process is started, false if the loader is currently busy. */ public bool startLoading(DICOMSeries toLoad, int slice = 0) { if (!isBusy && toLoad != null) { // Lock: isBusy = true; seriesToLoad = toLoad; sliceToLoad = slice; ThreadUtil t = new ThreadUtil(load, loadCallback); t.Run(); // Let event system know what we're currently doing: PatientEventSystem.triggerEvent(PatientEventSystem.Event.LOADING_AddLoadingJob, "DICOM directory parsing"); return(true); } else { return(false); } }
/*! Searches the directoryToLoad for DICOMs. Called in thread!*/ public void parseDirectory(object sender, DoWorkEventArgs e) { // Parse the directory and return the seriesUIDs of all the DICOM series: try { availableSeries.Clear(); Debug.Log("[DICOM] Searching directory: " + directoryToLoad); VectorString series = ImageSeriesReader.GetGDCMSeriesIDs(directoryToLoad); if (series.Count > 0) { foreach (string s in series) { DICOMSeries info = new DICOMSeries(directoryToLoad, s); availableSeries.Add(info); } } // Debug log: string str = "[DICOM] Found " + series.Count + " series."; Debug.Log(str); } catch (System.Exception err) { Debug.LogError("Error while trying to parse DICOM directory: " + err.Message); } }
/*! Starts loading the given DICOM volume, if available. * \return true if loading process is started, false if the loader is currently busy. */ public bool startLoadingVolume(DICOMSeries toLoad) { return(startLoading(toLoad, -1)); }
public VolumeDataset ImportDICOMSeries(DICOMSeries series) { List <DICOMSliceFile> files = series.dicomFiles; // Check if the series is missing the slice location tag bool needsCalcLoc = false; foreach (DICOMSliceFile file in files) { needsCalcLoc |= file.missingLocation; } // Calculate slice location from "Image Position" (0020,0032) if (needsCalcLoc) { CalcSliceLocFromPos(files); } // Sort files by slice location files.Sort((DICOMSliceFile a, DICOMSliceFile b) => { return(a.location.CompareTo(b.location)); }); Debug.Log($"Importing {files.Count} DICOM slices"); if (files.Count <= 1) { Debug.LogError("Insufficient number of slices."); return(null); } // Create dataset VolumeDataset dataset = new VolumeDataset(); dataset.datasetName = Path.GetFileName(datasetName); dataset.dimX = files[0].file.PixelData.Columns; dataset.dimY = files[0].file.PixelData.Rows; dataset.dimZ = files.Count; int dimension = dataset.dimX * dataset.dimY * dataset.dimZ; dataset.data = new float[dimension]; for (int iSlice = 0; iSlice < files.Count; iSlice++) { DICOMSliceFile slice = files[iSlice]; PixelData pixelData = slice.file.PixelData; int[] pixelArr = ToPixelArray(pixelData); if (pixelArr == null) // This should not happen { pixelArr = new int[pixelData.Rows * pixelData.Columns]; } for (int iRow = 0; iRow < pixelData.Rows; iRow++) { for (int iCol = 0; iCol < pixelData.Columns; iCol++) { int pixelIndex = (iRow * pixelData.Columns) + iCol; int dataIndex = (iSlice * pixelData.Columns * pixelData.Rows) + (iRow * pixelData.Columns) + iCol; int pixelValue = pixelArr[pixelIndex]; float hounsfieldValue = pixelValue * slice.slope + slice.intercept; dataset.data[dataIndex] = Mathf.Clamp(hounsfieldValue, -1024.0f, 3071.0f); } } } if (files[0].pixelSpacing > 0.0f) { dataset.scaleX = files[0].pixelSpacing * dataset.dimX; dataset.scaleY = files[0].pixelSpacing * dataset.dimY; dataset.scaleZ = Mathf.Abs(files[files.Count - 1].location - files[0].location); } dataset.FixDimensions(); return(dataset); }
/*! Constructor, loads the DICOM image data from file. * The constructor starts the loading of pixel data from the files (filenames are * taken from the seriesInfo). If slice is zero or positive, only the single file * will be read. If slice is negative, the entire volume (i.e. all files - and thus * all slices) will be read. * \note Since the constructor does so much work, the Object should be created in * a background thread and then passed to the main thread. */ public DICOM(DICOMSeries seriesInfo) { // Remember, we will need it later: this.seriesInfo = seriesInfo; }
public DICOM2D(DICOMSeries seriesInfo, int slice) : base(seriesInfo) { dimensions = 2; slice = Mathf.Clamp(slice, 0, seriesInfo.filenames.Count - 1); this.slice = slice; VectorString fileNames = seriesInfo.filenames; // Read the DICOM image: image = SimpleITK.ReadImage(fileNames[slice]); loadImageData(image); VectorDouble o1 = image.GetOrigin(); if (o1.Count < 3) { throw(new System.Exception("Invalid origins found in first image.")); } origin = new Vector3((float)o1 [0], (float)o1 [1], (float)o1 [2]); // Load the direction cosines: // ITK stores the direction cosines in a matrix with row-major-ordering. The weird indexing is because // we need the first and second column (0,3,6 for X and 1,4,7 for Y) VectorDouble direction = image.GetDirection(); if (direction.Count < 6) { throw(new System.Exception("Invalid direction cosines found in images.")); } directionCosineX = new Vector3((float)direction [0], (float)direction [3], (float)direction [6]); directionCosineY = new Vector3((float)direction [1], (float)direction [4], (float)direction [7]); sliceNormal = Vector3.Cross(directionCosineX, directionCosineY); // Calculate which direction the normal is facing to determine the orienation (Transverse, // Coronal or Saggital). float absX = Mathf.Abs(sliceNormal.x); float absY = Mathf.Abs(sliceNormal.y); float absZ = Mathf.Abs(sliceNormal.z); if (absX > absY && absX > absZ) { sliceOrientation = SliceOrientation.Saggital; } else if (absY > absX && absY > absZ) { sliceOrientation = SliceOrientation.Coronal; } else if (absZ > absX && absZ > absY) { sliceOrientation = SliceOrientation.Transverse; } else { sliceOrientation = SliceOrientation.Unknown; } // Load the pixel spacing: // NOTE: It seems that the the first value is the spacing between rows (i.e. y direction), // the second value is the spacing between columns (i.e. x direction). // I was not able to verify this so far, since all test dicoms we had have the same spacing in // x and y direction... VectorDouble spacing = image.GetSpacing(); if (spacing.Count < 2) { throw(new System.Exception("Invalid pixel spacing found in images.")); } pixelSpacing = new Vector2((float)spacing [1], (float)spacing [0]); // Generate the transformation matrices which can later be used to translate pixels to // 3D positions and vice versa. setupTransformationMatrices(); }
void eventNewDicomList(object obj = null) { eventClear(); // Clear the list // Make sure a patient is loaded: Patient p = Patient.getLoadedPatient(); if (p == null) { Text newEntryText = ListEntry.transform.GetComponentInChildren <Text> (); newEntryText.text = "No patient loaded."; ListEntry.SetActive(true); return; } // Make sure at least one series was found: List <DICOMSeries> series = DICOMLoader.instance.availableSeries; if (series.Count <= 0) { Text newEntryText = ListEntry.transform.GetComponentInChildren <Text> (); newEntryText.text = "No series found."; ListEntry.SetActive(true); return; } // Deactivate default button: ListEntry.SetActive(false); foreach (DICOMSeries s in series) { //customNames.Add (p.getDICOMNameForSeriesUID (uid)); GameObject newEntry = Instantiate(ListEntry) as GameObject; newEntry.SetActive(true); Text newEntryText = newEntry.transform.GetComponentInChildren <Text> (); newEntryText.text = s.getDescription(); newEntry.transform.SetParent(ListEntry.transform.parent, false); // Keep a reference to this series: DICOMSeries captured = s; // Make the button load the DICOM: GameObject newButton = newEntry.transform.Find("EntryButton").gameObject; newButton.GetComponent <Button> ().onClick.AddListener(() => selectedNewDicom(captured)); // Make the volume button display the DICOM as volume: GameObject volumetricButton = newEntry.transform.Find("VolumetricButton").gameObject; // For now, only allow Transverse volumes: if (captured.sliceOrientation == SliceOrientation.Transverse && captured.isConsecutiveVolume) { volumetricButton.GetComponent <Button> ().onClick.AddListener(() => selectedNewVolumetric(captured)); volumetricButton.SetActive(true); } else { volumetricButton.SetActive(false); } } DicomImage.gameObject.SetActive(false); }