/// <summary> /// Starts threads for frontal texture creation. /// </summary> /// <param name="groupState">synchronized Threadstate used to observe progress of one or multiple threads.</param> /// <param name="processed">synchronized queue which will be filled with each image index, that is ready.</param> /// <param name="data">pixel intensity values in a 3D Array mapped to a 1D Array.</param> /// <param name="files">all the DICOM files.</param> /// <param name="target">target jagged array, which the result will be written to.</param> /// <param name="windowWidth">Option to set custom windowWidth, Double.MinValue to not use it</param> /// <param name="windowCenter">Option to set custom windowCenter, Double.MinValue to not use it</param> /// <param name="threadCount">Amount of Threads to use</param> private void StartCreatingFrontTextures(ThreadGroupState groupState, ConcurrentQueue <int> processed, int[] data, IReadOnlyList <DiFile> files, IList <Color32[]> target, double windowWidth, double windowCenter, int threadCount) { int spacing = Height / threadCount; for (var i = 0; i < threadCount; ++i) { groupState.Register(); var startIndex = i * spacing; var endIndex = startIndex + spacing; if (i + 1 == threadCount) { endIndex = Height; } var t = new Thread(() => CreateFrontTextures(groupState, processed, data, Width, Height, files, target, windowWidth, windowCenter, startIndex, endIndex)) { IsBackground = true }; t.Start(); } }
/// <summary> /// Start coroutine for parsing of files. /// </summary> public ThreadGroupState StartParsingFiles(string folderPath) { ThreadGroupState state = new ThreadGroupState(); StartCoroutine(InitFiles(folderPath, state)); return(state); }
/// <summary> /// Allows a Unity coroutine to wait for every working thread to finish. /// </summary> /// <param name="threadGroupState">Thread safe thread-state used to observe progress of one or multiple threads.</param> /// <returns>IEnumerator for usage as a coroutine</returns> private static IEnumerator WaitForThreads(ThreadGroupState threadGroupState) { while (threadGroupState.Working > 0) { yield return(null); } }
/// <summary> /// Fills the target array with 3D data while applying basic preprocessing. /// </summary> /// <param name="groupState">synchronized Threadstate used to observe progress of one or multiple threads.</param> /// <param name="files">all the DICOM files.</param> /// <param name="width">width of a DICOM slice.</param> /// <param name="height">height of a DICOM slice.</param> /// <param name="target">1D array receiving the 3D data.</param> /// <param name="start">Start index used to determine partition of images to be computed</param> /// <param name="end">End index used to determine upper bound of partition of images to be computed</param> private static void PreProcess(ThreadGroupState groupState, IReadOnlyList <DiFile> files, int width, int height, IList <int> target, int start, int end) { var storedBytes = new byte[4]; for (var layer = start; layer < end; ++layer) { var currentDiFile = files[layer]; var pixelData = currentDiFile.RemoveElement(0x7FE0, 0x0010); uint mask = ~(0xFFFFFFFF << (currentDiFile.GetHighBit() + 1)); int allocated = currentDiFile.GetBitsAllocated() / 8; var baseOffset = layer * width * height; using (var pixels = new MemoryStream(pixelData.GetValues())) { for (var y = 0; y < height; ++y) { for (var x = 0; x < width; ++x) { //get current Int value pixels.Read(storedBytes, 0, allocated); var value = BitConverter.ToInt32(storedBytes, 0); var currentPix = GetPixelIntensity((int)(value & mask), currentDiFile); target[baseOffset + y * width + x] = currentPix; } } } groupState.IncrementProgress(); Thread.Sleep(10); } groupState.Done(); }
/// <summary> /// Starts threads for volume texture creation. /// </summary> /// <param name="groupState">synchronized Threadstate used to observe progress of one or multiple threads.</param> /// <param name="files">all the DICOM files.</param> /// <param name="data">pixel intensity values in a 3D Array mapped to a 1D Array.</param> /// <param name="target">target jagged array, which the result will be written to.</param> /// <param name="windowWidth">Option to set custom windowWidth, Double.MinValue to not use it</param> /// <param name="windowCenter">Option to set custom windowCenter, Double.MinValue to not use it</param> /// <param name="threadCount">Amount of Threads to use</param> private void StartCreatingVolume(ThreadGroupState groupState, IReadOnlyList <DiFile> files, IReadOnlyList <int> data, IList <Color32> target, double windowWidth, double windowCenter, int threadCount) { #if PRINT_USAGE Debug.Log(Time.time + $" : Started Creating Volume with Window (Center {WindowCenter}, Width {WindowWidth})"); #endif var spacing = files.Count / threadCount; for (var i = 0; i < threadCount; ++i) { groupState.Register(); var startIndex = i * spacing; var endIndex = startIndex + spacing; if (i + 1 == threadCount) { endIndex = files.Count; } var t = new Thread(() => CreateVolume(groupState, data, files, Width, Height, target, windowWidth, windowCenter, startIndex, endIndex)) { IsBackground = true }; t.Start(); } }
/// <summary> /// Starts coroutine for preprocessing DICOM pixeldata /// </summary> public ThreadGroupState StartPreprocessData() { ThreadGroupState state = new ThreadGroupState { TotalProgress = DicomFiles.Length }; PreprocessData(state); return(state); }
/// <summary> /// Unity coroutine for loading the selected folder of files. /// </summary> /// <param name="folderPath">Path of the folder containing the DICOM files</param> /// <param name="threadGroupState">Thread safe thread-state used to observe progress of one or multiple threads.</param> /// <returns>IEnumerator for usage as a coroutine</returns> private IEnumerator InitFiles(string folderPath, ThreadGroupState threadGroupState) { threadGroupState.Register(); //string[] filePaths = Directory.GetFiles(folderPath); //filePaths = Array.FindAll(filePaths, HasNoExtension); var fileNames = GetFiles(folderPath); DicomFiles = new DiFile[fileNames.Count]; threadGroupState.TotalProgress = fileNames.Count; yield return(null); var zeroBased = true; foreach (var path in fileNames) { var diFile = new DiFile(); diFile.InitFromFile(path); if (zeroBased && diFile.GetImageNumber() == DicomFiles.Length) { ShiftLeft(DicomFiles); zeroBased = false; } if (zeroBased) { DicomFiles[diFile.GetImageNumber()] = diFile; } else { DicomFiles[diFile.GetImageNumber() - 1] = diFile; } threadGroupState.IncrementProgress(); yield return(null); } Width = DicomFiles[0].GetImageWidth(); Height = DicomFiles[0].GetImageHeight(); _data = new int[DicomFiles.Length * Width * Height]; VolumeTexture = null; WindowCenterPresets = DicomFiles[0].GetElement(0x0028, 0x1050)?.GetDoubles() ?? new[] { double.MinValue }; WindowWidthPresets = DicomFiles[0].GetElement(0x0028, 0x1051)?.GetDoubles() ?? new[] { double.MinValue }; WindowCenter = WindowCenterPresets[0]; WindowWidth = WindowWidthPresets[0]; MinPixelIntensity = (int)(DicomFiles[0].GetElement(0x0028, 0x1052)?.GetDouble() ?? 0d); MaxPixelIntensity = (int)((DicomFiles[0].GetElement(0x0028, 0x1053)?.GetDouble() ?? 1d) * (Math.Pow(2, DicomFiles[0].GetBitsStored()) - 1) + MinPixelIntensity); threadGroupState.Done(); }
/// <summary> /// Start coroutine for creating 2D textures. /// </summary> public ThreadGroupState StartCreatingTextures() { ThreadGroupState state = new ThreadGroupState { TotalProgress = DicomFiles.Length + Width + Height }; StartCoroutine(CreateTextures(state)); return(state); }
/// <summary> /// Starts coroutine for creating the 3D texture /// </summary> public ThreadGroupState StartCreatingVolume() { ThreadGroupState state = new ThreadGroupState { TotalProgress = DicomFiles.Length }; StartCoroutine(CreateVolume(state)); return(state); }
/// <summary> /// Adds a workload to be completed. /// </summary> /// <param name="threadGroupState">State of the workload</param> /// <param name="description">Displayed description of the workload</param> /// <param name="onFinished">Callback for completed work</param> public void AddWorkload(ThreadGroupState threadGroupState, string description, Action onFinished) { _currentWorkloads.Add(new Tuple <ThreadGroupState, string, Action>(threadGroupState, description, onFinished)); if (_currentWorkloads.Count == 1) { MainMenu.ProgressHandler.TaskDescription = description; MainMenu.ProgressHandler.Value = 0; } MainMenu.ProgressHandler.Max += threadGroupState.TotalProgress; }
/// <summary> /// Fills the target color array with the pixels for all saggital images in range from start to end (excluding end). /// </summary> /// <param name="groupState">synchronized Threadstate used to observe progress of one or multiple threads.</param> /// <param name="processed">synchronized queue which will be filled with each image index, that is ready.</param> /// <param name="data">pixel intensity values in a 3D Array mapped to a 1D Array.</param> /// <param name="width">width of a transversal image.</param> /// <param name="height">height of a transversal image.</param> /// <param name="files">all the DICOM files.</param> /// <param name="target">target jagged array, which the result will be written to.</param> /// <param name="windowWidth">Option to set custom windowWidth, Double.MinValue to not use it</param> /// <param name="windowCenter">Option to set custom windowCenter, Double.MinValue to not use it</param> /// <param name="start">Start index used to determine partition of images to be computed</param> /// <param name="end">End index used to determine upper bound of partition of images to be computed</param> private static void CreateSagTextures(ThreadGroupState groupState, ConcurrentQueue <int> processed, int[] data, int width, int height, IReadOnlyList <DiFile> files, IList <Color32[]> target, double windowWidth, double windowCenter, int start, int end) { for (var x = start; x < end; ++x) { target[x] = new Color32[height * files.Count]; FillPixelsSagittal(x, data, width, height, files, target[x], TransferFunction.Identity, windowWidth, windowCenter); processed.Enqueue(x); Thread.Sleep(5); } groupState.Done(); }
/// <summary> /// Fills the target color array with the pixels for all transversal images in range from start to end (excluding end). /// </summary> /// <param name="groupState">synchronized Threadstate used to observe progress of one or multiple threads.</param> /// <param name="processed">synchronized queue which will be filled with each image index, that is ready.</param> /// <param name="data">pixel intensity values in a 3D Array mapped to a 1D Array.</param> /// <param name="width">width of a transversal image.</param> /// <param name="height">height of a transversal image.</param> /// <param name="files">all the DICOM files.</param> /// <param name="target">target jagged array, which the result will be written to.</param> /// <param name="windowWidth">Option to set custom windowWidth, Double.MinValue to not use it</param> /// <param name="windowCenter">Option to set custom windowCenter, Double.MinValue to not use it</param> /// <param name="start">Start index used to determine partition of images to be computed</param> /// <param name="end">End index used to determine upper bound of partition of images to be computed</param> private static void CreateTransTextures(ThreadGroupState groupState, ConcurrentQueue <int> processed, int[] data, int width, int height, IReadOnlyList <DiFile> files, IList <Color32[]> target, double windowWidth, double windowCenter, int start, int end) { for (var layer = start; layer < end; ++layer) { target[layer] = new Color32[width * height]; FillPixelsTransversal(layer, data, width, height, files, target[layer], TransferFunction.Identity, windowWidth, windowCenter); processed.Enqueue(layer); Thread.Sleep(5); } groupState.Done(); }
/// <summary> /// Unity coroutine used to create the 3D texture using multiple threads. /// </summary> /// <param name="threadGroupState">Thread safe thread-state used to observe progress of one or multiple threads.</param> /// <returns>IEnumerator for usage as a coroutine</returns> private IEnumerator CreateVolume(ThreadGroupState threadGroupState) { VolumeTexture = new Texture3D(Width, Height, DicomFiles.Length, TextureFormat.ARGB32, false); var cols = new Color32[Width * Height * DicomFiles.Length]; StartCreatingVolume(threadGroupState, DicomFiles, _data, cols, WindowWidth, WindowCenter, 6); yield return(WaitForThreads(threadGroupState)); VolumeTexture.SetPixels32(cols); VolumeTexture.Apply(); }
/// <summary> /// Unity coroutine used to create all textures using multiple threads. /// </summary> /// <param name="threadGroupState">Thread safe thread-state used to observe progress of one or multiple threads.</param> /// <returns>IEnumerator for usage as a coroutine</returns> private IEnumerator CreateTextures(ThreadGroupState threadGroupState) { #if PRINT_USAGE Debug.Log(Time.time + $" : Started Creating Textures with Window (Center {WindowCenter}, Width {WindowWidth})"); #endif _transversalTexture2Ds = new Texture2D[DicomFiles.Length]; _frontalTexture2Ds = new Texture2D[Height]; _sagittalTexture2Ds = new Texture2D[Width]; var transTextureColors = new Color32[DicomFiles.Length][]; var frontTextureColors = new Color32[Height][]; var sagTextureColors = new Color32[Width][]; var transProgress = new ConcurrentQueue <int>(); var frontProgress = new ConcurrentQueue <int>(); var sagProgress = new ConcurrentQueue <int>(); StartCreatingTransTextures(threadGroupState, transProgress, _data, DicomFiles, transTextureColors, WindowWidth, WindowCenter, 2); StartCreatingFrontTextures(threadGroupState, frontProgress, _data, DicomFiles, frontTextureColors, WindowWidth, WindowCenter, 2); StartCreatingSagTextures(threadGroupState, sagProgress, _data, DicomFiles, sagTextureColors, WindowWidth, WindowCenter, 2); while (threadGroupState.Working > 0 || !(transProgress.IsEmpty && frontProgress.IsEmpty && sagProgress.IsEmpty)) { int current; if (transProgress.TryDequeue(out current)) { CreateTexture2D(Width, Height, transTextureColors, _transversalTexture2Ds, current); OnTextureUpdate.Invoke(SliceType.Transversal, current); threadGroupState.IncrementProgress(); } if (frontProgress.TryDequeue(out current)) { CreateTexture2D(Width, DicomFiles.Length, frontTextureColors, _frontalTexture2Ds, current); OnTextureUpdate.Invoke(SliceType.Frontal, current); threadGroupState.IncrementProgress(); } if (sagProgress.TryDequeue(out current)) { CreateTexture2D(Height, DicomFiles.Length, sagTextureColors, _sagittalTexture2Ds, current); OnTextureUpdate.Invoke(SliceType.Sagittal, current); threadGroupState.IncrementProgress(); } yield return(null); } }
/// <summary> /// Starts one or more Threads for preprocessing. /// </summary> /// <param name="groupState">synchronized Threadstate used to observe progress of one or multiple threads.</param> /// <param name="files">all the DICOM files.</param> /// <param name="target">1D array receiving the 3D data.</param> /// <param name="threadCount">Amount of Threads to use.</param> private void StartPreProcessing(ThreadGroupState groupState, IReadOnlyList <DiFile> files, int[] target, int threadCount) { int spacing = files.Count / threadCount; for (var i = 0; i < threadCount; ++i) { var startIndex = i * spacing; var endIndex = startIndex + spacing; if (i + 1 == threadCount) { endIndex = files.Count; } groupState.Register(); var t = new Thread(() => PreProcess(groupState, files, Width, Height, target, startIndex, endIndex)) { IsBackground = true }; t.Start(); } }
/// <summary> /// Fills the given 3D color array using the given 3D pixel intensity array of same size. /// </summary> /// <param name="groupState">synchronized Threadstate used to observe progress of one or multiple threads.</param> /// <param name="data">pixel intensity values in a 3D Array mapped to a 1D Array.</param> /// <param name="dicomFiles">all the DICOM files.</param> /// <param name="width">width of a transversal image.</param> /// <param name="height">height of a transversal image.</param> /// <param name="target">§D color array mapped to 1D Array.</param> /// <param name="windowWidth">Option to set custom windowWidth, Double.MinValue to not use it</param> /// <param name="windowCenter">Option to set custom windowCenter, Double.MinValue to not use it</param> /// <param name="start">Start index used to determine partition of images to be computed</param> /// <param name="end">End index used to determine upper bound of partition of images to be computed</param> private void CreateVolume(ThreadGroupState groupState, IReadOnlyList <int> data, IReadOnlyList <DiFile> dicomFiles, int width, int height, IList <Color32> target, double windowWidth, double windowCenter, int start, int end) { var idx = start * width * height; for (var z = start; z < end; ++z) { var idxPartZ = z * width * height; for (var y = 0; y < height; ++y) { var idxPart = idxPartZ + y * width; for (var x = 0; x < width; ++x, ++idx) { target[idx] = TransferFunction.DYN_ALPHA(GetRGBValue(data[idxPart + x], dicomFiles[z], windowWidth, windowCenter)); } } Thread.Sleep(5); groupState.IncrementProgress(); } groupState.Done(); }
/// <summary> /// Creates an empty Segment with uninitialized data array. /// </summary> /// <param name="segmentColor">Color Number used inside the shader.</param> public Segment(Color segmentColor) { SegmentColor = segmentColor; _currentWorkload = new ThreadGroupState(); }
/// <summary> /// Unity coroutine used to preprocess the DICOM pixel data using multiple threads. /// </summary> /// <param name="threadGroupState"></param> /// <returns>IEnumerator for usage as a coroutine</returns> private void PreprocessData(ThreadGroupState threadGroupState) { StartPreProcessing(threadGroupState, DicomFiles, _data, 12); }