コード例 #1
0
        async void UpdateParticles()
        {
            if (!IsInitialized)
            {
                return;
            }

            bool  UseWarp      = (bool)RadioParticlesWarp.IsChecked;
            float AngPixCoords = (float)ParticleCoordinatesPixel;
            float AngPixShifts = (float)ParticleShiftsPixel;
            int   ResMov       = TemporalResMov;
            int   ResRot       = TemporalResRot;

            TextParticlesError.Visibility = Visibility.Collapsed;
            ProgressParticles.Visibility  = Visibility.Visible;

            ParticlesFinal = null;

            await Task.Run(() =>
            {
                if (UseWarp && TableWarp != null)
                {
                    #region Figure out missing sources

                    Dictionary <string, int> ParticleHashes = new Dictionary <string, int>();
                    foreach (var hash in TableWarp.GetColumn("wrpSourceHash"))
                    {
                        if (!ParticleHashes.ContainsKey(hash))
                        {
                            ParticleHashes.Add(hash, 0);
                        }
                        ParticleHashes[hash]++;
                    }

                    HashSet <string> AvailableHashes = new HashSet <string>(Helper.Combine(ValidSources.Select(s => s.Files.Keys.ToArray())));
                    List <string> HashesNotFound     = ParticleHashes.Keys.Where(hash => !AvailableHashes.Contains(hash)).ToList();

                    ParticlesUnmatched = HashesNotFound.Sum(h => ParticleHashes[h]);
                    ParticlesMatched   = TableWarp.RowCount - ParticlesUnmatched;

                    #endregion

                    #region Create particles

                    int TableResMov      = 1, TableResRot = 1;
                    string[] PrefixesMov = { "wrpCoordinateX", "wrpCoordinateY", "wrpCoordinateZ" };
                    string[] PrefixesRot = { "wrpAngleRot", "wrpAngleTilt", "wrpAnglePsi" };

                    while (true)
                    {
                        if (PrefixesMov.Any(p => !TableWarp.HasColumn(p + (TableResMov + 1).ToString())))
                        {
                            break;
                        }
                        TableResMov++;
                    }
                    while (true)
                    {
                        if (PrefixesRot.Any(p => !TableWarp.HasColumn(p + (TableResRot + 1).ToString())))
                        {
                            break;
                        }
                        TableResRot++;
                    }

                    string[] NamesCoordX = Helper.ArrayOfFunction(i => $"wrpCoordinateX{i + 1}", TableResMov);
                    string[] NamesCoordY = Helper.ArrayOfFunction(i => $"wrpCoordinateY{i + 1}", TableResMov);
                    string[] NamesCoordZ = Helper.ArrayOfFunction(i => $"wrpCoordinateZ{i + 1}", TableResMov);

                    string[] NamesAngleRot  = Helper.ArrayOfFunction(i => $"wrpAngleRot{i + 1}", TableResRot);
                    string[] NamesAngleTilt = Helper.ArrayOfFunction(i => $"wrpAngleTilt{i + 1}", TableResRot);
                    string[] NamesAnglePsi  = Helper.ArrayOfFunction(i => $"wrpAnglePsi{i + 1}", TableResRot);

                    float[][] ColumnsCoordX = NamesCoordX.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();
                    float[][] ColumnsCoordY = NamesCoordY.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();
                    float[][] ColumnsCoordZ = NamesCoordZ.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();

                    float[][] ColumnsAngleRot  = NamesAngleRot.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();
                    float[][] ColumnsAngleTilt = NamesAngleTilt.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();
                    float[][] ColumnsAnglePsi  = NamesAnglePsi.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();

                    int[] ColumnSubset = TableWarp.GetColumn("wrpRandomSubset").Select(v => int.Parse(v) - 1).ToArray();

                    string[] ColumnSourceName = TableWarp.GetColumn("wrpSourceName");
                    string[] ColumnSourceHash = TableWarp.GetColumn("wrpSourceHash");

                    ParticlesFinal = new Particle[TableWarp.RowCount];

                    for (int p = 0; p < ParticlesFinal.Length; p++)
                    {
                        float3[] Coordinates = Helper.ArrayOfFunction(i => new float3(ColumnsCoordX[p][i],
                                                                                      ColumnsCoordY[p][i],
                                                                                      ColumnsCoordZ[p][i]), TableResMov);
                        float3[] Angles = Helper.ArrayOfFunction(i => new float3(ColumnsAngleRot[p][i],
                                                                                 ColumnsAngleTilt[p][i],
                                                                                 ColumnsAnglePsi[p][i]), TableResRot);

                        ParticlesFinal[p] = new Particle(Coordinates, Angles, ColumnSubset[p], ColumnSourceName[p], ColumnSourceHash[p]);
                        ParticlesFinal[p].ResampleCoordinates(TemporalResMov);
                        ParticlesFinal[p].ResampleAngles(TemporalResRot);
                    }

                    #endregion
                }
                else if (!UseWarp && TableRelion != null)
                {
                    #region Figure out missing and ambigous sources

                    Dictionary <string, int> ParticleImageNames = new Dictionary <string, int>();
                    foreach (var imageName in TableRelion.GetColumn("rlnMicrographName"))
                    {
                        if (!ParticleImageNames.ContainsKey(imageName))
                        {
                            ParticleImageNames.Add(imageName, 0);
                        }
                        ParticleImageNames[imageName]++;
                    }

                    List <string> NamesNotFound  = new List <string>();
                    List <string> NamesAmbiguous = new List <string>();
                    HashSet <string> NamesGood   = new HashSet <string>();
                    foreach (var imageName in ParticleImageNames.Keys)
                    {
                        int Possibilities = UsedSources.Count(source => source.Files.Values.Contains(imageName));

                        if (Possibilities == 0)
                        {
                            NamesNotFound.Add(imageName);
                        }
                        else if (Possibilities > 1)
                        {
                            NamesAmbiguous.Add(imageName);
                        }
                        else
                        {
                            NamesGood.Add(imageName);
                        }
                    }

                    if (NamesAmbiguous.Count > 0)
                    {
                        Dispatcher.Invoke(() =>
                        {
                            TextParticlesError.Text       = $"{NamesAmbiguous.Count} image names are ambiguous between selected data sources!";
                            TextParticlesError.Visibility = Visibility.Visible;
                        });
                    }

                    ParticlesUnmatched = NamesNotFound.Sum(h => ParticleImageNames[h]);
                    ParticlesMatched   = TableRelion.RowCount - ParticlesUnmatched;

                    #endregion

                    #region Create particles

                    Dictionary <string, string> ReverseMapping = new Dictionary <string, string>();
                    foreach (var source in UsedSources)
                    {
                        foreach (var pair in source.Files)
                        {
                            if (NamesGood.Contains(pair.Value))
                            {
                                ReverseMapping.Add(pair.Value, pair.Key);
                            }
                        }
                    }

                    List <int> ValidRows    = new List <int>(TableRelion.RowCount);
                    string[] ColumnMicNames = TableRelion.GetColumn("rlnMicrographName");
                    for (int r = 0; r < ColumnMicNames.Length; r++)
                    {
                        if (ReverseMapping.ContainsKey(ColumnMicNames[r]))
                        {
                            ValidRows.Add(r);
                        }
                    }
                    Star CleanRelion = TableRelion.CreateSubset(ValidRows);

                    int NParticles  = CleanRelion.RowCount;
                    bool IsTomogram = CleanRelion.HasColumn("rlnCoordinateZ");

                    float[] CoordinatesX = CleanRelion.GetColumn("rlnCoordinateX").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixCoords).ToArray();
                    float[] CoordinatesY = CleanRelion.GetColumn("rlnCoordinateY").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixCoords).ToArray();
                    float[] CoordinatesZ = IsTomogram ? CleanRelion.GetColumn("rlnCoordinateZ").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixCoords).ToArray() : new float[NParticles];

                    float[] OffsetsX = CleanRelion.HasColumn("rlnOffsetX") ? CleanRelion.GetColumn("rlnOffsetX").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixShifts).ToArray() : new float[NParticles];
                    float[] OffsetsY = CleanRelion.HasColumn("rlnOffsetY") ? CleanRelion.GetColumn("rlnOffsetY").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixShifts).ToArray() : new float[NParticles];
                    float[] OffsetsZ = CleanRelion.HasColumn("rlnOffsetZ") ? CleanRelion.GetColumn("rlnOffsetZ").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixShifts).ToArray() : new float[NParticles];

                    float3[] Coordinates = Helper.ArrayOfFunction(p => new float3(CoordinatesX[p] - OffsetsX[p], CoordinatesY[p] - OffsetsY[p], CoordinatesZ[p] - OffsetsZ[p]), NParticles);

                    float[] AnglesRot  = CleanRelion.GetColumn("rlnAngleRot").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] AnglesTilt = CleanRelion.GetColumn("rlnAngleTilt").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] AnglesPsi  = CleanRelion.GetColumn("rlnAnglePsi").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();

                    float3[] Angles = Helper.ArrayOfFunction(p => new float3(AnglesRot[p], AnglesTilt[p], AnglesPsi[p]), NParticles);

                    int[] Subsets = CleanRelion.HasColumn("rlnRandomSubset") ? CleanRelion.GetColumn("rlnRandomSubset").Select(v => int.Parse(v, CultureInfo.InvariantCulture) - 1).ToArray() : Helper.ArrayOfFunction(i => i % 2, NParticles);

                    string[] MicrographNames  = CleanRelion.GetColumn("rlnMicrographName").ToArray();
                    string[] MicrographHashes = MicrographNames.Select(v => ReverseMapping[v]).ToArray();

                    ParticlesFinal = Helper.ArrayOfFunction(p => new Particle(new[] { Coordinates[p] }, new[] { Angles[p] }, Subsets[p], MicrographNames[p], MicrographHashes[p]), NParticles);
                    foreach (var particle in ParticlesFinal)
                    {
                        particle.ResampleCoordinates(ResMov);
                        particle.ResampleAngles(ResRot);
                    }

                    #endregion
                }
                else
                {
                    ParticlesMatched   = 0;
                    ParticlesUnmatched = 0;
                }
            });

            ProgressParticles.Visibility = Visibility.Hidden;

            TextParticlesResult.Text = $"{ParticlesMatched}/{ParticlesMatched + ParticlesUnmatched} particles matched to available data sources";

            RevalidateTab();
        }
