Esempio n. 1
0
        public override INifti <float> AddOverlay(INifti <float> overlay)
        {
            NiftiFloat32 output = (NiftiFloat32)(this.DeepCopy());

            // Caclulate conversion to colour map.
            var range = Header.cal_max - Header.cal_min;
            var scale = ColorMap.Length / range;
            var bias  = -(Header.cal_min);

            // Caclulate conversion to colour map.
            var rangeOverlay = overlay.Header.cal_max - overlay.Header.cal_min;
            var scaleOverlay = overlay.ColorMap.Length / rangeOverlay;
            var biasOverlay  = -(overlay.Header.cal_min);

            for (int i = 0; i < Voxels.Length; ++i)
            {
                // Get the colour map index for our value.
                int idx = (int)((Voxels[i] + bias) * scale);
                if (idx < 0)
                {
                    idx = 0;
                }
                else if (idx > ColorMap.Length - 1)
                {
                    idx = ColorMap.Length - 1;
                }

                // Get the colour map value for the overlay
                int idxOverlay = (int)((overlay.Voxels[i] + biasOverlay) * scaleOverlay);
                if (idxOverlay < 0)
                {
                    idxOverlay = 0;
                }
                else if (idxOverlay > overlay.ColorMap.Length - 1)
                {
                    idxOverlay = overlay.ColorMap.Length - 1;
                }

                var red   = (overlay.ColorMap[idxOverlay].R * overlay.ColorMap[idxOverlay].A + ColorMap[idx].R * (255 - overlay.ColorMap[idxOverlay].A)) / 255;
                var green = (overlay.ColorMap[idxOverlay].G * overlay.ColorMap[idxOverlay].A + ColorMap[idx].G * (255 - overlay.ColorMap[idxOverlay].A)) / 255;
                var blue  = (overlay.ColorMap[idxOverlay].B * overlay.ColorMap[idxOverlay].A + ColorMap[idx].B * (255 - overlay.ColorMap[idxOverlay].A)) / 255;

                output.Voxels[i] = Convert.ToUInt32((byte)red << 16 | (byte)green << 8 | (byte)blue);
            }

            output.ConvertHeaderToRgb();
            output.RecalcHeaderMinMax();

            return(output);
        }
Esempio n. 2
0
        /// <summary>
        /// Compares the decrease in values from the reference Nifti (prior) to the input Nifti (current).
        /// The inputs should be pre-registered and normalised.
        /// </summary>
        /// <param name="input">Current example</param>
        /// <param name="reference">Prior example</param>
        /// <returns>Nifti who's values are the meaningful decrease (less than 0) between prior and current.</returns>
        public static INifti <float> CompareMSLesionDecrease(INifti <float> input, INifti <float> reference)
        {
            INifti <float> output = Compare.GatedSubract(input, reference, backgroundThreshold: 10, minRelevantStd: -1, maxRelevantStd: 5, minChange: 0.8f, maxChange: 5);

            for (int i = 0; i < output.Voxels.Length; ++i)
            {
                if (output.Voxels[i] > 0)
                {
                    output.Voxels[i] = 0;
                }
            }
            output.RecalcHeaderMinMax(); // This will update the header range.
            output.ColorMap = ColorMaps.ReverseGreenScale();

            return(output);
        }
Esempio n. 3
0
        /// <summary>
        /// Normalizes the data to a Z-Value
        /// </summary>
        /// <param name="input"></param>
        /// <param name="backgroundThreshold"></param>
        /// <returns></returns>
        public static INifti <float> ZNormalize(INifti <float> input, float backgroundThreshold = 10)
        {
            dynamic output = input.DeepCopy();
            // We take the mean and standard deviation ignoring background.
            var currentMean   = input.Voxels.Where(val => val > backgroundThreshold).Mean();
            var currentStdDev = input.Voxels.Where(val => val > backgroundThreshold).StandardDeviation();

            for (var i = 0; i < output.Voxels.Length; i++)
            {
                output.Voxels[i] = (float)((output.Voxels[i] - currentMean) / currentStdDev);
            }

            output.RecalcHeaderMinMax(); //update display range

            return(output);
        }
