Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions tests/MSDIAL5/MsdialCoreTestApp/Parser/ConfigParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ private static List<MspAnnotatorSetting> ReadMspAnnotatorSettingsTable(string fi
}
}
settings.Add(new MspAnnotatorSetting(annotatorId, mspFilePath, priority, searchParameter, targetOmics));
ReportEffectiveAnnotatorSettings("MSP", annotatorId, mspFilePath, priority, searchParameter);
}
return settings;
}
Expand Down Expand Up @@ -340,10 +341,29 @@ private static List<TextAnnotatorSetting> ReadTextAnnotatorSettingsTable(string
var searchParameter = new MsRefSearchParameterBase(param.TextDbSearchParam);
ApplyMspSearchParameter(searchParameter, fields, headers);
settings.Add(new TextAnnotatorSetting(annotatorId, textDbFilePath, priority, searchParameter));
ReportEffectiveAnnotatorSettings("Text", annotatorId, textDbFilePath, priority, searchParameter);
}
return settings;
}

/// <summary>
/// States the settings an annotator will actually use.
/// </summary>
/// <remarks>
/// A settings row starts from the method file's annotation block and overrides,
/// column by column, whatever the table supplies. So the same setting is written
/// down in two places with two different values and neither file says which one
/// governs. Printing the resolved value settles it in the run log, where a reader
/// of the artifacts can see it.
/// </remarks>
private static void ReportEffectiveAnnotatorSettings(
string kind, string annotatorId, string filePath, int priority, MsRefSearchParameterBase parameter) {
Console.WriteLine(
$"{kind} annotator {annotatorId} ({Path.GetFileName(filePath)}), priority {priority}: "
+ $"RT tolerance {parameter.RtTolerance}, MS1 tolerance {parameter.Ms1Tolerance}, "
+ $"MS2 tolerance {parameter.Ms2Tolerance}, total score cutoff {parameter.TotalScoreCutoff}");
}

