Skip to content

Commit 844343b

Browse files
Add AreTomo3 wrapper for tilt series alignment (#407)
1 parent 422739a commit 844343b

File tree

4 files changed

+296
-0
lines changed

4 files changed

+296
-0
lines changed

WarpLib/TiltSeries/TiltSeries.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2641,6 +2641,25 @@ public class ProcessingOptionsTomoAretomo : TomoProcessingOptionsBase
26412641
public int[] NPatchesXY { get; set; }
26422642
}
26432643

2644+
[Serializable]
2645+
public class ProcessingOptionsTomoAretomo3 : TomoProcessingOptionsBase
2646+
{
2647+
[WarpSerializable]
2648+
public int AlignZ { get; set; }
2649+
2650+
[WarpSerializable]
2651+
public int[] AtPatch { get; set; }
2652+
2653+
[WarpSerializable]
2654+
public decimal AxisAngle { get; set; }
2655+
2656+
[WarpSerializable]
2657+
public bool DoAxisSearch { get; set; }
2658+
2659+
[WarpSerializable]
2660+
public string Executable { get; set; }
2661+
}
2662+
26442663
[Serializable]
26452664
public class ProcessingOptionsTomoEtomoPatch : TomoProcessingOptionsBase
26462665
{

WarpLib/WorkerWrapper.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,13 @@ public void TomoAretomo(string path, ProcessingOptionsTomoAretomo options)
397397
options));
398398
}
399399

