Example #1
0
 public void TomoExportParticles(string path, ProcessingOptionsTomoSubReconstruction options, float3[] coordinates, float3[] angles)
 {
     if (!SendCommand(new NamedSerializableObject("TomoExportParticles",
                                                  path,
                                                  options,
                                                  coordinates,
                                                  angles)))
     {
         throw new Exception("Couldn't export the particles!");
     }
 }
Example #2
0
        static void Main(string[] args)
        {
            CultureInfo.DefaultThreadCurrentCulture   = CultureInfo.InvariantCulture;
            CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;

            //if (!Debugger.IsAttached)
            //    Debugger.Launch();

            if (args.Length < 3)
            {
                return;
            }

            DeviceID = int.Parse(args[0]) % GPU.GetDeviceCount();
            PipeName = args[1];
            bool DebugMode = bool.Parse(args[2]);

            GPU.SetDevice(DeviceID);

            Console.WriteLine($"Running on GPU #{DeviceID} ({GPU.GetFreeMemory(DeviceID)} MB free) through {PipeName}\n");

            Formatter = new BinaryFormatter();

            Heartbeat = new Thread(new ThreadStart(() =>
            {
                if (!DebugMode)
                {
                    while (true)
                    {
                        try
                        {
                            NamedPipeClientStream PipeHeartbeat = new NamedPipeClientStream(".", PipeName + "_heartbeat", PipeDirection.In);
                            PipeHeartbeat.Connect(5000);

                            PipeHeartbeat.Dispose();
                        }
                        catch
                        {
                            if (!Terminating)
                            {
                                Process.GetCurrentProcess().Kill();
                            }
                        }
                    }
                }
            }));
            Heartbeat.Start();

            while (true)
            {
                PipeReceive = new NamedPipeClientStream(".", PipeName + "_out", PipeDirection.In);
                PipeReceive.Connect();

                NamedSerializableObject Command = (NamedSerializableObject)Formatter.Deserialize(PipeReceive);

                PipeReceive.Dispose();

                Console.WriteLine($"Received \"{Command.Name}\", with {Command.Content.Length} arguments, for GPU #{GPU.GetDevice()}, {GPU.GetFreeMemory(DeviceID)} MB free");

                try
                {
                    Stopwatch Watch = new Stopwatch();
                    Watch.Start();

                    if (Command.Name == "Exit")
                    {
                        Movie.WriteAverageAsync?.Wait();

                        SendSuccessStatus(true);
                        Process.GetCurrentProcess().Kill();

                        return;
                    }
                    else if (Command.Name == "Ping")
                    {
                        Console.WriteLine("Ping!");
                    }
                    else if (Command.Name == "SetHeaderlessParams")
                    {
                        HeaderlessDims   = (int2)Command.Content[0];
                        HeaderlessOffset = (long)Command.Content[1];
                        HeaderlessType   = (string)Command.Content[2];

                        Console.WriteLine($"Set headerless parameters to {HeaderlessDims}, {HeaderlessOffset}, {HeaderlessType}");
                    }
                    else if (Command.Name == "LoadGainRef")
                    {
                        GainRef?.Dispose();
                        DefectMap?.Dispose();

                        string GainPath    = (string)Command.Content[0];
                        bool   FlipX       = (bool)Command.Content[1];
                        bool   FlipY       = (bool)Command.Content[2];
                        bool   Transpose   = (bool)Command.Content[3];
                        string DefectsPath = (string)Command.Content[4];

                        if (!string.IsNullOrEmpty(GainPath))
                        {
                            GainRef = LoadAndPrepareGainReference(GainPath, FlipX, FlipY, Transpose);
                        }
                        if (!string.IsNullOrEmpty(DefectsPath))
                        {
                            DefectMap = LoadAndPrepareDefectMap(DefectsPath, FlipX, FlipY, Transpose);
                        }

                        Console.WriteLine($"Loaded gain reference and defect map: {GainRef}, {FlipX}, {FlipY}, {Transpose}, {DefectsPath}");
                    }
                    else if (Command.Name == "LoadStack")
                    {
                        OriginalStack?.Dispose();

                        string  Path           = (string)Command.Content[0];
                        decimal ScaleFactor    = (decimal)Command.Content[1];
                        int     EERGroupFrames = (int)Command.Content[2];

                        HeaderEER.GroupNFrames = EERGroupFrames;

                        OriginalStack      = LoadAndPrepareStack(Path, ScaleFactor);
                        OriginalStackOwner = Helper.PathToNameWithExtension(Path);

                        Console.WriteLine($"Loaded stack: {OriginalStack}, {ScaleFactor}");
                    }
                    else if (Command.Name == "MovieProcessCTF")
                    {
                        string Path = (string)Command.Content[0];
                        ProcessingOptionsMovieCTF Options = (ProcessingOptionsMovieCTF)Command.Content[1];

                        if (Helper.PathToNameWithExtension(Path) != OriginalStackOwner)
                        {
                            throw new Exception("Currently loaded stack doesn't match the movie requested for processing!");
                        }

                        Movie M = new Movie(Path);
                        M.ProcessCTF(OriginalStack, Options);
                        M.SaveMeta();

                        Console.WriteLine($"Processed CTF for {Path}");
                    }
                    else if (Command.Name == "MovieProcessMovement")
                    {
                        string Path = (string)Command.Content[0];
                        ProcessingOptionsMovieMovement Options = (ProcessingOptionsMovieMovement)Command.Content[1];

                        if (Helper.PathToNameWithExtension(Path) != OriginalStackOwner)
                        {
                            throw new Exception("Currently loaded stack doesn't match the movie requested for processing!");
                        }

                        Movie M = new Movie(Path);
                        M.ProcessShift(OriginalStack, Options);
                        M.SaveMeta();

                        Console.WriteLine($"Processed movement for {Path}");
                    }
                    else if (Command.Name == "MovieExportMovie")
                    {
                        string Path = (string)Command.Content[0];
                        ProcessingOptionsMovieExport Options = (ProcessingOptionsMovieExport)Command.Content[1];

                        if (Helper.PathToNameWithExtension(Path) != OriginalStackOwner)
                        {
                            throw new Exception("Currently loaded stack doesn't match the movie requested for processing!");
                        }

                        Movie M = new Movie(Path);
                        M.ExportMovie(OriginalStack, Options);
                        M.SaveMeta();

                        Console.WriteLine($"Exported movie for {Path}");
                    }
                    else if (Command.Name == "MovieExportParticles")
                    {
                        string Path = (string)Command.Content[0];
                        ProcessingOptionsParticlesExport Options = (ProcessingOptionsParticlesExport)Command.Content[1];
                        float2[] Coordinates = (float2[])Command.Content[2];

                        if (Helper.PathToNameWithExtension(Path) != OriginalStackOwner)
                        {
                            throw new Exception("Currently loaded stack doesn't match the movie requested for processing!");
                        }

                        Movie M = new Movie(Path);
                        M.ExportParticles(OriginalStack, Coordinates, Options);
                        M.SaveMeta();

                        Console.WriteLine($"Exported {Coordinates.Length} particles for {Path}");
                    }
                    else if (Command.Name == "TomoProcessCTF")
                    {
                        string Path = (string)Command.Content[0];
                        ProcessingOptionsMovieCTF Options = (ProcessingOptionsMovieCTF)Command.Content[1];

                        TiltSeries T = new TiltSeries(Path);
                        T.ProcessCTFSimultaneous(Options);
                        T.SaveMeta();

                        Console.WriteLine($"Processed CTF for {Path}");
                    }
                    else if (Command.Name == "TomoExportParticles")
                    {
                        string Path = (string)Command.Content[0];
                        ProcessingOptionsTomoSubReconstruction Options = (ProcessingOptionsTomoSubReconstruction)Command.Content[1];
                        float3[] Coordinates = (float3[])Command.Content[2];
                        float3[] Angles      = Command.Content[3] != null ? (float3[])Command.Content[3] : null;

                        TiltSeries T = new TiltSeries(Path);
                        T.ReconstructSubtomos(Options, Coordinates, Angles);
                        T.SaveMeta();

                        Console.WriteLine($"Exported {Coordinates.Length} particles for {Path}");
                    }
                    else if (Command.Name == "MPAPreparePopulation")
                    {
                        string Path = (string)Command.Content[0];

                        MPAPopulation = new Population(Path);

                        foreach (var species in MPAPopulation.Species)
                        {
                            Console.Write($"Preparing {species.Name} for refinement... ");

                            species.PrepareRefinementRequisites(true, DeviceID);

                            Console.WriteLine("Done.");
                        }
                    }
                    else if (Command.Name == "MPARefine")
                    {
                        string Path                        = (string)Command.Content[0];
                        string WorkingDirectory            = (string)Command.Content[1];
                        string LogPath                     = (string)Command.Content[2];
                        ProcessingOptionsMPARefine Options = (ProcessingOptionsMPARefine)Command.Content[3];
                        DataSource Source                  = (DataSource)Command.Content[4];

                        Movie Item = null;

                        if (Helper.PathToExtension(Path).ToLower() == ".tomostar")
                        {
                            Item = new TiltSeries(Path);
                        }
                        else
                        {
                            Item = new Movie(Path);
                        }

                        GPU.SetDevice(DeviceID);

                        Item.PerformMultiParticleRefinement(WorkingDirectory, Options, MPAPopulation.Species.ToArray(), Source, GainRef, DefectMap, (message) =>
                        {
                            Console.WriteLine(message);

                            bool Success = false;
                            int Tries    = 0;
                            while (!Success && Tries < 10)
                            {
                                try
                                {
                                    using (TextWriter Writer = File.AppendText(LogPath))
                                        Writer.WriteLine(message);
                                    Success = true;
                                }
                                catch
                                {
                                    Thread.Sleep(100);
                                    Tries++;
                                }
                            }
                        });

                        Item.SaveMeta();

                        GPU.CheckGPUExceptions();

                        Console.WriteLine($"Finished refining {Item.Name}");
                    }
                    else if (Command.Name == "MPASaveProgress")
                    {
                        string Path = (string)Command.Content[0];

                        MPAPopulation.SaveRefinementProgress(Path);
                    }
                    else if (Command.Name == "TryAllocatePinnedMemory")
                    {
                        long[]   ChunkSizes = (long[])Command.Content[0];
                        IntPtr[] Chunks     = new IntPtr[ChunkSizes.Length];

                        for (int i = 0; i < ChunkSizes.Length; i++)
                        {
                            Chunks[i] = GPU.MallocHostPinned(ChunkSizes[i] / sizeof(float));
                            //Dummies.Add(Helper.ArrayOfSequence(0, (int)(ChunkSizes[i] / sizeof(float) / 2), 1));
                        }

                        GPU.CheckGPUExceptions();

                        //for (int i = 0; i < ChunkSizes.Length; i++)
                        //    GPU.FreeHostPinned(Chunks[i]);

                        Console.WriteLine($"Successfully allocated {ChunkSizes.Sum()} bytes of pinned memory");
                    }

                    Watch.Stop();
                    Console.WriteLine((Watch.ElapsedMilliseconds / 1000f).ToString("F3"));

                    Console.WriteLine("");

                    SendSuccessStatus(true);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());

                    File.WriteAllText($"worker_{DeviceID}_crash.txt", e.ToString());

                    //Console.Read();

                    SendSuccessStatus(false);
                }
            }
        }
        private async void ButtonWrite_OnClick(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.SaveFileDialog SaveDialog = new System.Windows.Forms.SaveFileDialog
            {
                Filter = "STAR Files|*.star"
            };
            System.Windows.Forms.DialogResult ResultSave = SaveDialog.ShowDialog();

            if (ResultSave.ToString() == "OK")
            {
                ExportPath = SaveDialog.FileName;
            }
            else
            {
                return;
            }

            decimal _OutputAngPix = OutputAngPix;
            int     _BoxSize      = BoxSize;
            int     _Diameter     = Diameter;

            bool _ReconstructVolume = ReconstructVolume;
            bool _ReconstructSeries = ReconstructSeries;

            bool    _ApplyShift = ApplyShift;
            decimal _ShiftX     = ShiftX;
            decimal _ShiftY     = ShiftY;
            decimal _ShiftZ     = ShiftZ;

            bool _ReconstructPrerotated  = ReconstructPrerotated;
            bool _ReconstructDoLimitDose = ReconstructDoLimitDose;
            int  _ReconstructNTilts      = ReconstructNTilts;

            bool _InputInvert           = InputInvert;
            bool _InputNormalize        = InputNormalize;
            bool _OutputNormalize       = OutputNormalize;
            bool _ReconstructMakeSparse = ReconstructMakeSparse;

            if (!_ReconstructVolume)
            {
                _ReconstructPrerotated = true;
            }

            float3 AdditionalShiftAngstrom = new float3((float)_ShiftX,
                                                        (float)_ShiftY,
                                                        (float)_ShiftZ);

            ProgressWrite.Visibility      = Visibility.Visible;
            ProgressWrite.IsIndeterminate = true;
            PanelButtons.Visibility       = Visibility.Collapsed;
            PanelRemaining.Visibility     = Visibility.Visible;

            foreach (var element in DisableWhileProcessing)
            {
                element.IsEnabled = false;
            }

            MainWindow MainWindow = (MainWindow)Application.Current.MainWindow;

            await Task.Run(() =>
            {
                List <Star> AllSourceTables = new List <Star>();
                string[] AllSourceHashes    = Helper.GetUniqueElements(Species.Particles.Select(p => p.SourceHash)).ToArray();
                AllSourceHashes             = AllSourceHashes.Where(h => Sources.Any(s => s.Files.ContainsKey(h))).ToArray();

                foreach (var source in Sources)
                {
                    if (!source.IsTiltSeries)
                    {
                        continue;
                    }

                    #region Get all movies that can potentially be used

                    List <string> ValidSourceHashes = AllSourceHashes.Where(h => source.Files.ContainsKey(h)).ToList();
                    List <string> ValidSourcePaths  = ValidSourceHashes.Select(k => System.IO.Path.Combine(source.FolderPath, source.Files[k])).ToList();
                    List <string> ValidMovieNames   = ValidSourcePaths.Select(p => Helper.PathToName(p)).ToList();
                    List <TiltSeries> ValidSeries   = ValidSourcePaths.Select(p => new TiltSeries(p)).ToList();

                    List <Particle> Particles = Species.Particles.Where(p => ValidSourceHashes.Contains(p.SourceHash)).ToList();
                    Particles.Sort((a, b) => a.SourceHash.CompareTo(b.SourceHash));

                    #endregion

                    if (IsCanceled)
                    {
                        return;
                    }

                    #region Create worker processes

                    int NDevices                   = GPU.GetDeviceCount();
                    List <int> UsedDevices         = MainWindow.GetDeviceList();
                    List <int> UsedDeviceProcesses = Helper.Combine(Helper.ArrayOfFunction(i => UsedDevices.Select(d => d + i *NDevices).ToArray(), 1)).ToList();

                    WorkerWrapper[] Workers = new WorkerWrapper[GPU.GetDeviceCount() * 1];
                    foreach (var gpuID in UsedDeviceProcesses)
                    {
                        Workers[gpuID] = new WorkerWrapper(gpuID);
                        Workers[gpuID].SetHeaderlessParams(new int2(1, 1),
                                                           0,
                                                           "float32");

                        Workers[gpuID].LoadGainRef(source.GainPath,
                                                   source.GainFlipX,
                                                   source.GainFlipY,
                                                   source.GainTranspose,
                                                   source.DefectsPath);
                    }

                    #endregion

                    List <Star> SourceTables = new List <Star>();

                    {
                        Dispatcher.Invoke(() => ProgressWrite.MaxValue = AllSourceHashes.Length);

                        Helper.ForEachGPU(ValidSeries, (series, gpuID) =>
                        {
                            if (IsCanceled)
                            {
                                return;
                            }

                            Stopwatch ItemTime = new Stopwatch();
                            ItemTime.Start();

                            string SeriesHash = series.GetDataHash();

                            Star TableOut = new Star(new string[] { "rlnMagnification",
                                                                    "rlnDetectorPixelSize",
                                                                    "rlnCoordinateX",
                                                                    "rlnCoordinateY",
                                                                    "rlnCoordinateZ",
                                                                    "rlnAngleRot",
                                                                    "rlnAngleTilt",
                                                                    "rlnAnglePsi",
                                                                    "rlnImageName",
                                                                    "rlnCtfImage",
                                                                    "rlnRandomSubset" });

                            ProcessingOptionsTomoSubReconstruction ExportOptions = new ProcessingOptionsTomoSubReconstruction()
                            {
                                PixelSizeX     = source.PixelSizeX,
                                PixelSizeY     = source.PixelSizeY,
                                PixelSizeAngle = source.PixelSizeAngle,

                                BinTimes      = (decimal)Math.Log((double)(_OutputAngPix / source.PixelSizeMean), 2.0),
                                GainPath      = source.GainPath,
                                DefectsPath   = source.DefectsPath,
                                GainFlipX     = source.GainFlipX,
                                GainFlipY     = source.GainFlipY,
                                GainTranspose = source.GainTranspose,

                                Dimensions = new float3((float)source.DimensionsX,
                                                        (float)source.DimensionsY,
                                                        (float)source.DimensionsZ),

                                Suffix = "_" + Species.NameSafe,

                                BoxSize          = _BoxSize,
                                ParticleDiameter = _Diameter,

                                Invert          = _InputInvert,
                                NormalizeInput  = _InputNormalize,
                                NormalizeOutput = _OutputNormalize,

                                PrerotateParticles = _ReconstructPrerotated,
                                DoLimitDose        = _ReconstructDoLimitDose,
                                NTilts             = Math.Min(series.NTilts, Math.Min(source.FrameLimit, _ReconstructNTilts)),

                                MakeSparse = _ReconstructMakeSparse
                            };

                            Particle[] SeriesParticles = Particles.Where(p => p.SourceHash == SeriesHash).ToArray();
                            int NParticles             = SeriesParticles.Length;

                            #region Process particle positions and angles

                            float3[] Positions         = new float3[NParticles *series.NTilts];
                            float3[] Angles            = new float3[NParticles *series.NTilts];
                            int[] Subsets              = SeriesParticles.Select(p => p.RandomSubset + 1).ToArray();
                            float MinDose              = MathHelper.Min(series.Dose);
                            float MaxDose              = MathHelper.Max(series.Dose);
                            float[] InterpolationSteps = Helper.ArrayOfFunction(i => (series.Dose[i] - MinDose) / (MaxDose - MinDose), series.NTilts);

                            for (int p = 0; p < NParticles; p++)
                            {
                                float3[] ParticlePositions = SeriesParticles[p].GetCoordinateSeries(InterpolationSteps);
                                float3[] ParticleAngles    = SeriesParticles[p].GetAngleSeries(InterpolationSteps);

                                if (_ApplyShift)
                                {
                                    Matrix3 R0          = Matrix3.Euler(ParticleAngles[0] * Helper.ToRad);
                                    float3 RotatedShift = R0 *AdditionalShiftAngstrom;

                                    for (int t = 0; t < ParticlePositions.Length; t++)
                                    {
                                        ParticlePositions[t] += RotatedShift;
                                    }
                                }

                                if (!_ReconstructPrerotated)
                                {
                                    Matrix3 R0I = Matrix3.Euler(ParticleAngles[0] * Helper.ToRad).Transposed();

                                    for (int t = 0; t < ParticleAngles.Length; t++)
                                    {
                                        ParticleAngles[t] = Matrix3.EulerFromMatrix(R0I *Matrix3.Euler(ParticleAngles[t] * Helper.ToRad)) * Helper.ToDeg;
                                    }
                                }

                                for (int t = 0; t < series.NTilts; t++)
                                {
                                    Positions[p *series.NTilts + t] = ParticlePositions[t];
                                    Angles[p *series.NTilts + t]    = ParticleAngles[t];
                                }

                                string PathSubtomo = series.SubtomoDir + $"{series.RootName}{ExportOptions.Suffix}_{p:D7}_{ExportOptions.BinnedPixelSizeMean:F2}A.mrc";
                                string PathCTF     = (series.SubtomoDir + $"{series.RootName}{ExportOptions.Suffix}_{p:D7}_ctf_{ExportOptions.BinnedPixelSizeMean:F2}A.mrc");

                                Uri UriStar = new Uri(ExportPath);
                                PathSubtomo = UriStar.MakeRelativeUri(new Uri(PathSubtomo)).ToString();
                                PathCTF     = UriStar.MakeRelativeUri(new Uri(PathCTF)).ToString();

                                TableOut.AddRow(new List <string>()
                                {
                                    "10000.0",
                                    ExportOptions.BinnedPixelSizeMean.ToString("F5", CultureInfo.InvariantCulture),
                                    (ParticlePositions[0].X / (float)ExportOptions.BinnedPixelSizeMean).ToString("F5", CultureInfo.InvariantCulture),
                                    (ParticlePositions[0].Y / (float)ExportOptions.BinnedPixelSizeMean).ToString("F5", CultureInfo.InvariantCulture),
                                    (ParticlePositions[0].Z / (float)ExportOptions.BinnedPixelSizeMean).ToString("F5", CultureInfo.InvariantCulture),
                                    (_ReconstructPrerotated ? 0 : SeriesParticles[p].Angles[0].X).ToString("F5", CultureInfo.InvariantCulture),
                                    (_ReconstructPrerotated ? 0 : SeriesParticles[p].Angles[0].Y).ToString("F5", CultureInfo.InvariantCulture),
                                    (_ReconstructPrerotated ? 0 : SeriesParticles[p].Angles[0].Z).ToString("F5", CultureInfo.InvariantCulture),
                                    PathSubtomo,
                                    PathCTF,
                                    Subsets[p].ToString()
                                });
                            }

                            #endregion

                            #region Finally, reconstruct the actual sub-tomos

                            if (_ReconstructVolume)
                            {
                                Workers[gpuID].TomoExportParticles(series.Path, ExportOptions, Positions, Angles);
                            }
                            else
                            {
                                series.ReconstructParticleSeries(ExportOptions, Positions, Angles, Subsets, ExportPath, out TableOut);
                            }

                            lock (AllSourceTables)
                                AllSourceTables.Add(TableOut);

                            #endregion

                            #region Add this micrograph's table to global collection, update remaining time estimate

                            lock (AllSourceTables)
                            {
                                Timings.Add(ItemTime.ElapsedMilliseconds / (float)UsedDeviceProcesses.Count);

                                int MsRemaining        = (int)(MathHelper.Mean(Timings) * (AllSourceHashes.Length - AllSourceTables.Count));
                                TimeSpan SpanRemaining = new TimeSpan(0, 0, 0, 0, MsRemaining);

                                Dispatcher.Invoke(() => TextRemaining.Text = SpanRemaining.ToString((int)SpanRemaining.TotalHours > 0 ? @"hh\:mm\:ss" : @"mm\:ss"));

                                Dispatcher.Invoke(() =>
                                {
                                    ProgressWrite.IsIndeterminate = false;
                                    ProgressWrite.Value           = AllSourceTables.Count;
                                });
                            }

                            #endregion
                        }, 1, UsedDeviceProcesses);
                    }

                    Thread.Sleep(10000);    // Writing out particles is async, so if workers are killed immediately they may not write out everything

                    foreach (var worker in Workers)
                    {
                        worker?.Dispose();
                    }

                    if (IsCanceled)
                    {
                        return;
                    }
                }

                if (AllSourceTables.Count > 0)
                {
                    (new Star(AllSourceTables.ToArray())).Save(ExportPath);
                }
            });

            Close?.Invoke();
        }