Esempio n. 4
0
        private void EstimateEdgeRatio(INifti <float> increaseNifti, INifti <float> decreaseNifti, MSMetrics qaResults)
        {
            var varianceIncrease = 0d;
            var totalIncrease    = 0d;
            var varianceDecrease = 0d;
            var totalDecrease    = 0d;

            for (int i = 0; i < increaseNifti.Voxels.Length - 1; ++i)
            {
                if ((increaseNifti.Voxels[i] > 0 != increaseNifti.Voxels[i + 1] > 0))
                {
                    varianceIncrease++;
                }
                if (increaseNifti.Voxels[i] > 0)
                {
                    totalIncrease++;                              // Note, we'll miss the last voxel but it's just a rough estimate.
                }
                if ((decreaseNifti.Voxels[i] < 0 != decreaseNifti.Voxels[i + 1] < 0))
                {
                    varianceDecrease++;
                }
                if (decreaseNifti.Voxels[i] < 0)
                {
                    totalDecrease++;
                }

                if (i == increaseNifti.Voxels.Length - 2)
                {
                    if (increaseNifti.Voxels[i + 1] > 0)
                    {
                        totalIncrease++;
                    }
                    if (decreaseNifti.Voxels[i + 1] < 0)
                    {
                        totalDecrease++;
                    }
                }
            }

            _log.Info($@"Edge ratio for increase: {varianceIncrease / totalIncrease}");
            _log.Info($@"Edge ratio for decrease: {varianceDecrease / totalDecrease}");

            if (totalIncrease == 0 || totalDecrease == 0 || double.IsNaN(varianceIncrease / totalIncrease))
            {
                qaResults.Passed = false;
            }
        }
Esempio n. 5
0
        public static INifti <float> ANTSRegistration(INifti <float> floating, INifti <float> reference, DataReceivedEventHandler updates = null)
        {
            // Setup our temp file names.
            string niftiInPath  = Path.GetFullPath(Tools.TEMPDIR + floating.GetHashCode() + ".antsrego.in.nii");
            string niftiRefPath = Path.GetFullPath(Tools.TEMPDIR + floating.GetHashCode() + ".antsrego.ref.nii");

            floating.WriteNifti(niftiInPath);
            reference.WriteNifti(niftiRefPath);

            string niftiOutPath = Path.GetFullPath(ANTSRegistration(niftiInPath, niftiRefPath, updates));

            var output = floating.DeepCopy();

            output = output.ReadNifti(niftiOutPath);

            return(output);
        }
Esempio n. 6
0
        /// <summary>
        /// Uses the CMTK registration and reformatx tools to register and reslice the floating nifti to match the reference nifti.
        /// </summary>
        /// <param name="floating">Nifti to be registered</param>
        /// <param name="reference">Reference nifti</param>
        /// <param name="updates">Event handler for updates from the toolchain...</param>
        /// <returns></returns>
        public static INifti <float> CMTKRegistration(INifti <float> floating, INifti <float> reference, DataReceivedEventHandler updates = null)
        {
            // Setup our temp file names.
            string niftiInPath  = Tools.TEMPDIR + floating.GetHashCode() + ".cmtkrego.in.nii";
            string niftiRefPath = Tools.TEMPDIR + floating.GetHashCode() + ".cmtkrego.ref.nii";
            string niftiOutPath = Tools.TEMPDIR + floating.GetHashCode() + ".cmtkrego.out.nii";

            // Write nifti to temp directory.
            floating.WriteNifti(niftiInPath);
            reference.WriteNifti(niftiRefPath);

            niftiOutPath = CMTKRegistration(niftiInPath, niftiRefPath, updates);

            //INifti output = floating.DeepCopy();
            floating.ReadNifti(niftiOutPath);

            return(floating);
        }
Esempio n. 7
0
        /// <summary>
        /// Shifts the ditribution to be within the given range. Default is 0-1.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="rangeStart"></param>
        /// <param name="rangeEnd"></param>
        /// <returns></returns>
        public static INifti <float> RangeNormalize(INifti <float> input, float rangeStart = 0, float rangeEnd = 1)
        {
            if (rangeEnd <= rangeStart)
            {
                throw new ArgumentException("Start of range cannot be greater than end of range.");
            }

            var min   = input.Voxels.Min();
            var range = input.Voxels.Max() - input.Voxels.Min();

            var output = input.DeepCopy();

            for (int i = 0; i < output.Voxels.Length; ++i)
            {
                output.Voxels[i] = ((output.Voxels[i] - min) / range) * (rangeEnd - rangeStart) + rangeStart;
            }

            return(output);
        }