400+
public void TomoAretomo3(string path, ProcessingOptionsTomoAretomo3 options)
401+
{
402+
SendCommand(new NamedSerializableObject("TomoAretomo3",
403+
path,
404+
options));
405+
}
406+
400407
public void TomoEtomoPatchTrack(string path, ProcessingOptionsTomoEtomoPatch options)
401408
{
402409
SendCommand(new NamedSerializableObject("TomoEtomoPatchTrack",
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
using CommandLine;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using Warp;
9+
using Warp.Tools;
10+
11+
namespace WarpTools.Commands
12+
{
13+
[VerbGroup("Tilt series")]
14+
[Verb("ts_aretomo3", HelpText = "Create tilt series stacks and run AreTomo3 to obtain tilt series alignments")]
15+
[CommandRunner(typeof(AreTomo3Tiltseries))]
16+
class AreTomo3TiltseriesOptions : DistributedOptions
17+
{
18+
[Option("angpix", HelpText = "Rescale tilt images to this pixel size; normally 10–15 for cryo data; leave out to keep the original pixel size")]
19+
public double? AngPix { get; set; }
20+
21+
[Option("mask", HelpText = "Apply mask to each image if available; masked areas will be filled with Gaussian noise")]
22+
public bool ApplyMask { get; set; }
23+
24+
[Option("alignz", Default = 0, HelpText = "Sample thickness in Angstrom for AreTomo3's AlignZ parameter (auto-converted to binned pixels). When 0 or not given, AreTomo3 will estimate sample thickness automatically")]
25+
public int AlignZ { get; set; }
26+
27+
[Option("patches", Default = "4,4", HelpText = "Number of patches for local alignments in X, Y, separated by comma: e.g. 4,4")]
28+
public string AtPatch { get; set; }
29+
30+
[Option("axis_iter", Default = 0, HelpText = "Number of tilt axis angle refinement iterations; each iteration will be started with median value from previous iteration, final iteration will use fixed angle")]
31+
public int AxisIterations { get; set; }
32+
33+
[Option("axis_batch", Default = 0, HelpText = "Use only this many tilt series for the tilt axis angle search; only relevant if --axis_iter isn't 0")]
34+
public int AxisBatch { get; set; }
35+
36+
[Option("min_fov", Default = 0.0, HelpText = "Disable tilts that contain less than this fraction of the tomogram's field of view due to excessive shifts")]
37+
public double MinFOV { get; set; }
38+
39+
[Option("axis", HelpText = "Override tilt axis angle with this value")]
40+
public double? AxisAngle { get; set; }
41+
42+
[Option("delete_intermediate", HelpText = "Delete tilt series stacks generated for AreTomo3")]
43+
public bool DeleteIntermediate { get; set; }
44+
45+
[Option("thumbnails", HelpText = "Create thumbnails for each tilt image using the same pixel size as the stack")]
46+
public bool CreateThumbnails { get; set; }
47+
48+
[Option("exe", Default = "AreTomo3", HelpText = "Name of the AreTomo3 executable; must be in $PATH")]
49+
public string Executable { get; set; }
50+
}
51+
52+
class AreTomo3Tiltseries : BaseCommand
53+
{
54+
public override async Task Run(object options)
55+
{
56+
await base.Run(options);
57+
AreTomo3TiltseriesOptions CLI = options as AreTomo3TiltseriesOptions;
58+
CLI.Evaluate();
59+
60+
OptionsWarp Options = CLI.Options;
61+
62+
#region Validate options
63+
64+
if (CLI.AngPix.HasValue && CLI.AngPix.Value < (double)Options.Import.BinnedPixelSize)
65+
throw new Exception("--angpix can't be smaller than the binned pixel size of the original data");
66+
else if (!CLI.AngPix.HasValue)
67+
CLI.AngPix = (double)Options.Import.BinnedPixelSize;
68+
69+
if (CLI.MinFOV > 1)
70+
throw new Exception("--min_fov can't be higher than 1");
71+
72+
if (CLI.AxisIterations < 0)
73+
throw new Exception("--axis_iter can't be negative");
74+
75+
if (CLI.AxisBatch < 0)
76+
throw new Exception("--axis_batch can't be negative");
77+
78+
if (CLI.AlignZ != 0 && CLI.AlignZ < 1)
79+
throw new Exception("--alignz can't be lower than 1 (or use 0 as default)");
80+
81+
bool RefiningAxis = CLI.AxisIterations > 0;
82+
83+
if (!Helper.ExeutableIsOnPath(CLI.Executable))
84+
throw new Exception($"Executable '{CLI.Executable}' not found on PATH");
85+
86+
#endregion
87+
88+
#region Create processing options
89+
90+
var OptionsStack = (ProcessingOptionsTomoStack)Options.FillTomoProcessingBase(new ProcessingOptionsTomoStack());
91+
OptionsStack.ApplyMask = CLI.ApplyMask;
92+
OptionsStack.CreateThumbnails = CLI.CreateThumbnails;
93+
OptionsStack.BinTimes = (decimal)Math.Log(CLI.AngPix.Value / (double)Options.Import.PixelSize, 2.0);
94+
95+
var OptionsImport = (ProcessingOptionsTomoImportAlignments)Options.FillTomoProcessingBase(new ProcessingOptionsTomoImportAlignments());
96+
OptionsImport.MinFOV = (decimal)CLI.MinFOV;
97+
OptionsImport.BinTimes = OptionsStack.BinTimes;
98+
99+
var OptionsAretomo3 = (ProcessingOptionsTomoAretomo3)Options.FillTomoProcessingBase(new ProcessingOptionsTomoAretomo3());
100+
OptionsAretomo3.Executable = CLI.Executable;
101+
OptionsAretomo3.AlignZ = (int)Math.Round(CLI.AlignZ / OptionsStack.BinnedPixelSizeMean);
102+
103+
if (!string.IsNullOrEmpty(CLI.AtPatch))
104+
{
105+
try
106+
{
107+
var AtPatchParts = CLI.AtPatch.Split(',').Select(int.Parse).ToArray();
108+
if (AtPatchParts.Length == 2)
109+
OptionsAretomo3.AtPatch = AtPatchParts;
110+
else
111+
throw new Exception("AtPatch must have exactly 2 values");
112+
}
113+
catch
114+
{
115+
throw new Exception("AtPatch dimensions must be specified as X,Y, e.g. 4,4");
116+
}
117+
}
118+
else
119+
{
120+
OptionsAretomo3.AtPatch = new int[] { 4, 4 };
121+
}
122+
123+
124+
125+
#endregion
126+
127+
WorkerWrapper[] Workers = CLI.GetWorkers();
128+
129+
Func<float> CalculateAverageAxis = () =>
130+
{
131+
float2 MedianVec = new float2(1, 0);
132+
if (CLI.InputSeries.Count() > 3)
133+
MedianVec = MathHelper.GeometricMedian(CLI.InputSeries.Select(m =>
134+
{
135+
float Axis = (m as TiltSeries).TiltAxisAngles.Average() * Helper.ToRad;
136+
return new float2(MathF.Cos(Axis), MathF.Sin(Axis));
137+
})).Normalized();
138+
else
139+
MedianVec = MathHelper.Mean(CLI.InputSeries.Select(m =>
140+
{
141+
float Axis = (m as TiltSeries).TiltAxisAngles.Average() * Helper.ToRad;
142+
return new float2(MathF.Cos(Axis), MathF.Sin(Axis));
143+
})).Normalized();
144+
145+
return MathF.Atan2(MedianVec.Y, MedianVec.X) * Helper.ToDeg;
146+
};
147+
148+
float AxisAngle = CLI.AxisAngle.HasValue ? (float)CLI.AxisAngle.Value : CalculateAverageAxis();
149+
var AllSeries = CLI.InputSeries.ToArray();
150+
var UsedForSearch = CLI.AxisBatch > 0 ? AllSeries.Take(CLI.AxisBatch).ToArray() : AllSeries;
151+
var NotUsedForSearch = CLI.AxisBatch > 0 ? AllSeries.Where(s => !UsedForSearch.Contains(s)).ToArray() : Array.Empty<Movie>();
152+
153+
for (int iiter = 0; iiter < CLI.AxisIterations + 1; iiter++)
154+
{
155+
bool LastIter = iiter == CLI.AxisIterations;
156+
OptionsAretomo3.AxisAngle = (decimal)AxisAngle;
157+
OptionsAretomo3.DoAxisSearch = !LastIter;
158+
159+
Console.WriteLine($"Current tilt axis angle: {AxisAngle:F3} °");
160+
161+
if (iiter < CLI.AxisIterations)
162+
{
163+
Console.WriteLine($"Running iteration {iiter + 1} of tilt axis refinement:");
164+
165+
if (CLI.AxisBatch > 0)
166+
{
167+
CLI.InputSeries = UsedForSearch;
168+
Console.WriteLine($"Using {CLI.InputSeries.Length} out of {AllSeries.Length} series for tilt axis refinement");
169+
}
170+
}
171+
else
172+
{
173+
Console.WriteLine("Running AreTomo3 with final average tilt axis angle:");
174+
175+
CLI.InputSeries = AllSeries;
176+
}
177+
178+
IterateOverItems<TiltSeries>(Workers, CLI, (worker, t) =>
179+
{
180+
if (iiter == 0 || NotUsedForSearch.Contains(t))
181+
worker.TomoStack(t.Path, OptionsStack);
182+
183+
worker.TomoAretomo3(t.Path, OptionsAretomo3);
184+
185+
t.ImportAlignments(OptionsImport);
186+
187+
if (LastIter)
188+
t.SaveMeta();
189+
});
190+
191+
AxisAngle = CalculateAverageAxis();
192+
}
193+
194+
if (CLI.DeleteIntermediate)
195+
{
196+
Console.Write("Deleting intermediate stacks... ");
197+
198+
foreach (var t in CLI.InputSeries)
199+
{
200+
foreach (var dir in Directory.GetDirectories((t as TiltSeries).TiltStackDir))
201+
if (!dir.EndsWith("thumbnails"))
202+
Directory.Delete(dir, true);
203+
204+
foreach (var file in Directory.GetFiles((t as TiltSeries).TiltStackDir))
205+
File.Delete(file);
206+
}
207+
208+
Console.WriteLine("Done");
209+
}
210+
211+
Console.Write("Saying goodbye to all workers...");
212+
foreach (var worker in Workers)
213+
worker.Dispose();
214+
Console.WriteLine(" Done");
215+
}
216+
}
217+
}

WarpWorker/WarpWorker.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,59 @@ public static void EvaluateCommand(NamedSerializableObject Command)
500500

501501
Console.WriteLine($"Executed AreTomo for {SeriesPath}");
502502
}
503+
else if (Command.Name == "TomoAretomo3")
504+
{
505+
string SeriesPath = (string)Command.Content[0];
506+
var Options = (ProcessingOptionsTomoAretomo3)Command.Content[1];
507+
508+
TiltSeries T = new TiltSeries(SeriesPath);
509+
510+
string StackDir = T.TiltStackDir;
511+
string StackPath = Path.GetFileName(T.TiltStackPath);
512+
513+
// Build the AreTomo3 command
514+
// Use the actual stack path as the input for AreTomo3
515+
string BaseName = Path.GetFileNameWithoutExtension(StackPath);
516+
string InPrefix = BaseName;
517+
string InSuffix = ".st";
518+
string AtPatch = $"{Options.AtPatch[0]} {Options.AtPatch[1]}";
519+
string Axis = Options.AxisAngle.ToString() + (Options.DoAxisSearch ? " 0" : " -1");
520+
string AlignZ = Options.AlignZ.ToString();
521+
522+
// VolZ is forced to 0 as per AreTomo3 requirements
523+
string Arguments = $"-InPrefix {InPrefix} -InSuffix {InSuffix} -OutDir {StackDir} " +
524+
$"-TiltAxis {Axis} -AlignZ {AlignZ} -AtPatch {AtPatch} -VolZ 0 " +
525+
$"-ExtZ 0 -OutImod 2 -TiltCor 1 -Cmd 1 -Serial 1 " +
526+
$"-CorrCTF 0 0 -DarkTol 0 -Gpu {DeviceID}";
527+
528+
Console.WriteLine($"Executing {Options.Executable} in {StackDir} with arguments: {Arguments}");
529+
530+
Process AreTomo3 = new Process
531+
{
532+
StartInfo =
533+
{
534+
FileName = Options.Executable,
535+
CreateNoWindow = false,
536+
WindowStyle = ProcessWindowStyle.Minimized,
537+
WorkingDirectory = StackDir,
538+
Arguments = Arguments,
539+
RedirectStandardOutput = true,
540+
RedirectStandardError = true
541+
}
542+
};
543+
DataReceivedEventHandler Handler = (sender, args) => { if (args.Data != null) Console.WriteLine(args.Data); };
544+
AreTomo3.OutputDataReceived += Handler;
545+
AreTomo3.ErrorDataReceived += Handler;
546+
547+
AreTomo3.Start();
548+
549+
AreTomo3.BeginOutputReadLine();
550+
AreTomo3.BeginErrorReadLine();
551+
552+
AreTomo3.WaitForExit();
553+
554+
Console.WriteLine($"Executed AreTomo3 for {SeriesPath}");
555+
}
503556
else if (Command.Name == "TomoEtomoPatchTrack")
504557
{
505558
string TiltSeriesPath = (string)Command.Content[0];

0 commit comments

Comments
 (0)