コード例 #2
0
        async void UpdateParticles()
        {
            if (!IsInitialized || PauseParticleUpdates)
            {
                return;
            }


            bool  UseWarp      = (bool)RadioParticlesWarp.IsChecked;
            float AngPixCoords = (float)ParticleCoordinatesPixel;
            float AngPixShifts = (float)ParticleShiftsPixel;
            int   ResMov       = TemporalResMov;
            int   ResRot       = TemporalResMov;

            TextParticlesError.Visibility = Visibility.Collapsed;
            ProgressParticles.Visibility  = Visibility.Visible;

            ParticlesNew = null;

            ClosestDistanceOld = null;
            ClosestDistanceNew = null;

            await Task.Run(() =>
            {
                if (UseWarp && TableWarp != null)
                {
                    #region Figure out missing sources

                    Dictionary <string, int> ParticleHashes = new Dictionary <string, int>();
                    foreach (var hash in TableWarp.GetColumn("wrpSourceHash"))
                    {
                        if (!ParticleHashes.ContainsKey(hash))
                        {
                            ParticleHashes.Add(hash, 0);
                        }
                        ParticleHashes[hash]++;
                    }

                    HashSet <string> AvailableHashes = new HashSet <string>(Helper.Combine(ValidSources.Select(s => s.Files.Keys.ToArray())));
                    List <string> HashesNotFound     = ParticleHashes.Keys.Where(hash => !AvailableHashes.Contains(hash)).ToList();

                    ParticlesUnmatched = HashesNotFound.Sum(h => ParticleHashes[h]);
                    ParticlesMatched   = TableWarp.RowCount - ParticlesUnmatched;

                    #endregion

                    #region Create particles

                    int TableResMov      = 1, TableResRot = 1;
                    string[] PrefixesMov = { "wrpCoordinateX", "wrpCoordinateY", "wrpCoordinateZ" };
                    string[] PrefixesRot = { "wrpAngleRot", "wrpAngleTilt", "wrpAnglePsi" };

                    while (true)
                    {
                        if (PrefixesMov.Any(p => !TableWarp.HasColumn(p + (TableResMov + 1).ToString())))
                        {
                            break;
                        }
                        TableResMov++;
                    }
                    while (true)
                    {
                        if (PrefixesRot.Any(p => !TableWarp.HasColumn(p + (TableResRot + 1).ToString())))
                        {
                            break;
                        }
                        TableResRot++;
                    }

                    string[] NamesCoordX = Helper.ArrayOfFunction(i => $"wrpCoordinateX{i + 1}", TableResMov);
                    string[] NamesCoordY = Helper.ArrayOfFunction(i => $"wrpCoordinateY{i + 1}", TableResMov);
                    string[] NamesCoordZ = Helper.ArrayOfFunction(i => $"wrpCoordinateZ{i + 1}", TableResMov);

                    string[] NamesAngleRot  = Helper.ArrayOfFunction(i => $"wrpAngleRot{i + 1}", TableResRot);
                    string[] NamesAngleTilt = Helper.ArrayOfFunction(i => $"wrpAngleTilt{i + 1}", TableResRot);
                    string[] NamesAnglePsi  = Helper.ArrayOfFunction(i => $"wrpAnglePsi{i + 1}", TableResRot);

                    float[][] ColumnsCoordX = NamesCoordX.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();
                    float[][] ColumnsCoordY = NamesCoordY.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();
                    float[][] ColumnsCoordZ = NamesCoordZ.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();

                    float[][] ColumnsAngleRot  = NamesAngleRot.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();
                    float[][] ColumnsAngleTilt = NamesAngleTilt.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();
                    float[][] ColumnsAnglePsi  = NamesAnglePsi.Select(n => TableWarp.GetColumn(n).Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray()).ToArray();

                    int[] ColumnSubset = TableWarp.GetColumn("wrpRandomSubset").Select(v => int.Parse(v) - 1).ToArray();

                    string[] ColumnSourceName = TableWarp.GetColumn("wrpSourceName");
                    string[] ColumnSourceHash = TableWarp.GetColumn("wrpSourceHash");

                    ParticlesNew = new Particle[TableWarp.RowCount];

                    for (int p = 0; p < ParticlesNew.Length; p++)
                    {
                        float3[] Coordinates = Helper.ArrayOfFunction(i => new float3(ColumnsCoordX[i][p],
                                                                                      ColumnsCoordY[i][p],
                                                                                      ColumnsCoordZ[i][p]), TableResMov);
                        float3[] Angles = Helper.ArrayOfFunction(i => new float3(ColumnsAngleRot[i][p],
                                                                                 ColumnsAngleTilt[i][p],
                                                                                 ColumnsAnglePsi[i][p]), TableResRot);

                        ParticlesNew[p] = new Particle(Coordinates, Angles, ColumnSubset[p], ColumnSourceName[p], ColumnSourceHash[p]);
                        ParticlesNew[p].ResampleCoordinates(ResMov);
                        ParticlesNew[p].ResampleAngles(ResRot);
                    }

                    #endregion
                }
                else if (!UseWarp && TableRelion != null)
                {
                    #region Figure out missing and ambigous sources

                    Dictionary <string, int> ParticleImageNames = new Dictionary <string, int>();
                    foreach (var imageName in TableRelion.GetColumn("rlnMicrographName"))
                    {
                        if (!ParticleImageNames.ContainsKey(imageName))
                        {
                            ParticleImageNames.Add(imageName, 0);
                        }
                        ParticleImageNames[imageName]++;
                    }

                    List <string> NamesNotFound  = new List <string>();
                    List <string> NamesAmbiguous = new List <string>();
                    HashSet <string> NamesGood   = new HashSet <string>();
                    foreach (var imageName in ParticleImageNames.Keys)
                    {
                        int Possibilities = UsedSources.Count(source => source.Files.Values.Contains(imageName));

                        if (Possibilities == 0)
                        {
                            NamesNotFound.Add(imageName);
                        }
                        else if (Possibilities > 1)
                        {
                            NamesAmbiguous.Add(imageName);
                        }
                        else
                        {
                            NamesGood.Add(imageName);
                        }
                    }

                    if (NamesAmbiguous.Count > 0)
                    {
                        Dispatcher.Invoke(() =>
                        {
                            TextParticlesError.Text       = $"{NamesAmbiguous.Count} image names are ambiguous between selected data sources!";
                            TextParticlesError.Visibility = Visibility.Visible;
                        });
                    }

                    ParticlesUnmatched = NamesNotFound.Sum(h => ParticleImageNames[h]);
                    ParticlesMatched   = TableRelion.RowCount - ParticlesUnmatched;

                    #endregion

                    #region Create particles

                    Dictionary <string, string> ReverseMapping = new Dictionary <string, string>();
                    foreach (var source in UsedSources)
                    {
                        foreach (var pair in source.Files)
                        {
                            if (NamesGood.Contains(pair.Value))
                            {
                                ReverseMapping.Add(pair.Value, pair.Key);
                            }
                        }
                    }

                    List <int> ValidRows    = new List <int>(TableRelion.RowCount);
                    string[] ColumnMicNames = TableRelion.GetColumn("rlnMicrographName");
                    for (int r = 0; r < ColumnMicNames.Length; r++)
                    {
                        if (ReverseMapping.ContainsKey(ColumnMicNames[r]))
                        {
                            ValidRows.Add(r);
                        }
                    }
                    Star CleanRelion = TableRelion.CreateSubset(ValidRows);

                    int NParticles  = CleanRelion.RowCount;
                    bool IsTomogram = CleanRelion.HasColumn("rlnCoordinateZ");

                    float[] CoordinatesX = CleanRelion.GetColumn("rlnCoordinateX").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixCoords).ToArray();
                    float[] CoordinatesY = CleanRelion.GetColumn("rlnCoordinateY").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixCoords).ToArray();
                    float[] CoordinatesZ = IsTomogram ? CleanRelion.GetColumn("rlnCoordinateZ").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixCoords).ToArray() : new float[NParticles];

                    float[] OffsetsX = CleanRelion.HasColumn("rlnOriginX") ? CleanRelion.GetColumn("rlnOriginX").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixShifts).ToArray() : new float[NParticles];
                    float[] OffsetsY = CleanRelion.HasColumn("rlnOriginY") ? CleanRelion.GetColumn("rlnOriginY").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixShifts).ToArray() : new float[NParticles];
                    float[] OffsetsZ = CleanRelion.HasColumn("rlnOriginZ") ? CleanRelion.GetColumn("rlnOriginZ").Select(v => float.Parse(v, CultureInfo.InvariantCulture) * AngPixShifts).ToArray() : new float[NParticles];

                    float3[] Coordinates = Helper.ArrayOfFunction(p => new float3(CoordinatesX[p] - OffsetsX[p], CoordinatesY[p] - OffsetsY[p], CoordinatesZ[p] - OffsetsZ[p]), NParticles);

                    float[] AnglesRot  = CleanRelion.GetColumn("rlnAngleRot").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] AnglesTilt = CleanRelion.GetColumn("rlnAngleTilt").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] AnglesPsi  = CleanRelion.GetColumn("rlnAnglePsi").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();

                    float3[] Angles = Helper.ArrayOfFunction(p => new float3(AnglesRot[p], AnglesTilt[p], AnglesPsi[p]), NParticles);

                    int[] Subsets = CleanRelion.HasColumn("rlnRandomSubset") ? CleanRelion.GetColumn("rlnRandomSubset").Select(v => int.Parse(v, CultureInfo.InvariantCulture) - 1).ToArray() : Helper.ArrayOfFunction(i => i % 2, NParticles);

                    string[] MicrographNames  = CleanRelion.GetColumn("rlnMicrographName").ToArray();
                    string[] MicrographHashes = MicrographNames.Select(v => ReverseMapping[v]).ToArray();

                    ParticlesNew = Helper.ArrayOfFunction(p => new Particle(new[] { Coordinates[p] }, new[] { Angles[p] }, Subsets[p], MicrographNames[p], MicrographHashes[p]), NParticles);
                    foreach (var particle in ParticlesNew)
                    {
                        particle.ResampleCoordinates(ResMov);
                        particle.ResampleAngles(ResRot);
                    }

                    #endregion
                }
                else
                {
                    ParticlesMatched   = 0;
                    ParticlesUnmatched = 0;
                }

                #region Match particles between old and new set, compute histogram

                if (ParticlesNew != null && ParticlesNew.Length > 0)
                {
                    bool[] MatchFoundOld = new bool[ParticlesOld.Length];
                    bool[] MatchFoundNew = new bool[ParticlesNew.Length];
                    ClosestDistanceOld   = Helper.ArrayOfConstant(-1f, ParticlesOld.Length);
                    ClosestDistanceNew   = Helper.ArrayOfConstant(-1f, ParticlesNew.Length);

                    Dictionary <string, (List <Particle>, List <int>)> GroupedOld = new Dictionary <string, (List <Particle>, List <int>)>();
                    Dictionary <string, (List <Particle>, List <int>)> GroupedNew = new Dictionary <string, (List <Particle>, List <int>)>();

                    for (int i = 0; i < ParticlesOld.Length; i++)
                    {
                        string Hash = ParticlesOld[i].SourceHash;
                        if (!GroupedOld.ContainsKey(Hash))
                        {
                            GroupedOld.Add(Hash, (new List <Particle>(), new List <int>()));
                        }
                        GroupedOld[Hash].Item1.Add(ParticlesOld[i]);
                        GroupedOld[Hash].Item2.Add(i);
                    }

                    for (int i = 0; i < ParticlesNew.Length; i++)
                    {
                        string Hash = ParticlesNew[i].SourceHash;
                        if (!GroupedNew.ContainsKey(Hash))
                        {
                            GroupedNew.Add(Hash, (new List <Particle>(), new List <int>()));
                        }
                        GroupedNew[Hash].Item1.Add(ParticlesNew[i]);
                        GroupedNew[Hash].Item2.Add(i);
                    }

                    Parallel.ForEach(GroupedNew, pair =>
                    {
                        if (!GroupedOld.ContainsKey(pair.Key))
                        {
                            return;
                        }

                        List <Particle> Old = GroupedOld[pair.Key].Item1;
                        List <int> OldIDs   = GroupedOld[pair.Key].Item2;
                        List <Particle> New = pair.Value.Item1;
                        List <int> NewIDs   = pair.Value.Item2;

                        float[][] DistanceMatrix = new float[Old.Count][];

                        for (int i1 = 0; i1 < Old.Count; i1++)
                        {
                            float BestDistance = float.MaxValue;
                            int BestID         = -1;
                            float3 P1          = Old[i1].Coordinates[0];

                            DistanceMatrix[i1] = new float[New.Count];

                            for (int i2 = 0; i2 < New.Count; i2++)
                            {
                                float3 P2       = New[i2].Coordinates[0];
                                float Distance2 = (P2 - P1).Length();

                                DistanceMatrix[i1][i2] = Distance2;

                                //if (Distance2 < BestDistance)
                                //{
                                //    BestDistance = Distance2;
                                //    BestID = OldIDs[i2];
                                //}
                            }

                            //BestDistance = (float)Math.Sqrt(BestDistance);

                            //if (BestID >= 0)
                            //{
                            //    ClosestDistanceOld[BestID] = BestDistance;
                            //    ClosestDistanceNew[NewIDs[i1]] = BestDistance;
                            //}
                        }

                        HungarianAlgorithm Bla = new HungarianAlgorithm(DistanceMatrix);
                        int[] Matching         = Bla.execute();

                        for (int i1 = 0; i1 < Matching.Length; i1++)
                        {
                            if (Matching[i1] < 0)
                            {
                                continue;
                            }

                            int i2 = Matching[i1];
                            ClosestDistanceOld[OldIDs[i1]] = DistanceMatrix[i1][i2];
                            ClosestDistanceNew[NewIDs[i2]] = DistanceMatrix[i1][i2];
                        }
                    });

                    IEnumerable <float> ValidDistances = ClosestDistanceNew.Where(v => v >= 0);
                    MaxDistance = Math.Max(1, MathHelper.Max(ValidDistances));

                    float[] HistogramBins = new float[NBins];
                    foreach (var d in ValidDistances)
                    {
                        HistogramBins[(int)Math.Round(d / MaxDistance * (NBins - 1))]++;
                    }

                    double HistogramWidth = 400;
                    //Dispatcher.Invoke(() => HistogramWidth = PanelDistanceOptions.ActualWidth - 1);

                    float MaxBin  = Math.Max(1, MathHelper.Max(HistogramBins));
                    HistogramBins = HistogramBins.Select(v => v / MaxBin).ToArray();

                    PauseSetUpdates = true;

                    Dispatcher.Invoke(() =>
                    {
                        PointCollection HistogramPoints = new PointCollection();

                        HistogramPoints.Add(new Point(0, 30));
                        for (int i = 0; i < NBins; i++)
                        {
                            double X = (double)i / (NBins - 1) * HistogramWidth;
                            double Y = (1 - HistogramBins[i]) * 30;
                            HistogramPoints.Add(new Point(X, Y));
                        }
                        HistogramPoints.Add(new Point(HistogramWidth, 30));

                        SliderToleranceDistance.MaxValue = (decimal)MaxDistance;
                        ToleranceDistance = Math.Min((decimal)MaxDistance, ToleranceDistance);

                        PolygonHistogramGreen.Points = HistogramPoints;
                        PolygonHistogramGray.Points  = HistogramPoints;

                        TextMaxDistance.Text = $"{MaxDistance:F1} Å";
                    });

                    PauseSetUpdates = false;
                }

                #endregion
            });

            ProgressParticles.Visibility = Visibility.Hidden;

            TextParticlesResult.Text = $"{ParticlesMatched}/{ParticlesMatched + ParticlesUnmatched} new particles matched to available data sources";

            UpdateSets();

            Revalidate();
        }