Esempio n. 8
0
        /// <summary>
        /// Uses the BrainSuite BSE tool to extract the brain from a given INifti.
        /// </summary>
        /// <param name="input">Nifti which contains the brain to be extracted</param>
        /// <param name="updates">Data handler for updates from the BSE tool.</param>
        /// <returns>The INifti containing the extracted brain.</returns>
        public static INifti <float> BrainSuiteBSE(INifti <float> input, DataReceivedEventHandler updates = null)
        {
            // Setup our temp file names.
            string niftiInPath  = Path.GetFullPath(Tools.TEMPDIR + input.GetHashCode() + ".bse.in.nii");
            string niftiOutPath = Path.GetFullPath(Tools.TEMPDIR + input.GetHashCode() + ".bse.out.nii");

            // Write nifti to temp directory.
            input.WriteNifti(niftiInPath);

            var args = $"--auto --trim -i \"{niftiInPath}\" -o \"{niftiOutPath}\"";

            ProcessBuilder.CallExecutableFile(CapiConfig.GetConfig().Binaries.bse, args, outputDataReceived: updates);

            var output = input.DeepCopy(); // Sometimes this messes with the header and gives us a 4-up???

            output.ReadNifti(niftiOutPath);

            return(output);
        }
Esempio n. 9
0
        /// <summary>
        /// Uses the ANTS implementation of the N4 bias correction algorithm.
        /// </summary>
        /// <param name="input">The input nifti to be corrected</param>
        /// <param name="updates">Event handler for updates from the process</param>
        /// <returns>New, corrected nifti</returns>
        public static INifti <float> AntsN4(INifti <float> input, DataReceivedEventHandler updates = null)
        {
            // Setup our temp file names.
            string niftiInPath  = Path.GetFullPath(Tools.TEMPDIR + input.GetHashCode() + ".antsN4.in.nii");
            string niftiOutPath = Path.GetFullPath(Tools.TEMPDIR + input.GetHashCode() + ".antsN4.out.nii");

            // Write nifti to temp directory.
            input.WriteNifti(niftiInPath);

            var args = $"-i \"{niftiInPath}\" -o \"{niftiOutPath}\"";

            ProcessBuilder.CallExecutableFile(CapiConfig.GetConfig().Binaries.N4BiasFieldCorrection, args, outputDataReceived: updates);

            var output = input.DeepCopy();

            output.ReadNifti(niftiOutPath);
            output.RecalcHeaderMinMax();

            return(output);
        }
Esempio n. 10
0
        private Histogram DoCompare(INifti <float> currentnii, INifti <float> priornii)
        {
            _log.Info("Starting normalization...");
            currentnii = Normalization.ZNormalize(currentnii, priornii);
            _log.Info($@"..done.");

            currentnii.RecalcHeaderMinMax();
            priornii.RecalcHeaderMinMax();

            var tasks     = new List <Task>();
            var histogram = new Histogram
            {
                Prior   = priornii,
                Current = currentnii
            };

            var cs = _recipe.CompareSettings;

            if (cs.CompareIncrease)
            {
                var t = Task.Run(() =>
                {
                    _log.Info("Comparing increased signal...");
                    var increase = Compare.GatedSubract(currentnii, priornii, cs.BackgroundThreshold, cs.MinRelevantStd, cs.MaxRelevantStd, cs.MinChange, cs.MaxChange);
                    for (int i = 0; i < increase.Voxels.Length; ++i)
                    {
                        increase.Voxels[i] = increase.Voxels[i] > 0 ? increase.Voxels[i] : 0;
                    }
                    increase.RecalcHeaderMinMax();
                    increase.ColorMap  = ColorMaps.RedScale();
                    histogram.Increase = increase;
                    var increaseOut    = currentnii.AddOverlay(increase);
                    var outpath        = _currentPath + ".increase.nii";
                    increaseOut.WriteNifti(outpath);
                    Metrics.ResultFiles.Add(new ResultFile()
                    {
                        FilePath = outpath, Description = "Increased Signal", Type = ResultType.CURRENT_PROCESSED
                    });
                });
                tasks.Add(t);
            }
            if (cs.CompareDecrease)
            {
                _log.Info("Comparing decreased signal...");
                // I know the code in these two branches looks similar but there's too many inputs to make a function that much simpler...
                var t = Task.Run(() =>
                {
                    var decrease = Compare.GatedSubract(currentnii, priornii, cs.BackgroundThreshold, cs.MinRelevantStd, cs.MaxRelevantStd, cs.MinChange, cs.MaxChange);
                    for (int i = 0; i < decrease.Voxels.Length; ++i)
                    {
                        decrease.Voxels[i] = decrease.Voxels[i] < 0 ? decrease.Voxels[i] : 0;
                    }
                    decrease.RecalcHeaderMinMax();
                    decrease.ColorMap = ColorMaps.ReverseGreenScale();

                    histogram.Decrease = decrease;
                    var decreaseOut    = currentnii.AddOverlay(decrease);
                    var outpath        = _currentPath + ".decrease.nii";
                    decreaseOut.WriteNifti(outpath);
                    Metrics.ResultFiles.Add(new ResultFile()
                    {
                        FilePath = outpath, Description = "Decreased Signal", Type = ResultType.CURRENT_PROCESSED
                    });
                });
                tasks.Add(t);
            }
            Task.WaitAll(tasks.ToArray());
            _log.Info("...done.");

            return(histogram);
        }