private static void ApplyMspSearchParameter(MsRefSearchParameterBase parameter, string[] fields, string[] headers) {
SetFloat(fields, headers, value => parameter.MassRangeBegin = value, "massrangebegin", "massbegin");
SetFloat(fields, headers, value => parameter.MassRangeEnd = value, "massrangeend", "massend");
Expand Down Expand Up @@ -950,6 +970,10 @@ public static bool ReadCommonParameter(ParameterBase param, string method, strin
case "set fully labeled reference file": if (valueLower == "true" || valueLower == "false") param.SetFullyLabeledReferenceFile = bool.Parse(valueLower); return true;
case "non labeled reference id": if (int.TryParse(valueLower, out int nonlabeledrefid)) param.NonLabeledReferenceID = nonlabeledrefid; return true;
case "fully labeled reference id": if (int.TryParse(valueLower, out int fulllabeledrefid)) param.FullyLabeledReferenceID = fulllabeledrefid; return true;
// ParameterBase writes "Number of threads" into every exported method file,
// but nothing read it back, so a method file could describe a thread count
// it could never request and every Console run stayed on the default of 2.
case "number of threads": if (int.TryParse(valueLower, out int numthreads) && numthreads > 0) param.NumThreads = numthreads; return true;
case "isotope tracking dictionary id": if (int.TryParse(valueLower, out int isotopetrackdictionaryid)) param.IsotopeTrackingDictionary.SelectedID = isotopetrackdictionaryid; return true;

//CorrDec settings
Expand Down
20 changes: 20 additions & 0 deletions tests/MSDIAL5/MsdialCoreTestApp/Process/CommonProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,26 @@ public static bool SetProjectProperty(ParameterBase param, string input, out Lis
return true;
}

/// <summary>
/// Names the LBM annotator for the exported Comment column.
/// </summary>
/// <remarks>
/// The annotator identifier is written into every exported row as
/// "Annotation method: ...". Passing the library's file path there put an
/// absolute local directory into an artifact meant for sharing, and told the
/// reader nothing a directory-free name does not. The file stem is kept because
/// a laboratory library is usually date-stamped and the reader needs to know
/// which one annotated the row; the checksum that pins it exactly belongs in the
/// run manifest, not in every cell.
/// </remarks>
public static string LbmAnnotatorId(string lbmFilePath) {
var stem = string.IsNullOrWhiteSpace(lbmFilePath)
? string.Empty
: System.IO.Path.GetFileNameWithoutExtension(lbmFilePath);
return string.IsNullOrWhiteSpace(stem) ? "LbmDB" : "LbmDB: " + stem;
}


public static void ParseLibraries(ParameterBase param, float targetMz,
out IupacDatabase iupacDB, out MoleculeDataBase? mspDB, out MoleculeDataBase? txtDB,
out List<MoleculeMsReference> isotopeTextDB, out List<MoleculeMsReference> compoundsInTargetMode,
Expand Down
2 changes: 1 addition & 1 deletion tests/MSDIAL5/MsdialCoreTestApp/Process/LcimmsProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public int Run(string inputFolder, string outputFolder, string methodFile, bool
]);
}
if (lbmDB is { Database.Count: > 0 }) {
var lbmAnnotator = new LcimmsMspAnnotator(lbmDB, param.LbmSearchParam, param.TargetOmics, param.LbmFilePath, 1);
var lbmAnnotator = new LcimmsMspAnnotator(lbmDB, param.LbmSearchParam, param.TargetOmics, CommonProcess.LbmAnnotatorId(param.LbmFilePath), 1);
dbStorage.AddMoleculeDataBase(lbmDB, [
new MetabolomicsAnnotatorParameterPair(lbmAnnotator.Save(), new AnnotationQueryFactory(lbmAnnotator, param.PeakPickBaseParam, param.LbmSearchParam, ignoreIsotopicPeak: true)),
]);
Expand Down
40 changes: 36 additions & 4 deletions tests/MSDIAL5/MsdialCoreTestApp/Process/LcmsProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public int Run(string inputFolder, string outputFolder, string methodFile, bool
}
}
if (lbmDB is { Database.Count: > 0 }) {
var lbmAnnotator = new LcmsMspAnnotator(lbmDB, param.LbmSearchParam, TargetOmics.Lipidomics, param.LbmFilePath, lbmAnnotatorPriority);
var lbmAnnotator = new LcmsMspAnnotator(lbmDB, param.LbmSearchParam, TargetOmics.Lipidomics, CommonProcess.LbmAnnotatorId(param.LbmFilePath), lbmAnnotatorPriority);
dbStorage.AddMoleculeDataBase(lbmDB, [
new MetabolomicsAnnotatorParameterPair(lbmAnnotator.Save(), new AnnotationQueryFactory(lbmAnnotator, param.PeakPickBaseParam, param.LbmSearchParam, ignoreIsotopicPeak: true)),
]);
Expand Down Expand Up @@ -228,10 +228,39 @@ IQuantValueAccessor CreateQuantAccessor(string exportType) => alignmentLightPeak
Console.WriteLine($"Detailed alignment provenance: {provenanceOutputFile}");
}

// The parameter file offers a family of matrix-export flags and is portable
// into the GUI, where each means what it says. The Console read exactly one
// of them, and used it to gate an unrelated artifact: a run that asked for a
// height matrix got a long-format quality-assurance table and no matrix, with
// nothing said about either. The flags are honoured here.
var matrixFolder = String.IsNullOrWhiteSpace(storage.Parameter.ExportFolderPath)
? outputFolder
: storage.Parameter.ExportFolderPath;
var requestedMatrices = new List<(bool Requested, string ExportType, string Suffix)> {
(storage.Parameter.IsHeightMatrixExport, "Height", "_Height.txt"),
(storage.Parameter.IsNormalizedMatrixExport, "Normalized height", "_NormalizedHeight.txt"),
(storage.Parameter.IsPeakAreaMatrixExport, "Area", "_Area.txt"),
(storage.Parameter.IsRetentionTimeMatrixExport, "RT", "_RT.txt"),
(storage.Parameter.IsMassMatrixExport, "MZ", "_MZ.txt"),
(storage.Parameter.IsSnMatrixExport, "SN", "_SN.txt"),
};
if (requestedMatrices.Any(item => item.Requested)) {
Directory.CreateDirectory(matrixFolder);
var matrixStats = new[] { StatsValue.Average, StatsValue.Stdev };
foreach (var (_, exportType, suffix) in requestedMatrices.Where(item => item.Requested)) {
var matrixFile = Path.Combine(matrixFolder, alignmentFile.FileName + suffix);
using (var matrixStream = File.Open(matrixFile, FileMode.Create, FileAccess.Write)) {
new AlignmentCSVExporter().Export(
matrixStream, result.AlignmentSpotProperties, align_decResults, files,
new MulticlassFileMetaAccessor(0), align_accessor,
new LegacyQuantValueAccessor(exportType, storage.Parameter), matrixStats);
}
Console.WriteLine($"{exportType} matrix: {matrixFile}");
}
}

if (storage.Parameter.IsHeightMatrixExport) {
var qaOutputFolder = String.IsNullOrWhiteSpace(storage.Parameter.ExportFolderPath)
? outputFolder
: storage.Parameter.ExportFolderPath;
var qaOutputFolder = matrixFolder;
Directory.CreateDirectory(qaOutputFolder);
var qaOutputFile = Path.Combine(qaOutputFolder, alignmentFile.FileName + ".qa.tsv");
using var qaStream = File.Open(qaOutputFile, FileMode.Create, FileAccess.Write);
Expand All @@ -246,6 +275,9 @@ IQuantValueAccessor CreateQuantAccessor(string exportType) => alignmentLightPeak
("SN", CreateQuantAccessor("SN")),
("MSMS", CreateQuantAccessor("MSMS")),
("Reference matched", CreateQuantAccessor("Reference matched")));
// Written beside the height matrix rather than instead of it: it is the
// same peak heights in long form, with the per-file columns the QA step
// reads. It follows the height request because no parameter names it.
Console.WriteLine($"LC-MS quality-assurance matrix: {qaOutputFile}");
}