Example #4
0
        private async void ButtonWrite_OnClick(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.SaveFileDialog SaveDialog = new System.Windows.Forms.SaveFileDialog
            {
                Filter = "STAR Files|*.star"
            };
            System.Windows.Forms.DialogResult ResultSave = SaveDialog.ShowDialog();

            if (ResultSave.ToString() == "OK")
            {
                ExportPath = SaveDialog.FileName;
            }
            else
            {
                return;
            }

            bool Invert    = (bool)CheckInvert.IsChecked;
            bool Normalize = (bool)CheckNormalize.IsChecked;
            bool Preflip   = (bool)CheckPreflip.IsChecked;

            bool Relative = (bool)CheckRelative.IsChecked;

            bool Filter = (bool)CheckFilter.IsChecked;
            bool Manual = (bool)CheckManual.IsChecked;

            int BoxSize      = (int)Options.Tasks.Export2DBoxSize;
            int NormDiameter = (int)Options.Tasks.Export2DParticleDiameter;

            bool DoVolumes = (bool)RadioVolume.IsChecked;

            if (!DoVolumes)
            {
                Options.Tasks.TomoSubReconstructPrerotated = true;
            }

            bool MakeSparse = (bool)CheckSparse.IsChecked;

            float3 AdditionalShiftAngstrom = (bool)CheckShiftParticles.IsChecked ?
                                             new float3((float)SliderShiftParticlesX.Value,
                                                        (float)SliderShiftParticlesY.Value,
                                                        (float)SliderShiftParticlesZ.Value) :
                                             new float3(0);

            ProgressWrite.Visibility      = Visibility.Visible;
            ProgressWrite.IsIndeterminate = true;
            PanelButtons.Visibility       = Visibility.Collapsed;
            PanelRemaining.Visibility     = Visibility.Visible;

            foreach (var element in DisableWhileProcessing)
            {
                element.IsEnabled = false;
            }

            await Task.Run(() =>
            {
                #region Get all movies that can potentially be used

                List <TiltSeries> ValidSeries = Series.Where(v =>
                {
                    if (!Filter && v.UnselectFilter && v.UnselectManual == null)
                    {
                        return(false);
                    }
                    if (!Manual && v.UnselectManual != null && (bool)v.UnselectManual)
                    {
                        return(false);
                    }
                    if (v.OptionsCTF == null)
                    {
                        return(false);
                    }
                    return(true);
                }).ToList();
                List <string> ValidMovieNames = ValidSeries.Select(m => m.RootName).ToList();

                #endregion

                #region Read table and intersect its micrograph set with valid movies

                Star TableIn;

                if (Options.Tasks.InputOnePerItem)
                {
                    List <Star> Tables = new List <Star>();
                    foreach (var item in Series)
                    {
                        string StarPath = InputFolder + item.RootName + InputSuffix;
                        if (File.Exists(StarPath))
                        {
                            Star TableItem = new Star(StarPath);
                            if (!TableItem.HasColumn("rlnMicrographName"))
                            {
                                TableItem.AddColumn("rlnMicrographName", item.Name);
                            }
                            else
                            {
                                TableItem.SetColumn("rlnMicrographName", Helper.ArrayOfConstant(item.Name, TableItem.RowCount));
                            }

                            Tables.Add(TableItem);
                        }
                    }

                    TableIn = new Star(Tables.ToArray());
                }
                else
                {
                    TableIn = new Star(ImportPath);
                }

                if (!TableIn.HasColumn("rlnMicrographName"))
                {
                    throw new Exception("Couldn't find rlnMicrographName column.");
                }
                if (!TableIn.HasColumn("rlnCoordinateX"))
                {
                    throw new Exception("Couldn't find rlnCoordinateX column.");
                }
                if (!TableIn.HasColumn("rlnCoordinateY"))
                {
                    throw new Exception("Couldn't find rlnCoordinateY column.");
                }
                if (!TableIn.HasColumn("rlnCoordinateZ"))
                {
                    throw new Exception("Couldn't find rlnCoordinateZ column.");
                }

                Dictionary <string, List <int> > Groups = new Dictionary <string, List <int> >();
                {
                    string[] ColumnMicNames = TableIn.GetColumn("rlnMicrographName");
                    for (int r = 0; r < ColumnMicNames.Length; r++)
                    {
                        if (!Groups.ContainsKey(ColumnMicNames[r]))
                        {
                            Groups.Add(ColumnMicNames[r], new List <int>());
                        }
                        Groups[ColumnMicNames[r]].Add(r);
                    }
                    Groups = Groups.ToDictionary(group => Helper.PathToName(group.Key), group => group.Value);

                    Groups = Groups.Where(group => ValidMovieNames.Any(n => group.Key.Contains(n))).ToDictionary(group => group.Key, group => group.Value);
                }

                bool[] RowsIncluded = new bool[TableIn.RowCount];
                foreach (var group in Groups)
                {
                    foreach (var r in group.Value)
                    {
                        RowsIncluded[r] = true;
                    }
                }
                List <int> RowsNotIncluded = new List <int>();
                for (int r = 0; r < RowsIncluded.Length; r++)
                {
                    if (!RowsIncluded[r])
                    {
                        RowsNotIncluded.Add(r);
                    }
                }

                ValidSeries = ValidSeries.Where(v => Groups.Any(n => n.Key.Contains(v.RootName))).ToList();

                if (ValidSeries.Count == 0)     // Exit if there is nothing to export, otherwise errors will be thrown below
                {
                    return;
                }

                #endregion

                #region Make sure all columns are there

                if (!TableIn.HasColumn("rlnMagnification"))
                {
                    TableIn.AddColumn("rlnMagnification", "10000.0");
                }
                else
                {
                    TableIn.SetColumn("rlnMagnification", Helper.ArrayOfConstant("10000.0", TableIn.RowCount));
                }

                if (!TableIn.HasColumn("rlnDetectorPixelSize"))
                {
                    TableIn.AddColumn("rlnDetectorPixelSize", Options.Tasks.TomoSubReconstructPixel.ToString("F5", CultureInfo.InvariantCulture));
                }
                else
                {
                    TableIn.SetColumn("rlnDetectorPixelSize", Helper.ArrayOfConstant(Options.Tasks.TomoSubReconstructPixel.ToString("F5", CultureInfo.InvariantCulture), TableIn.RowCount));
                }

                if (!TableIn.HasColumn("rlnCtfMaxResolution"))
                {
                    TableIn.AddColumn("rlnCtfMaxResolution", "999.0");
                }

                if (!TableIn.HasColumn("rlnImageName"))
                {
                    TableIn.AddColumn("rlnImageName", "None");
                }

                if (!TableIn.HasColumn("rlnCtfImage"))
                {
                    TableIn.AddColumn("rlnCtfImage", "None");
                }

                List <Star> SeriesTablesOut = new List <Star>();

                #endregion

                if (IsCanceled)
                {
                    return;
                }

                #region Create worker processes

                int NDevices                   = GPU.GetDeviceCount();
                List <int> UsedDevices         = Options.MainWindow.GetDeviceList();
                List <int> UsedDeviceProcesses = Helper.Combine(Helper.ArrayOfFunction(i => UsedDevices.Select(d => d + i *NDevices).ToArray(), MainWindow.GlobalOptions.ProcessesPerDevice)).ToList();

                WorkerWrapper[] Workers = new WorkerWrapper[GPU.GetDeviceCount() * MainWindow.GlobalOptions.ProcessesPerDevice];
                foreach (var gpuID in UsedDeviceProcesses)
                {
                    Workers[gpuID] = new WorkerWrapper(gpuID);
                    Workers[gpuID].SetHeaderlessParams(new int2(Options.Import.HeaderlessWidth, Options.Import.HeaderlessHeight),
                                                       Options.Import.HeaderlessOffset,
                                                       Options.Import.HeaderlessType);

                    Workers[gpuID].LoadGainRef(Options.Import.CorrectGain ? Options.Import.GainPath : "",
                                               Options.Import.GainFlipX,
                                               Options.Import.GainFlipY,
                                               Options.Import.GainTranspose,
                                               Options.Import.CorrectDefects ? Options.Import.DefectsPath : "");
                }

                #endregion

                Star TableOut = null;
                {
                    Dictionary <string, Star> MicrographTables = new Dictionary <string, Star>();

                    #region Get coordinates and angles

                    float[] PosX   = TableIn.GetColumn("rlnCoordinateX").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] PosY   = TableIn.GetColumn("rlnCoordinateY").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] PosZ   = TableIn.GetColumn("rlnCoordinateZ").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] ShiftX = TableIn.HasColumn("rlnOriginX") ? TableIn.GetColumn("rlnOriginX").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];
                    float[] ShiftY = TableIn.HasColumn("rlnOriginY") ? TableIn.GetColumn("rlnOriginY").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];
                    float[] ShiftZ = TableIn.HasColumn("rlnOriginZ") ? TableIn.GetColumn("rlnOriginZ").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];


                    if (Options.Tasks.TomoSubReconstructNormalizedCoords)
                    {
                        for (int r = 0; r < TableIn.RowCount; r++)
                        {
                            PosX[r] *= (float)Options.Tomo.DimensionsX * (float)Options.PixelSizeMean;
                            PosY[r] *= (float)Options.Tomo.DimensionsY * (float)Options.PixelSizeMean;
                            PosZ[r] *= (float)Options.Tomo.DimensionsZ * (float)Options.PixelSizeMean;
                        }
                    }
                    else
                    {
                        for (int r = 0; r < TableIn.RowCount; r++)
                        {
                            PosX[r] = (PosX[r] - ShiftX[r]) * (float)Options.Tasks.InputPixelSize;
                            PosY[r] = (PosY[r] - ShiftY[r]) * (float)Options.Tasks.InputPixelSize;
                            PosZ[r] = (PosZ[r] - ShiftZ[r]) * (float)Options.Tasks.InputPixelSize;
                        }
                    }

                    float[] AngleRot  = TableIn.HasColumn("rlnAngleRot") && Options.Tasks.TomoSubReconstructPrerotated ? TableIn.GetColumn("rlnAngleRot").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];
                    float[] AngleTilt = TableIn.HasColumn("rlnAngleTilt") && Options.Tasks.TomoSubReconstructPrerotated ? TableIn.GetColumn("rlnAngleTilt").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];
                    float[] AnglePsi  = TableIn.HasColumn("rlnAnglePsi") && Options.Tasks.TomoSubReconstructPrerotated ? TableIn.GetColumn("rlnAnglePsi").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];

                    if (TableIn.HasColumn("rlnOriginX"))
                    {
                        TableIn.RemoveColumn("rlnOriginX");
                    }
                    if (TableIn.HasColumn("rlnOriginY"))
                    {
                        TableIn.RemoveColumn("rlnOriginY");
                    }
                    if (TableIn.HasColumn("rlnOriginZ"))
                    {
                        TableIn.RemoveColumn("rlnOriginZ");
                    }

                    if (AdditionalShiftAngstrom.Length() > 0)
                    {
                        for (int r = 0; r < TableIn.RowCount; r++)
                        {
                            Matrix3 R = Matrix3.Euler(AngleRot[r] * Helper.ToRad,
                                                      AngleTilt[r] * Helper.ToRad,
                                                      AnglePsi[r] * Helper.ToRad);
                            float3 RotatedShift = R *AdditionalShiftAngstrom;

                            PosX[r] += RotatedShift.X;
                            PosY[r] += RotatedShift.Y;
                            PosZ[r] += RotatedShift.Z;
                        }
                    }

                    if (Options.Tasks.TomoSubReconstructPrerotated)
                    {
                        if (TableIn.HasColumn("rlnAngleRot"))
                        {
                            TableIn.RemoveColumn("rlnAngleRot");
                            TableIn.AddColumn("rlnAngleRot", "0");
                        }
                        if (TableIn.HasColumn("rlnAngleTilt"))
                        {
                            TableIn.RemoveColumn("rlnAngleTilt");
                            TableIn.AddColumn("rlnAngleTilt", "0");
                        }
                        if (TableIn.HasColumn("rlnAnglePsi"))
                        {
                            TableIn.RemoveColumn("rlnAnglePsi");
                            TableIn.AddColumn("rlnAnglePsi", "0");
                        }
                    }

                    #endregion

                    Dispatcher.Invoke(() => ProgressWrite.MaxValue = ValidSeries.Count);


                    //Dispatcher.Invoke(() =>
                    //{
                    //    ProgressWrite.IsIndeterminate = true;
                    //    TextRemaining.Text = "?:??";
                    //});

                    Helper.ForEachGPU(ValidSeries, (series, gpuID) =>
                    {
                        if (IsCanceled)
                        {
                            return;
                        }

                        Stopwatch ItemTime = new Stopwatch();
                        ItemTime.Start();

                        ProcessingOptionsTomoSubReconstruction ExportOptions = Options.GetProcessingTomoSubReconstruction();

                        #region Update row values

                        List <int> GroupRows = Groups.First(n => n.Key.Contains(series.RootName)).Value;

                        int pi = 0;
                        foreach (var r in GroupRows)
                        {
                            TableIn.SetRowValue(r, "rlnCtfMaxResolution", series.CTFResolutionEstimate.ToString("F1", CultureInfo.InvariantCulture));

                            TableIn.SetRowValue(r, "rlnCoordinateX", (PosX[r] / (float)ExportOptions.BinnedPixelSizeMean).ToString("F3", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnCoordinateY", (PosY[r] / (float)ExportOptions.BinnedPixelSizeMean).ToString("F3", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnCoordinateZ", (PosZ[r] / (float)ExportOptions.BinnedPixelSizeMean).ToString("F3", CultureInfo.InvariantCulture));

                            #region Figure out relative or absolute path to sub-tomo and its CTF

                            string PathSubtomo = series.SubtomoDir + $"{series.RootName}{ExportOptions.Suffix}_{pi:D7}_{ExportOptions.BinnedPixelSizeMean:F2}A.mrc";
                            string PathCTF     = //MakeSparse ?
                                                 //(series.SubtomoDir + $"{series.RootName}_{pi:D7}_ctf_{ExportOptions.BinnedPixelSizeMean:F2}A.tif") :
                                                 (series.SubtomoDir + $"{series.RootName}{ExportOptions.Suffix}_{pi:D7}_ctf_{ExportOptions.BinnedPixelSizeMean:F2}A.mrc");
                            if (Relative)
                            {
                                Uri UriStar = new Uri(ExportPath);
                                PathSubtomo = UriStar.MakeRelativeUri(new Uri(PathSubtomo)).ToString();
                                PathCTF     = UriStar.MakeRelativeUri(new Uri(PathCTF)).ToString();
                            }

                            #endregion

                            TableIn.SetRowValue(r, "rlnImageName", PathSubtomo);
                            TableIn.SetRowValue(r, "rlnCtfImage", PathCTF);

                            pi++;
                        }

                        #endregion

                        #region Populate micrograph table with rows for all exported particles

                        Star MicrographTable = new Star(TableIn.GetColumnNames());

                        foreach (var r in GroupRows)
                        {
                            MicrographTable.AddRow(TableIn.GetRow(r).ToList());
                        }

                        #endregion

                        #region Finally, reconstruct the actual sub-tomos

                        float3[] TomoPositions = Helper.Combine(GroupRows.Select(r => Helper.ArrayOfConstant(new float3(PosX[r], PosY[r], PosZ[r]), series.NTilts)).ToArray());
                        float3[] TomoAngles    = Helper.Combine(GroupRows.Select(r => Helper.ArrayOfConstant(new float3(AngleRot[r], AngleTilt[r], AnglePsi[r]), series.NTilts)).ToArray());

                        if (DoVolumes)
                        {
                            Workers[gpuID].TomoExportParticles(series.Path, ExportOptions, TomoPositions, TomoAngles);
                            //series.ReconstructSubtomos(ExportOptions, TomoPositions, TomoAngles);

                            lock (MicrographTables)
                                MicrographTables.Add(series.RootName, MicrographTable);
                        }
                        else
                        {
                            Star SeriesTable;
                            Random Rand   = new Random(123);
                            int[] Subsets = Helper.ArrayOfFunction(i => Rand.Next(1, 3), GroupRows.Count);
                            series.ReconstructParticleSeries(ExportOptions, TomoPositions, TomoAngles, Subsets, ExportPath, out SeriesTable);

                            lock (MicrographTables)
                                MicrographTables.Add(series.RootName, SeriesTable);
                        }

                        #endregion

                        #region Add this micrograph's table to global collection, update remaining time estimate

                        lock (MicrographTables)
                        {
                            Timings.Add(ItemTime.ElapsedMilliseconds / (float)UsedDeviceProcesses.Count);

                            int MsRemaining        = (int)(MathHelper.Mean(Timings) * (ValidSeries.Count - MicrographTables.Count));
                            TimeSpan SpanRemaining = new TimeSpan(0, 0, 0, 0, MsRemaining);

                            Dispatcher.Invoke(() => TextRemaining.Text = SpanRemaining.ToString((int)SpanRemaining.TotalHours > 0 ? @"hh\:mm\:ss" : @"mm\:ss"));

                            Dispatcher.Invoke(() =>
                            {
                                ProgressWrite.IsIndeterminate = false;
                                ProgressWrite.Value           = MicrographTables.Count;
                            });
                        }

                        #endregion
                    }, 1, UsedDeviceProcesses);

                    if (MicrographTables.Count > 0)
                    {
                        TableOut = new Star(MicrographTables.Values.ToArray());
                    }
                }

                Thread.Sleep(10000);    // Writing out particles is async, so if workers are killed immediately they may not write out everything

                foreach (var worker in Workers)
                {
                    worker?.Dispose();
                }

                if (IsCanceled)
                {
                    return;
                }

                TableOut.Save(ExportPath);
            });

            Close?.Invoke();
        }