Esempio n. 11
0
        private void CheckBrainExtractionMatch(
            INifti <float> currentNifti, INifti <float> priorNifti,
            INifti <float> currentNiftiWithSkull, INifti <float> priorNiftiWithSkull, MSMetrics qaResults)
        {
            var volCurrent       = 0d;
            var volPrior         = 0d;
            var volWSkullCurrent = 0;
            var volWSkullPrior   = 0;

            for (int i = 0; i < currentNifti.Voxels.Length; ++i)
            {
                if (currentNifti.Voxels[i] > 0)
                {
                    volCurrent++;
                }
                if (currentNiftiWithSkull.Voxels[i] > 30)
                {
                    volWSkullCurrent++;
                }
                if (priorNifti.Voxels[i] > 0)
                {
                    volPrior++;
                }
                if (priorNiftiWithSkull.Voxels[i] > 30)
                {
                    volWSkullPrior++;
                }
            }
            var match = Math.Min(volPrior, volCurrent) / Math.Max(volPrior, volCurrent);

            // Add results to QA list
            qaResults.VoxelVolPrior   = volPrior;
            qaResults.VoxelVolCurrent = volCurrent;
            qaResults.BrainMatch      = match;

            _log.Info($@"Percentage of current volume that's brain: {(int)(volCurrent / volWSkullCurrent * 100d)}%");
            _log.Info($@"Percentage of prior volume that's brain: {(int)(volPrior / volWSkullPrior * 100d)}%");
            _log.Info($@"Brain extraction match: {(int)(match * 100)}%");

            // If the match is sub-80% it's probably because one of the brains didn't extract so
            // the compare operation will automatically use the intersection of the two. On the
            // other hand if we're above 80% but less than 95% one of the extractions probably cut
            // out a chunk of brain. So we'll make a mask of the union and apply it to both sides.
            if (match > 0.7 && match < 0.95)
            {
                _log.Info($@"Brain extraction match not good enough, taking the union...");
                // Let's try to make the brain mask an OR of the two.
                var mask = currentNifti.DeepCopy();
                for (int i = 0; i < mask.Voxels.Length; ++i)
                {
                    if (currentNifti.Voxels[i] != 0 || priorNifti.Voxels[i] != 0)
                    {
                        mask.Voxels[i] = 1;
                    }
                    else
                    {
                        mask.Voxels[i] = 0;
                    }
                }

                currentNifti = currentNiftiWithSkull.DeepCopy();
                priorNifti   = priorNiftiWithSkull.DeepCopy();

                for (int i = 0; i < currentNifti.Voxels.Length; ++i)
                {
                    currentNifti.Voxels[i] = currentNifti.Voxels[i] * mask.Voxels[i];
                    priorNifti.Voxels[i]   = priorNifti.Voxels[i] * mask.Voxels[i];
                }

                currentNifti.RecalcHeaderMinMax();
                priorNifti.RecalcHeaderMinMax();

                // Check brain extraction match again...
                volCurrent = 0d;
                volPrior   = 0d;
                for (int i = 0; i < currentNifti.Voxels.Length; ++i)
                {
                    if (currentNifti.Voxels[i] > 0)
                    {
                        volCurrent++;
                    }
                    if (priorNifti.Voxels[i] > 0)
                    {
                        volPrior++;
                    }
                }
                match = Math.Min(volPrior, volCurrent) / Math.Max(volPrior, volCurrent);
                _log.Info($@"Brain extraction match after mask: {(int)(match * 100)}%");
            }
            else if (match < 0.7)
            {
                qaResults.Passed = false;
            }

            // In theory this should stop the skull highlights darkening the output images...
            priorNifti.RecalcHeaderMinMax();
            currentNifti.RecalcHeaderMinMax();
            priorNiftiWithSkull.Header.cal_max   = priorNifti.Header.cal_max;
            currentNiftiWithSkull.Header.cal_max = currentNifti.Header.cal_max;
        }