Expand Down
44 changes: 43 additions & 1 deletion tests/MSDIAL5/MsdialCoreTestApp/Process/MainProcess.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CompMs.App.MsdialConsole.Process.MoleculerNetworking;
using CompMs.App.MsdialConsole.Process.MoleculerNetworking;
using CompMs.App.MsdialConsole.Properties;
using CompMs.Common.Enum;
using CompMs.Common.Extension;
Expand Down Expand Up @@ -443,6 +443,48 @@ public static void SetMsnCommand(Command root) {
root.Add(cmd);
}

public static void SetNormalizeCommand(Command root) {
var cmd = new Command(
"normalize",
"Normalize an aligned result against internal standards and export the matrix");
var input = new Option<FileInfo>("--input", "-i") { Required = true };
input.Description = "MS-DIAL project file holding the alignment to normalize";
var standards = new Option<FileInfo>("--standards", "-s") { Required = true };
standards.Description = "Table of internal standards: StandardName, TargetClass, Concentration, optional PeakID, DilutionRate, MolecularWeight";
var output = new Option<DirectoryInfo>("--output", "-o") { Required = true };
output.Description = "Directory to write the raw and normalized alignment matrices into";
var unit = new Option<IonAbundanceUnit>("--unit", "-u") {
DefaultValueFactory = _ => IonAbundanceUnit.NormalizedByInternalStandardPeakHeight,
};
unit.Description = "Unit of the normalized abundance, e.g. pmol_per_microL_plasma";
var alignment = new Option<int>("--alignment") { DefaultValueFactory = _ => 0 };
alignment.Description = "Index of the alignment result within the project";
var dilution = new Option<bool>("--apply-dilution-factor", "-d");
dilution.Description = "Divide by each file's dilution factor after normalizing";
var allowUnresolved = new Option<bool>("--allow-unresolved-standards");
allowUnresolved.Description = "Continue when a standard is not found, leaving its classes without a concentration";
var allowMismatched = new Option<bool>("--allow-mismatched-peak-ids");
allowMismatched.Description = "Continue when a standard's alignment ID holds a different compound";
cmd.Options.Add(input);
cmd.Options.Add(standards);
cmd.Options.Add(output);
cmd.Options.Add(unit);
cmd.Options.Add(alignment);
cmd.Options.Add(dilution);
cmd.Options.Add(allowUnresolved);
cmd.Options.Add(allowMismatched);
cmd.SetAction(parseResult => new NormalizationProcess().Run(
parseResult.GetRequiredValue(input),
parseResult.GetRequiredValue(standards),
parseResult.GetRequiredValue(output),
parseResult.GetValue(unit),
parseResult.GetValue(alignment),
parseResult.GetValue(dilution),
parseResult.GetValue(allowUnresolved),
parseResult.GetValue(allowMismatched)));
root.Subcommands.Add(cmd);
}

public static void SetEicCommand(Command root) {
var eic = new Command("eic", "Export extracted ion chromatograms");
var raw = new Command("raw", "Export EICs from a raw data file");
Expand Down
Loading
Loading