Example #5
0
        private async void ButtonWrite_OnClick(object sender, RoutedEventArgs e)
        {
            bool Invert    = (bool)CheckInvert.IsChecked;
            bool Normalize = (bool)CheckNormalize.IsChecked;
            bool Preflip   = (bool)CheckPreflip.IsChecked;

            bool Relative = (bool)CheckRelative.IsChecked;

            bool Filter = (bool)CheckFilter.IsChecked;
            bool Manual = (bool)CheckManual.IsChecked;

            int BoxSize      = (int)Options.Tasks.Export2DBoxSize;
            int NormDiameter = (int)Options.Tasks.Export2DParticleDiameter;

            ProgressWrite.Visibility      = Visibility.Visible;
            ProgressWrite.IsIndeterminate = true;
            PanelButtons.Visibility       = Visibility.Collapsed;
            PanelRemaining.Visibility     = Visibility.Visible;

            foreach (var element in DisableWhileProcessing)
            {
                element.IsEnabled = false;
            }

            await Task.Run(() =>
            {
                #region Get all movies that can potentially be used

                List <TiltSeries> ValidSeries = Series.Where(v =>
                {
                    if (!Filter && v.UnselectFilter && v.UnselectManual == null)
                    {
                        return(false);
                    }
                    if (!Manual && v.UnselectManual != null && (bool)v.UnselectManual)
                    {
                        return(false);
                    }
                    if (v.OptionsCTF == null)
                    {
                        return(false);
                    }
                    return(true);
                }).ToList();
                List <string> ValidMovieNames = ValidSeries.Select(m => m.RootName).ToList();

                #endregion

                #region Read table and intersect its micrograph set with valid movies

                Star TableIn;

                if (Options.Tasks.InputOnePerItem)
                {
                    List <Star> Tables = new List <Star>();
                    foreach (var item in Series)
                    {
                        string StarPath = InputFolder + item.RootName + InputSuffix;
                        if (File.Exists(StarPath))
                        {
                            Star TableItem = new Star(StarPath);
                            if (!TableItem.HasColumn("rlnMicrographName"))
                            {
                                TableItem.AddColumn("rlnMicrographName", item.Name);
                            }
                            else
                            {
                                TableItem.SetColumn("rlnMicrographName", Helper.ArrayOfConstant(item.Name, TableItem.RowCount));
                            }

                            Tables.Add(TableItem);
                        }
                    }

                    TableIn = new Star(Tables.ToArray());
                }
                else
                {
                    TableIn = new Star(ImportPath);
                }

                if (!TableIn.HasColumn("rlnMicrographName"))
                {
                    throw new Exception("Couldn't find rlnMicrographName column.");
                }
                if (!TableIn.HasColumn("rlnCoordinateX"))
                {
                    throw new Exception("Couldn't find rlnCoordinateX column.");
                }
                if (!TableIn.HasColumn("rlnCoordinateY"))
                {
                    throw new Exception("Couldn't find rlnCoordinateY column.");
                }
                if (!TableIn.HasColumn("rlnCoordinateZ"))
                {
                    throw new Exception("Couldn't find rlnCoordinateZ column.");
                }

                Dictionary <string, List <int> > Groups = new Dictionary <string, List <int> >();
                {
                    string[] ColumnMicNames = TableIn.GetColumn("rlnMicrographName");
                    for (int r = 0; r < ColumnMicNames.Length; r++)
                    {
                        if (!Groups.ContainsKey(ColumnMicNames[r]))
                        {
                            Groups.Add(ColumnMicNames[r], new List <int>());
                        }
                        Groups[ColumnMicNames[r]].Add(r);
                    }
                    Groups = Groups.ToDictionary(group => Helper.PathToName(group.Key), group => group.Value);

                    Groups = Groups.Where(group => ValidMovieNames.Any(n => group.Key.Contains(n))).ToDictionary(group => group.Key, group => group.Value);
                }

                bool[] RowsIncluded = new bool[TableIn.RowCount];
                foreach (var group in Groups)
                {
                    foreach (var r in group.Value)
                    {
                        RowsIncluded[r] = true;
                    }
                }
                List <int> RowsNotIncluded = new List <int>();
                for (int r = 0; r < RowsIncluded.Length; r++)
                {
                    if (!RowsIncluded[r])
                    {
                        RowsNotIncluded.Add(r);
                    }
                }

                ValidSeries = ValidSeries.Where(v => Groups.Any(n => n.Key.Contains(v.RootName))).ToList();

                if (ValidSeries.Count == 0)     // Exit if there is nothing to export, otherwise errors will be thrown below
                {
                    return;
                }

                #endregion

                #region Make sure all columns are there

                if (!TableIn.HasColumn("rlnMagnification"))
                {
                    TableIn.AddColumn("rlnMagnification", "10000.0");
                }
                else
                {
                    TableIn.SetColumn("rlnMagnification", Helper.ArrayOfConstant("10000.0", TableIn.RowCount));
                }

                if (!TableIn.HasColumn("rlnDetectorPixelSize"))
                {
                    TableIn.AddColumn("rlnDetectorPixelSize", Options.Tasks.TomoSubReconstructPixel.ToString("F5", CultureInfo.InvariantCulture));
                }
                else
                {
                    TableIn.SetColumn("rlnDetectorPixelSize", Helper.ArrayOfConstant(Options.Tasks.TomoSubReconstructPixel.ToString("F5", CultureInfo.InvariantCulture), TableIn.RowCount));
                }

                if (!TableIn.HasColumn("rlnCtfMaxResolution"))
                {
                    TableIn.AddColumn("rlnCtfMaxResolution", "999.0");
                }

                if (!TableIn.HasColumn("rlnImageName"))
                {
                    TableIn.AddColumn("rlnImageName", "None");
                }

                if (!TableIn.HasColumn("rlnCtfImage"))
                {
                    TableIn.AddColumn("rlnCtfImage", "None");
                }

                #endregion

                int MaxDevices  = 999;
                int UsedDevices = Math.Min(MaxDevices, GPU.GetDeviceCount());

                if (IsCanceled)
                {
                    return;
                }

                Star TableOut = null;
                {
                    Dictionary <string, Star> MicrographTables = new Dictionary <string, Star>();

                    #region Get coordinates and angles

                    float[] PosX   = TableIn.GetColumn("rlnCoordinateX").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] PosY   = TableIn.GetColumn("rlnCoordinateY").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] PosZ   = TableIn.GetColumn("rlnCoordinateZ").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                    float[] ShiftX = TableIn.HasColumn("rlnOriginX") ? TableIn.GetColumn("rlnOriginX").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];
                    float[] ShiftY = TableIn.HasColumn("rlnOriginY") ? TableIn.GetColumn("rlnOriginY").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];
                    float[] ShiftZ = TableIn.HasColumn("rlnOriginZ") ? TableIn.GetColumn("rlnOriginZ").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];


                    if (Options.Tasks.TomoSubReconstructNormalizedCoords)
                    {
                        for (int r = 0; r < TableIn.RowCount; r++)
                        {
                            PosX[r] *= (float)Options.Tomo.DimensionsX * (float)Options.PixelSizeMean;
                            PosY[r] *= (float)Options.Tomo.DimensionsY * (float)Options.PixelSizeMean;
                            PosZ[r] *= (float)Options.Tomo.DimensionsZ * (float)Options.PixelSizeMean;
                        }
                    }
                    else
                    {
                        for (int r = 0; r < TableIn.RowCount; r++)
                        {
                            PosX[r] = (PosX[r] - ShiftX[r]) * (float)Options.Tasks.InputPixelSize;
                            PosY[r] = (PosY[r] - ShiftY[r]) * (float)Options.Tasks.InputPixelSize;
                            PosZ[r] = (PosZ[r] - ShiftZ[r]) * (float)Options.Tasks.InputPixelSize;
                        }
                    }

                    float[] AngleRot  = TableIn.HasColumn("rlnAngleRot") && Options.Tasks.TomoSubReconstructPrerotated ? TableIn.GetColumn("rlnAngleRot").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];
                    float[] AngleTilt = TableIn.HasColumn("rlnAngleTilt") && Options.Tasks.TomoSubReconstructPrerotated ? TableIn.GetColumn("rlnAngleTilt").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];
                    float[] AnglePsi  = TableIn.HasColumn("rlnAnglePsi") && Options.Tasks.TomoSubReconstructPrerotated ? TableIn.GetColumn("rlnAnglePsi").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray() : new float[TableIn.RowCount];

                    if (TableIn.HasColumn("rlnOriginX"))
                    {
                        TableIn.RemoveColumn("rlnOriginX");
                    }
                    if (TableIn.HasColumn("rlnOriginY"))
                    {
                        TableIn.RemoveColumn("rlnOriginY");
                    }
                    if (TableIn.HasColumn("rlnOriginZ"))
                    {
                        TableIn.RemoveColumn("rlnOriginZ");
                    }

                    if (Options.Tasks.TomoSubReconstructPrerotated)
                    {
                        if (TableIn.HasColumn("rlnAngleRot"))
                        {
                            TableIn.RemoveColumn("rlnAngleRot");
                            TableIn.AddColumn("rlnAngleRot", "0");
                        }
                        if (TableIn.HasColumn("rlnAngleTilt"))
                        {
                            TableIn.RemoveColumn("rlnAngleTilt");
                            TableIn.AddColumn("rlnAngleTilt", "0");
                        }
                        if (TableIn.HasColumn("rlnAnglePsi"))
                        {
                            TableIn.RemoveColumn("rlnAnglePsi");
                            TableIn.AddColumn("rlnAnglePsi", "0");
                        }
                    }

                    #endregion

                    Dispatcher.Invoke(() => ProgressWrite.MaxValue = ValidSeries.Count);

                    Helper.ForEachGPU(ValidSeries, (series, gpuID) =>
                    {
                        if (IsCanceled)
                        {
                            return;
                        }

                        Stopwatch ItemTime = new Stopwatch();
                        ItemTime.Start();

                        ProcessingOptionsTomoSubReconstruction ExportOptions = Options.GetProcessingTomoSubReconstruction();

                        #region Update row values

                        List <int> GroupRows = Groups.First(n => n.Key.Contains(series.RootName)).Value;

                        int pi = 0;
                        foreach (var r in GroupRows)
                        {
                            TableIn.SetRowValue(r, "rlnCtfMaxResolution", series.CTFResolutionEstimate.ToString("F1", CultureInfo.InvariantCulture));

                            TableIn.SetRowValue(r, "rlnCoordinateX", (PosX[r] / (float)ExportOptions.BinnedPixelSizeMean).ToString("F3", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnCoordinateY", (PosY[r] / (float)ExportOptions.BinnedPixelSizeMean).ToString("F3", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnCoordinateZ", (PosZ[r] / (float)ExportOptions.BinnedPixelSizeMean).ToString("F3", CultureInfo.InvariantCulture));

                            #region Figure out relative or absolute path to sub-tomo and its CTF

                            string PathSubtomo = series.SubtomoDir + $"{series.RootName}_{pi:D7}_{ExportOptions.BinnedPixelSizeMean:F2}A.mrc";
                            string PathCTF     = series.SubtomoDir + $"{series.RootName}_{pi:D7}_ctf_{ExportOptions.BinnedPixelSizeMean:F2}A.mrc";
                            if (Relative)
                            {
                                Uri UriStar = new Uri(ExportPath);
                                PathSubtomo = UriStar.MakeRelativeUri(new Uri(PathSubtomo)).ToString();
                                PathCTF     = UriStar.MakeRelativeUri(new Uri(PathCTF)).ToString();
                            }

                            #endregion

                            TableIn.SetRowValue(r, "rlnImageName", PathSubtomo);
                            TableIn.SetRowValue(r, "rlnCtfImage", PathCTF);

                            pi++;
                        }

                        #endregion

                        #region Populate micrograph table with rows for all exported particles

                        Star MicrographTable = new Star(TableIn.GetColumnNames());

                        foreach (var r in GroupRows)
                        {
                            MicrographTable.AddRow(TableIn.GetRow(r).ToList());
                        }

                        #endregion

                        #region Finally, reconstruct the actual sub-tomos

                        float3[] TomoPositions = Helper.Combine(GroupRows.Select(r => Helper.ArrayOfConstant(new float3(PosX[r], PosY[r], PosZ[r]), series.NTilts)).ToArray());
                        float3[] TomoAngles    = Helper.Combine(GroupRows.Select(r => Helper.ArrayOfConstant(new float3(AngleRot[r], AngleTilt[r], AnglePsi[r]), series.NTilts)).ToArray());

                        series.ReconstructSubtomos(ExportOptions, TomoPositions, TomoAngles);

                        #endregion

                        #region Add this micrograph's table to global collection, update remaining time estimate

                        lock (MicrographTables)
                        {
                            MicrographTables.Add(series.RootName, MicrographTable);

                            Timings.Add(ItemTime.ElapsedMilliseconds / (float)UsedDevices);

                            int MsRemaining        = (int)(MathHelper.Mean(Timings) * (ValidSeries.Count - MicrographTables.Count));
                            TimeSpan SpanRemaining = new TimeSpan(0, 0, 0, 0, MsRemaining);

                            Dispatcher.Invoke(() => TextRemaining.Text = SpanRemaining.ToString((int)SpanRemaining.TotalHours > 0 ? @"hh\:mm\:ss" : @"mm\:ss"));

                            Dispatcher.Invoke(() =>
                            {
                                ProgressWrite.IsIndeterminate = false;
                                ProgressWrite.Value           = MicrographTables.Count;
                            });
                        }

                        #endregion
                    }, 1);

                    if (MicrographTables.Count > 0)
                    {
                        TableOut = new Star(MicrographTables.Values.ToArray());
                    }
                }

                if (IsCanceled)
                {
                    return;
                }

                TableOut.Save(ExportPath);
            });

            Close?.Invoke();
        }