Esempio n. 1
0
 public void MovieExportParticles(string path, ProcessingOptionsParticlesExport options, float2[] coordinates)
 {
     if (!SendCommand(new NamedSerializableObject("MovieExportParticles",
                                                  path,
                                                  options,
                                                  coordinates)))
     {
         throw new Exception("Couldn't export the particles!");
     }
 }
Esempio n. 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 ButtonExport_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 DoAverage        = (bool)RadioAverage.IsChecked;
            bool DoStack          = (bool)RadioStack.IsChecked;
            bool DoDenoisingPairs = (bool)RadioDenoising.IsChecked;
            bool DoOnlyStar       = (bool)RadioStar.IsChecked;

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

            float AngPix = (float)Options.Tasks.InputPixelSize;

            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(async() =>
            {
                #region Get all movies that can potentially be used

                List <Movie> ValidMovies = Movies.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 = ValidMovies.Select(m => m.RootName).ToList();

                if (ValidMovies.Count == 0)
                {
                    await Dispatcher.Invoke(async() =>
                    {
                        await((MainWindow)Application.Current.MainWindow).ShowMessageAsync("Oopsie",
                                                                                           "No items were found to extract particles from.\n" +
                                                                                           "Please make sure the names match, estimate the CTF for all items, and review your filtering thresholds.");
                    });
                }

                #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 Movies)
                    {
                        string StarPath = InputFolder + item.RootName + InputSuffix + ".star";
                        if (File.Exists(StarPath))
                        {
                            Star TableItem = new Star(StarPath);
                            if (!TableItem.HasColumn("rlnMicrographName"))
                            {
                                TableItem.AddColumn("rlnMicrographName", item.Name);
                            }

                            if (item.PickingThresholds.ContainsKey(InputSuffix) && TableItem.HasColumn("rlnAutopickFigureOfMerit"))
                            {
                                float Threshold   = (float)item.PickingThresholds[InputSuffix];
                                float[] Scores    = TableItem.GetColumn("rlnAutopickFigureOfMerit").Select(v => float.Parse(v, CultureInfo.InvariantCulture)).ToArray();
                                int[] InvalidRows = Helper.ArrayOfSequence(0, TableItem.RowCount, 1).Where(i => Scores[i] < Threshold).ToArray();
                                TableItem.RemoveRows(InvalidRows);
                            }

                            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.");
                }

                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.Contains(group.Key)).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);
                    }
                }

                ValidMovies = ValidMovies.Where(v => Groups.ContainsKey(v.RootName)).ToList();

                if (ValidMovies.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.GetProcessingParticleExport().BinnedPixelSizeMean.ToString("F5", CultureInfo.InvariantCulture));
                }
                else
                {
                    TableIn.SetColumn("rlnDetectorPixelSize", Helper.ArrayOfConstant(Options.GetProcessingParticleExport().BinnedPixelSizeMean.ToString("F5", CultureInfo.InvariantCulture), TableIn.RowCount));
                }

                if (!TableIn.HasColumn("rlnVoltage"))
                {
                    TableIn.AddColumn("rlnVoltage", "300.0");
                }

                if (!TableIn.HasColumn("rlnSphericalAberration"))
                {
                    TableIn.AddColumn("rlnSphericalAberration", "2.7");
                }

                if (!TableIn.HasColumn("rlnAmplitudeContrast"))
                {
                    TableIn.AddColumn("rlnAmplitudeContrast", "0.07");
                }

                if (!TableIn.HasColumn("rlnPhaseShift"))
                {
                    TableIn.AddColumn("rlnPhaseShift", "0.0");
                }

                if (!TableIn.HasColumn("rlnDefocusU"))
                {
                    TableIn.AddColumn("rlnDefocusU", "0.0");
                }

                if (!TableIn.HasColumn("rlnDefocusV"))
                {
                    TableIn.AddColumn("rlnDefocusV", "0.0");
                }

                if (!TableIn.HasColumn("rlnDefocusAngle"))
                {
                    TableIn.AddColumn("rlnDefocusAngle", "0.0");
                }

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

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

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

                #endregion

                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();

                if (IsCanceled)
                {
                    return;
                }

                #region Create worker processes

                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);

                    if (!string.IsNullOrEmpty(Options.Import.GainPath) && Options.Import.CorrectGain)
                    {
                        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

                #region Load gain reference if needed

                //Image[] ImageGain = new Image[NDevices];
                //if (!string.IsNullOrEmpty(Options.Import.GainPath) && Options.Import.CorrectGain && File.Exists(Options.Import.GainPath))
                //    for (int d = 0; d < NDevices; d++)
                //    {
                //        GPU.SetDevice(d);
                //        ImageGain[d] = MainWindow.LoadAndPrepareGainReference();
                //    }

                #endregion

                bool Overwrite = true;
                if (DoAverage || DoStack)
                {
                    foreach (var movie in ValidMovies)
                    {
                        bool FileExists = File.Exists((DoAverage ? movie.ParticlesDir : movie.ParticleMoviesDir) + movie.RootName + Options.Tasks.OutputSuffix + ".mrcs");
                        if (FileExists)
                        {
                            await Dispatcher.Invoke(async() =>
                            {
                                var DialogResult = await((MainWindow)Application.Current.MainWindow).ShowMessageAsync("Some particle files already exist. Overwrite them?",
                                                                                                                      "",
                                                                                                                      MessageDialogStyle.AffirmativeAndNegative,
                                                                                                                      new MetroDialogSettings()
                                {
                                    AffirmativeButtonText = "Yes",
                                    NegativeButtonText    = "No"
                                });
                                if (DialogResult == MessageDialogResult.Negative)
                                {
                                    Overwrite = false;
                                }
                            });
                            break;
                        }
                    }
                }

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

                    #region Get coordinates

                    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[] 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];
                    for (int r = 0; r < TableIn.RowCount; r++)
                    {
                        PosX[r] -= ShiftX[r];
                        PosY[r] -= ShiftY[r];
                    }

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

                    #endregion

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

                    Helper.ForEachGPU(ValidMovies, (movie, gpuID) =>
                    {
                        if (IsCanceled)
                        {
                            return;
                        }

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

                        MapHeader OriginalHeader = MapHeader.ReadFromFile(movie.Path);

                        #region Set up export options

                        ProcessingOptionsParticlesExport ExportOptions = Options.GetProcessingParticleExport();
                        ExportOptions.DoAverage        = DoAverage;
                        ExportOptions.DoDenoisingPairs = DoDenoisingPairs;
                        ExportOptions.DoStack          = DoStack;
                        ExportOptions.Invert           = Invert;
                        ExportOptions.Normalize        = Normalize;
                        ExportOptions.PreflipPhases    = Preflip;
                        ExportOptions.Dimensions       = OriginalHeader.Dimensions.MultXY((float)Options.PixelSizeMean);
                        ExportOptions.BoxSize          = BoxSize;
                        ExportOptions.Diameter         = NormDiameter;

                        #endregion

                        bool FileExists = File.Exists((DoAverage ? movie.ParticlesDir : movie.ParticleMoviesDir) + movie.RootName + ExportOptions.Suffix + ".mrcs");

                        #region Load and prepare original movie

                        //Image OriginalStack = null;
                        decimal ScaleFactor = 1M / (decimal)Math.Pow(2, (double)ExportOptions.BinTimes);

                        if (!DoOnlyStar && (!FileExists || Overwrite))
                        {
                            Workers[gpuID].LoadStack(movie.Path, ScaleFactor, ExportOptions.EERGroupFrames);
                        }
                        //MainWindow.LoadAndPrepareHeaderAndMap(movie.Path, ImageGain[gpuID], ScaleFactor, out OriginalHeader, out OriginalStack);

                        if (IsCanceled)
                        {
                            //foreach (Image gain in ImageGain)
                            //    gain?.Dispose();
                            //OriginalStack?.Dispose();

                            return;
                        }

                        #endregion

                        #region Figure out relative or absolute path to particle stack

                        string PathStack      = (ExportOptions.DoStack ? movie.ParticleMoviesDir : movie.ParticlesDir) + movie.RootName + ExportOptions.Suffix + ".mrcs";
                        string PathMicrograph = movie.Path;
                        if (Relative)
                        {
                            Uri UriStar    = new Uri(ExportPath);
                            PathStack      = UriStar.MakeRelativeUri(new Uri(PathStack)).ToString();
                            PathMicrograph = UriStar.MakeRelativeUri(new Uri(PathMicrograph)).ToString();
                        }

                        #endregion

                        #region Update row values

                        List <int> GroupRows    = Groups[movie.RootName];
                        List <float2> Positions = new List <float2>();

                        float Astigmatism  = (float)movie.CTF.DefocusDelta / 2;
                        float PhaseShift   = movie.OptionsCTF.DoPhase ? movie.GridCTFPhase.GetInterpolated(new float3(0.5f)) * 180 : 0;
                        int ImageNameIndex = TableIn.GetColumnID("rlnImageName");

                        foreach (var r in GroupRows)
                        {
                            float3 Position = new float3(PosX[r] * AngPix / ExportOptions.Dimensions.X,
                                                         PosY[r] * AngPix / ExportOptions.Dimensions.Y,
                                                         0.5f);
                            float LocalDefocus = movie.GridCTFDefocus.GetInterpolated(Position);

                            TableIn.SetRowValue(r, "rlnDefocusU", ((LocalDefocus + Astigmatism) * 1e4f).ToString("F1", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnDefocusV", ((LocalDefocus - Astigmatism) * 1e4f).ToString("F1", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnDefocusAngle", movie.CTF.DefocusAngle.ToString("F1", CultureInfo.InvariantCulture));

                            TableIn.SetRowValue(r, "rlnVoltage", movie.CTF.Voltage.ToString("F1", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnSphericalAberration", movie.CTF.Cs.ToString("F4", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnAmplitudeContrast", movie.CTF.Amplitude.ToString("F3", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnPhaseShift", PhaseShift.ToString("F1", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnCtfMaxResolution", movie.CTFResolutionEstimate.ToString("F1", CultureInfo.InvariantCulture));

                            TableIn.SetRowValue(r, "rlnCoordinateX", (PosX[r] * AngPix / (float)ExportOptions.BinnedPixelSizeMean).ToString("F2", CultureInfo.InvariantCulture));
                            TableIn.SetRowValue(r, "rlnCoordinateY", (PosY[r] * AngPix / (float)ExportOptions.BinnedPixelSizeMean).ToString("F2", CultureInfo.InvariantCulture));

                            TableIn.SetRowValue(r, "rlnImageName", PathStack);

                            TableIn.SetRowValue(r, "rlnMicrographName", PathMicrograph);

                            Positions.Add(new float2(PosX[r] * AngPix,
                                                     PosY[r] * AngPix));
                        }

                        #endregion

                        #region Populate micrograph table with rows for all exported particles

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

                        int StackDepth = (ExportOptions.DoAverage || ExportOptions.DoDenoisingPairs || DoOnlyStar)
                                             ? 1
                                             : (OriginalHeader.Dimensions.Z - ExportOptions.SkipFirstN - ExportOptions.SkipLastN) /
                                         ExportOptions.StackGroupSize;

                        int pi = 0;
                        for (int i = 0; i < StackDepth; i++)
                        {
                            foreach (var r in GroupRows)
                            {
                                List <string> Row   = TableIn.GetRow(r).ToList();
                                Row[ImageNameIndex] = (++pi).ToString("D7") + "@" + Row[ImageNameIndex];
                                MicrographTable.AddRow(Row);
                            }
                        }

                        #endregion

                        #region Finally, process and export the actual particles

                        if (!DoOnlyStar && (!FileExists || Overwrite))
                        {
                            Workers[gpuID].MovieExportParticles(movie.Path, ExportOptions, Positions.ToArray());
                            //movie.ExportParticles(OriginalStack, Positions.ToArray(), ExportOptions);
                            //OriginalStack.Dispose();
                        }

                        #endregion

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

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

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

                            int MsRemaining        = (int)(MathHelper.Mean(Timings) * (ValidMovies.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 particle stacks is async, so if workers are killed immediately they may not write out everything

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

                //foreach (Image gain in ImageGain)
                //    gain?.Dispose();

                if (IsCanceled)
                {
                    return;
                }

                TableOut.Save(ExportPath);
            });

            DataContext = null;
            Close?.Invoke();
        }