Esempio n. 12
0
        public INifti <float> Classify(INifti <float> prior, INifti <float> current)
        {
            var output = MSCompare.CompareMSLesionIncrease(prior, current);

            return(output);
        }
Esempio n. 13
0
        /// <summary>
        /// Compares the meaningful change in value between the reference Nifti (prior) and the input Nifti (current).
        /// This function also allows significant fine-tuning of the cut-off values.
        /// </summary>
        /// <param name="input">Current Nifti</param>
        /// <param name="reference">Prior Nifti</param>
        /// <param name="backgroundThreshold">Absolute value of background threashold. Any voxels with a value less than this are considered background and ignored.</param>
        /// <param name="minRelevantStd">Minimum relevant value in number of standard deviations from the mean. e.g. a value of -1 will mean that the minimum relevant value will be the mean - 1 standard deviation. Voxels below this threashold are ignored.</param>
        /// <param name="maxRelevantStd">Maximum relevant value in number of standard deviations from the mean. e.g. a value of 3 will mean that the maximum relevant value will be the mean + 3 standard deviations. Voxels above this threashold are ignored.</param>
        /// <param name="minChange">Minimum difference to be considered significant (e.g. noise threshold). Value is given in multiples of the standard deviation for the input voxels (ignoring background).</param>
        /// <param name="maxChange">Maximum difference to be considered significant. Value is given in multiples of the standard deviation for the input voxels (ignoring background).</param>
        /// <returns>INifti object which contains the relevant difference between the reference nifti and the input nifti.</returns>
        public static INifti <float> GatedSubract(INifti <float> input, INifti <float> reference, float backgroundThreshold = 10, float minRelevantStd = -1, float maxRelevantStd = 5, float minChange = 0.8f, float maxChange = 5)
        {
            INifti <float> output = input.DeepCopy();

            //var mean = (float)input.Voxels.Where(val => val > backgroundThreshold).MeanStandardDeviation();
            var meanstddev = input.Voxels.Where(val => val > backgroundThreshold).MeanStandardDeviation();
            var mean       = meanstddev.Item1;
            var stdDev     = meanstddev.Item2; //(Not sure why decompose stopped working here).
            //float range = input.voxels.Max() - input.voxels.Min();
            // Values from trial and error....
            float minRelevantValue = (float)(mean + (minRelevantStd * stdDev));
            float maxRelevantValue = (float)(mean + (maxRelevantStd * stdDev));

            if (input.Voxels.Length != reference.Voxels.Length)
            {
                throw new Exception("Input and reference don't match size");
            }

            for (int i = 0; i < input.Voxels.Length; ++i)
            {
                output.Voxels[i] = input.Voxels[i] - reference.Voxels[i];


                // We want to ignore changes below the minimum relevant value.
                if (input.Voxels[i] < minRelevantValue)
                {
                    output.Voxels[i] = 0;
                }
                if (reference.Voxels[i] < minRelevantValue)
                {
                    output.Voxels[i] = 0;
                }

                // And above the maximum relevant value.
                if (input.Voxels[i] > maxRelevantValue)
                {
                    output.Voxels[i] = 0;
                }
                if (reference.Voxels[i] > maxRelevantValue)
                {
                    output.Voxels[i] = 0;
                }

                // If we haven't changed by at least 1 stdDev we're not significant
                if (Math.Abs(output.Voxels[i]) < Math.Abs(minChange * stdDev))
                {
                    output.Voxels[i] = 0;
                }
                if (Math.Abs(output.Voxels[i]) > Math.Abs(maxChange * stdDev))
                {
                    output.Voxels[i] = 0;
                }
                if (reference.Voxels[i] < backgroundThreshold)
                {
                    output.Voxels[i] = 0;
                }
                if (input.Voxels[i] < backgroundThreshold)
                {
                    output.Voxels[i] = 0;
                }
            }

            for (int i = 1; i < output.Voxels.Length - 1; ++i)
            {
                if (output.Voxels[i - 1] == 0 && output.Voxels[i + 1] == 0)
                {
                    output.Voxels[i] = 0;
                }
            }

            output.RecalcHeaderMinMax(); // Update header range.

            var stdDv = output.Voxels.StandardDeviation();
            var mean2 = output.Voxels.Where(val => val > 0).Mean();

            System.Console.WriteLine($"Compared. Mean={mean2}, stdDv={stdDv}, size={output.Voxels.Where(val => val > 0).Count()}");

            return(output);
        }
Esempio n. 14
0
 public abstract INifti <T> AddOverlay(INifti <T> overlay);