public NmmScanData(NmmFileName fileNameObject) { MetaData = new ScanMetaData(); MetaData.AddDataFrom(fileNameObject); MetaData.AddDataFrom(new NmmInstrumentCharacteristcs()); // first read the description file so we can check if requested scan index is valid MetaData.AddDataFrom(new NmmDescriptionFileParser(fileNameObject)); // now perform the scan index checks if (MetaData.NumberOfScans > 1) { int scanIndex = fileNameObject.ScanIndex; if (scanIndex == 0) { scanIndex = 1; } if (scanIndex > MetaData.NumberOfScans) { scanIndex = MetaData.NumberOfScans; } fileNameObject.SetScanIndex(scanIndex); MetaData.AddDataFrom(fileNameObject); } // at this stage the scan index of fileNameObject should be ok MetaData.AddDataFrom(new NmmIndFileParser(fileNameObject)); MetaData.AddDataFrom(new NmmEnvironmentData(fileNameObject)); topographyData = new TopographyData(MetaData); nmmDat = new NmmDatFileParser(fileNameObject); LoadTopographyData(); // contrary to similar classes of this library, the dat-files are not closed implicitely nmmDat.Close(); // populate MetaData with absolute origin and center coordinates PopulateFieldOriginAndCenter(); HeydemannCorrectionApplied = false; HeydemannCorrectionSpan = 0.0; }
static void Main(string[] args) { // parse command line arguments var options = new Options(); if (!CommandLine.Parser.Default.ParseArgumentsStrict(args, options)) { Console.WriteLine("*** ParseArgumentsStrict returned false"); } if (options.BeQuiet == true) { ConsoleUI.BeSilent(); } else { ConsoleUI.BeVerbatim(); } ConsoleUI.Welcome(); // get the filename(s) string[] fileNames = options.ListOfFileNames.ToArray(); if (fileNames.Length == 0) { ConsoleUI.ErrorExit("!Missing input file", 1); } // read all relevant scan data ConsoleUI.StartOperation("Reading and evaluating files"); NmmFileName nmmFileName = new NmmFileName(fileNames[0]); nmmFileName.SetScanIndex(options.ScanIndex); NmmScanData nmmScanData = new NmmScanData(nmmFileName); ConsoleUI.Done(); if (options.DoHeydemann) { nmmScanData.ApplyHeydemannCorrection(); if (nmmScanData.HeydemannCorrectionApplied) { ConsoleUI.WriteLine($"Heydemann correction applied, span {nmmScanData.HeydemannCorrectionSpan * 1e9:F1} nm"); } else { ConsoleUI.WriteLine($"Heydemann correction not successful."); } } // some checks of the provided CLA options if (options.ProfileIndex < 0) { options.ProfileIndex = 0; } if (options.ProfileIndex > nmmScanData.MetaData.NumberOfProfiles) { options.ProfileIndex = nmmScanData.MetaData.NumberOfProfiles; } TopographyProcessType topographyProcessType = TopographyProcessType.ForwardOnly; if (options.UseBack) { topographyProcessType = TopographyProcessType.BackwardOnly; } if (options.UseBoth) { topographyProcessType = TopographyProcessType.Average; } if (options.UseDiff) { topographyProcessType = TopographyProcessType.Difference; } if (nmmScanData.MetaData.ScanStatus == ScanDirectionStatus.ForwardOnly) { if (topographyProcessType != TopographyProcessType.ForwardOnly) { ConsoleUI.WriteLine("No backward scan data present, switching to forward only."); } topographyProcessType = TopographyProcessType.ForwardOnly; } if (nmmScanData.MetaData.ScanStatus == ScanDirectionStatus.Unknown) { ConsoleUI.ErrorExit("!Unknown scan type", 2); } if (nmmScanData.MetaData.ScanStatus == ScanDirectionStatus.NoData) { ConsoleUI.ErrorExit("!No scan data present", 3); } // now we can start to sort and format everything we need BcrWriter bcr = new BcrWriter(); bcr.Relaxed = !options.Strict; // overrules Relaxed ConsoleUI.WriteLine(bcr.Relaxed ? "Relaxed formatting" : "Strict formatting"); bcr.ForceIsoFormat = options.IsoFormat; ConsoleUI.WriteLine(bcr.ForceIsoFormat ? "ISO 25178-71 format" : "Legacy format"); // ISO 25178-71 file header bcr.CreationDate = nmmScanData.MetaData.CreationDate; bcr.ManufacurerId = nmmScanData.MetaData.InstrumentIdentifier; bcr.NumberOfPointsPerProfile = nmmScanData.MetaData.NumberOfDataPoints; if (options.ProfileIndex == 0) { bcr.NumberOfProfiles = nmmScanData.MetaData.NumberOfProfiles; bcr.YScale = nmmScanData.MetaData.ScanFieldDeltaY; ConsoleUI.WriteLine("Extract complete scanfield"); } else { bcr.NumberOfProfiles = 1; bcr.YScale = 0; ConsoleUI.WriteLine($"Extract single profile {options.ProfileIndex} only"); } bcr.XScale = nmmScanData.MetaData.ScanFieldDeltaX; bcr.ZScale = options.ZScale; // read actual topography data for given channel if (!nmmScanData.ColumnPresent(options.ChannelSymbol)) { ConsoleUI.ErrorExit($"!Channel {options.ChannelSymbol} not in scan data", 5); } double[] rawData = nmmScanData.ExtractProfile(options.ChannelSymbol, options.ProfileIndex, topographyProcessType); // level data DataLeveling levelObject; if (options.ProfileIndex == 0) { levelObject = new DataLeveling(rawData, nmmScanData.MetaData.NumberOfDataPoints, nmmScanData.MetaData.NumberOfProfiles); } else { levelObject = new DataLeveling(rawData, nmmScanData.MetaData.NumberOfDataPoints); } levelObject.BiasValue = options.Bias * 1.0e-6; // bias is given in µm on the command line double[] leveledTopographyData = levelObject.LevelData(MapOptionToReference(options.ReferenceMode)); // generate a dictionary with all relevant metadata for the ISO 25178-71 file trailer Dictionary <string, string> bcrMetaData = new Dictionary <string, string>(); bcrMetaData.Add("InputFile", nmmScanData.MetaData.BaseFileName); bcrMetaData.Add("ConvertedBy", $"{ConsoleUI.Title} version {ConsoleUI.Version}"); bcrMetaData.Add("UserComment", options.UserComment); bcrMetaData.Add("OperatorName", nmmScanData.MetaData.User); bcrMetaData.Add("Organisation", nmmScanData.MetaData.Organisation); bcrMetaData.Add("SampleIdentifier", nmmScanData.MetaData.SampleIdentifier); bcrMetaData.Add("SampleSpecies", nmmScanData.MetaData.SampleSpecies); bcrMetaData.Add("SampleSpecification", nmmScanData.MetaData.SampleSpecification); bcrMetaData.Add("SPMtechnique", nmmScanData.MetaData.SpmTechnique); bcrMetaData.Add("Probe", nmmScanData.MetaData.ProbeDesignation); bcrMetaData.Add("ZAxisSource", options.ChannelSymbol); bcrMetaData.Add("Trace", topographyProcessType.ToString()); if (options.ProfileIndex != 0) { bcrMetaData.Add("NumProfiles", $"{nmmScanData.MetaData.NumberOfProfiles}"); bcrMetaData.Add("ExtractedProfile", $"{options.ProfileIndex}"); } if (nmmScanData.MetaData.NumberOfScans > 1) { bcrMetaData.Add("NumberOfScans", $"{nmmScanData.MetaData.NumberOfScans}"); bcrMetaData.Add("Scan", $"{nmmScanData.MetaData.ScanIndex}"); } bcrMetaData.Add("ReferenceDatum", levelObject.LevelModeDescription); if (nmmScanData.HeydemannCorrectionApplied) { bcrMetaData.Add("HeydemannCorrection", $"Span {nmmScanData.HeydemannCorrectionSpan * 1e9:F1} nm"); } bcrMetaData.Add("EnvironmentMode", nmmScanData.MetaData.EnvironmentMode); bcrMetaData.Add("SampleTemperature", $"{nmmScanData.MetaData.SampleTemperature:F3} oC"); bcrMetaData.Add("AirTemperature", $"{nmmScanData.MetaData.AirTemperature:F3} oC"); bcrMetaData.Add("AirPressure", $"{nmmScanData.MetaData.BarometricPressure:F0} Pa"); bcrMetaData.Add("AirHumidity", $"{nmmScanData.MetaData.RelativeHumidity:F1} %"); bcrMetaData.Add("TemperatureGradient", $"{nmmScanData.MetaData.AirTemperatureGradient:F3} oC"); bcrMetaData.Add("TemperatureRange", $"{nmmScanData.MetaData.AirTemperatureDrift:F3} oC"); bcrMetaData.Add("ScanSpeed", $"{nmmScanData.MetaData.ScanSpeed * 1e6} um/s"); bcrMetaData.Add("AngularOrientation", $"{nmmScanData.MetaData.ScanFieldRotation:F3} grad"); bcrMetaData.Add("ScanFieldCenterX", $"{nmmScanData.MetaData.ScanFieldCenterZ * 1000:F3} mm"); bcrMetaData.Add("ScanFieldCenterY", $"{nmmScanData.MetaData.ScanFieldCenterY * 1000:F3} mm"); bcrMetaData.Add("ScanFieldCenterZ", $"{nmmScanData.MetaData.ScanFieldCenterZ * 1000:F3} mm"); bcrMetaData.Add("ScanFieldOriginX", $"{nmmScanData.MetaData.ScanFieldOriginX} m"); bcrMetaData.Add("ScanFieldOriginY", $"{nmmScanData.MetaData.ScanFieldOriginY} m"); bcrMetaData.Add("ScanFieldOriginZ", $"{nmmScanData.MetaData.ScanFieldOriginZ} m"); bcrMetaData.Add("ScanDuration", $"{nmmScanData.MetaData.ScanDuration.TotalSeconds:F0} s"); bcrMetaData.Add("GlitchedDataPoints", $"{nmmScanData.MetaData.NumberOfGlitchedDataPoints}"); bcrMetaData.Add("SpuriousDataLines", $"{nmmScanData.MetaData.SpuriousDataLines}"); for (int i = 0; i < nmmScanData.MetaData.ScanComments.Count; i++) { bcrMetaData.Add($"ScanComment{i + 1}", nmmScanData.MetaData.ScanComments[i]); } // ISO 25178-71 main section bcr.PrepareMainSection(leveledTopographyData); // ISO 25178-71 file trailer bcr.PrepareTrailerSection(bcrMetaData); // now generate output string outFileName; if (fileNames.Length >= 2) { outFileName = fileNames[1]; } else { outFileName = nmmFileName.GetFreeFileNameWithIndex("sdf"); } ConsoleUI.WritingFile(outFileName); if (!bcr.WriteToFile(outFileName)) { ConsoleUI.Abort(); ConsoleUI.ErrorExit("!could not write file", 4); } ConsoleUI.Done(); }
public static void Main(string[] args) { Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); if (!CommandLine.Parser.Default.ParseArgumentsStrict(args, options)) { Console.WriteLine("*** ParseArgumentsStrict returned false"); } // consume the verbosity option if (options.BeQuiet == true) { ConsoleUI.BeSilent(); } else { ConsoleUI.BeVerbatim(); } // print a welcome message ConsoleUI.Welcome(); ConsoleUI.WriteLine(); // get the filename(s) fileNames = options.ListOfFileNames.ToArray(); if (fileNames.Length == 0) { ConsoleUI.ErrorExit("!Missing input file", 1); } // if no filetype was choosen, use default one if (!(options.convertBcr || options.convertPrDe || options.convertPrEn || options.convertPrf || options.convertSig || options.convertSmd || options.convertTxt || options.convertCsv || options.convertX3p)) { options.convertSig = true; // this should be convertBcr in the future } // read all relevant scan data ConsoleUI.StartOperation("Reading and evaluating files"); nmmFileNameObject = new NmmFileName(fileNames[0]); nmmFileNameObject.SetScanIndex(options.ScanIndex); theData = new NmmScanData(nmmFileNameObject); ConsoleUI.Done(); ConsoleUI.WriteLine(); if (options.DoHeydemann) { theData.ApplyHeydemannCorrection(); if (theData.HeydemannCorrectionApplied) { ConsoleUI.WriteLine($"Heydemann correction applied, span {theData.HeydemannCorrectionSpan * 1e9:F1} nm"); } else { ConsoleUI.WriteLine($"Heydemann correction not successful."); } ConsoleUI.WriteLine(); } // some checks of the provided CLA options if (options.ProfileIndex < 0) { options.ProfileIndex = 0; // automatically extract all profiles } if (options.ProfileIndex > theData.MetaData.NumberOfProfiles) { options.ProfileIndex = theData.MetaData.NumberOfProfiles; } topographyProcessType = TopographyProcessType.ForwardOnly; if (options.UseBack) { topographyProcessType = TopographyProcessType.BackwardOnly; } if (options.UseBoth) { topographyProcessType = TopographyProcessType.Average; } if (options.UseDiff) { topographyProcessType = TopographyProcessType.Difference; } if (theData.MetaData.ScanStatus == ScanDirectionStatus.ForwardOnly) { if (topographyProcessType != TopographyProcessType.ForwardOnly) { ConsoleUI.WriteLine("No backward scan data present, switching to forward only."); } topographyProcessType = TopographyProcessType.ForwardOnly; } if (theData.MetaData.ScanStatus == ScanDirectionStatus.Unknown) { ConsoleUI.ErrorExit("!Unknown scan type", 2); } if (theData.MetaData.ScanStatus == ScanDirectionStatus.NoData) { ConsoleUI.ErrorExit("!No scan data present", 3); } // now we can start to sort and format everything we need prf.CreationDate = theData.MetaData.CreationDate; prf.SampleIdentification = theData.MetaData.SampleIdentifier; prf.DeltaX = theData.MetaData.ScanFieldDeltaX * 1e6; prf.UserComment = options.UserComment; // extract the requested profile if (options.ProfileIndex != 0) { ProcessSingleProfile(options.ProfileIndex); return; } // (ProfileIndex == 0) => extract all profiles for (int i = 1; i <= theData.MetaData.NumberOfProfiles; i++) { ProcessSingleProfile(i); } }
public static void Main(string[] args) { CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; // parse command line arguments if (!CommandLine.Parser.Default.ParseArgumentsStrict(args, options)) { Console.WriteLine("*** ParseArgumentsStrict returned false"); } // consume the verbosity option if (options.BeQuiet == true) { ConsoleUI.BeSilent(); } else { ConsoleUI.BeVerbatim(); } // print a welcome message ConsoleUI.Welcome(); ConsoleUI.WriteLine(); // get the filename(s) fileNames = options.ListOfFileNames.ToArray(); if (fileNames.Length == 0) { ConsoleUI.ErrorExit("!Missing input file name", 1); } // read all relevant scan data ConsoleUI.StartOperation("Reading NMM scan files"); nmmFileNameObject = new NmmFileName(fileNames[0]); nmmFileNameObject.SetScanIndex(options.ScanIndex); theData = new NmmScanData(nmmFileNameObject); ConsoleUI.Done(); // check if data present if (theData.MetaData.ScanStatus == ScanDirectionStatus.Unknown) { ConsoleUI.ErrorExit("!Unknown scan type", 4); } if (theData.MetaData.ScanStatus == ScanDirectionStatus.NoData) { ConsoleUI.ErrorExit("!No scan data present", 5); } // Check if requested channels are present in raw data if (!theData.ColumnPresent(options.XAxisDesignation)) { ConsoleUI.ErrorExit($"!Requested channel {options.XAxisDesignation} not in data files", 2); } if (!theData.ColumnPresent(options.ZAxisDesignation)) { ConsoleUI.ErrorExit($"!Requested channel {options.ZAxisDesignation} not in data files", 3); } // one must avoid referencing to an line outside of expected number of line marks if (options.RefLine < 0) { options.RefLine = 0; } if (options.RefLine >= options.ExpectedTargets) { options.RefLine = options.ExpectedTargets - 1; } // output scan files peculiaritues ConsoleUI.WriteLine(); ConsoleUI.WriteLine($"SpuriousDataLines: {theData.MetaData.SpuriousDataLines}"); ConsoleUI.WriteLine($"NumberOfGlitchedDataPoints: {theData.MetaData.NumberOfGlitchedDataPoints}"); // some screen output ConsoleUI.WriteLine(); ConsoleUI.WriteLine($"{theData.MetaData.NumberOfDataPoints} data lines with {theData.MetaData.NumberOfColumnsInFile} channels, organized in {theData.MetaData.NumberOfProfiles} profiles"); ConsoleUI.WriteLine($"x-axis channel: {options.XAxisDesignation}"); ConsoleUI.WriteLine($"z-axis channel: {options.ZAxisDesignation}"); ConsoleUI.WriteLine($"Threshold: {options.Threshold}"); ConsoleUI.WriteLine($"Morphological filter parameter: {options.Morpho}"); if (options.LineScale) { ConsoleUI.WriteLine($"Expected number of line marks: {options.ExpectedTargets}"); ConsoleUI.WriteLine($"Nominal scale division: {options.NominalDivision} um"); } ConsoleUI.WriteLine(); // evaluate the intensities for ALL profiles == the whole scan field ConsoleUI.StartOperation("Classifying intensity data"); double[] luminanceField = theData.ExtractProfile(options.ZAxisDesignation, 0, TopographyProcessType.ForwardOnly); IntensityEvaluator eval = new IntensityEvaluator(luminanceField); ConsoleUI.Done(); ConsoleUI.WriteLine(); ConsoleUI.WriteLine($"Intensity range from {eval.MinIntensity} to {eval.MaxIntensity}"); ConsoleUI.WriteLine($"Estimated bounds from {eval.LowerBound} to {eval.UpperBound}"); double relativeSpan = (double)(eval.UpperBound - eval.LowerBound) / (double)(eval.MaxIntensity - eval.MinIntensity) * 100.0; ConsoleUI.WriteLine($"({relativeSpan:F1} % of full range)"); ConsoleUI.WriteLine(); // prepare object for the overall dimensional result LineScale result = new LineScale(options.ExpectedTargets); result.SetNominalValues(options.NominalDivision, options.RefLine); // wraps profiles extracted from NMM files to a more abstract structure: profiles List <IntensityProfile> profilesList = new List <IntensityProfile>(); WarpNmmProfiles(TopographyProcessType.ForwardOnly, profilesList); if (theData.MetaData.ScanStatus == ScanDirectionStatus.ForwardAndBackward || theData.MetaData.ScanStatus == ScanDirectionStatus.ForwardAndBackwardJustified) { WarpNmmProfiles(TopographyProcessType.BackwardOnly, profilesList); } IntensityProfile[] profiles = profilesList.ToArray(); // the loop over all profiles for (int profileIndex = 0; profileIndex < profiles.Length; profileIndex++) { if (!profiles[profileIndex].IsValid) { ConsoleUI.ErrorExit($"!Profile {profileIndex} invalid", 6); } Classifier classifier = new Classifier(profiles[profileIndex].Zvalues); int[] skeleton = classifier.GetSegmentedProfile(options.Threshold, eval.LowerBound, eval.UpperBound); MorphoFilter filter = new MorphoFilter(skeleton); skeleton = filter.FilterWithParameter(options.Morpho); LineDetector marks = new LineDetector(skeleton, profiles[profileIndex].Xvalues); if (options.LineScale) { ConsoleUI.WriteLine($"profile: {profileIndex+1,3} with {marks.LineCount} line marks {(marks.LineCount != options.ExpectedTargets ? "*" : " ")}"); } result.UpdateSample(marks.LineMarks, options.RefLine); if (options.EdgeOnly) { ConsoleUI.WriteLine($"profile: {profileIndex+1,3} with L:{marks.LeftEdgePositions.Count} R:{marks.RightEdgePositions.Count}"); EdgesOnlyOutputToStringBuilder(marks, profileIndex); } } #region output // prepare output string outFormater = $"F{options.Precision}"; StringBuilder sb = new StringBuilder(); sb.AppendLine($"{ConsoleUI.WelcomeMessage}"); sb.AppendLine($"InputFile = {theData.MetaData.BaseFileName}"); // section for sample specific metadata sb.AppendLine($"SampleIdentifier = {theData.MetaData.SampleIdentifier}"); sb.AppendLine($"SampleSpecies = {theData.MetaData.SampleSpecies}"); sb.AppendLine($"SampleSpecification = {theData.MetaData.SampleSpecification}"); if (options.LineScale) { sb.AppendLine($"ExpectedLineMarks = {options.ExpectedTargets}"); sb.AppendLine($"NominalDivision = {options.NominalDivision} µm"); sb.AppendLine($"ScaleType = {result.ScaleType}"); } sb.AppendLine($"ThermalExpansion = {options.Alpha.ToString("E2")} 1/K"); // scan file specific data sb.AppendLine($"NumberOfScans = {theData.MetaData.NumberOfScans}"); sb.AppendLine($"ScanIndex = {theData.MetaData.ScanIndex}"); sb.AppendLine($"PointsPerProfile = {theData.MetaData.NumberOfDataPoints}"); sb.AppendLine($"Profiles = {theData.MetaData.NumberOfProfiles}"); sb.AppendLine($"InputChannels = {theData.MetaData.NumberOfColumnsInFile}"); sb.AppendLine($"PointSpacing = {(theData.MetaData.ScanFieldDeltaX * 1e6).ToString("F4")} µm"); sb.AppendLine($"ProfileSpacing = {(theData.MetaData.ScanFieldDeltaY * 1e6).ToString("F4")} µm"); sb.AppendLine($"ScanFieldCenterX = {theData.MetaData.ScanFieldCenterX * 1000:F1} mm"); sb.AppendLine($"ScanFieldCenterY = {theData.MetaData.ScanFieldCenterY * 1000:F1} mm"); sb.AppendLine($"ScanFieldCenterZ = {theData.MetaData.ScanFieldCenterZ * 1000:F1} mm"); sb.AppendLine($"AngularOrientation = {theData.MetaData.ScanFieldRotation:F2}°"); sb.AppendLine($"ScanSpeed = {theData.MetaData.ScanSpeed} µm/s"); sb.AppendLine($"GlitchedDataPoints = {theData.MetaData.NumberOfGlitchedDataPoints}"); sb.AppendLine($"SpuriousDataLines = {theData.MetaData.SpuriousDataLines}"); sb.AppendLine($"Probe = {theData.MetaData.ProbeDesignation}"); // evaluation parameters, user supplied sb.AppendLine($"X-AxisChannel = {options.XAxisDesignation}"); sb.AppendLine($"Z-AxisChannel = {options.ZAxisDesignation}"); sb.AppendLine($"Threshold = {options.Threshold}"); sb.AppendLine($"FilterParameter = {options.Morpho}"); if (options.LineScale) { sb.AppendLine($"ReferencedToLine = {options.RefLine}"); double maximumThermalCorrection = ThermalCorrection(result.LineMarks.Last().NominalPosition) - ThermalCorrection(result.LineMarks.First().NominalPosition); sb.AppendLine($"MaxThermalCorrection = {maximumThermalCorrection:F3} µm"); } // auxiliary values sb.AppendLine($"MinimumIntensity = {eval.MinIntensity}"); sb.AppendLine($"MaximumIntensity = {eval.MaxIntensity}"); sb.AppendLine($"LowerPlateau = {eval.LowerBound}"); sb.AppendLine($"UpperPlateau = {eval.UpperBound}"); sb.AppendLine($"RelativeSpan = {relativeSpan:F1} %"); if (options.LineScale) { sb.AppendLine($"EvaluatedProfiles = {result.SampleSize}"); } // environmental data sb.AppendLine($"SampleTemperature = {theData.MetaData.SampleTemperature.ToString("F3")} °C"); sb.AppendLine($"AirTemperature = {theData.MetaData.AirTemperature.ToString("F3")} °C"); sb.AppendLine($"AirPressure = {theData.MetaData.BarometricPressure.ToString("F0")} Pa"); sb.AppendLine($"AirHumidity = {theData.MetaData.RelativeHumidity.ToString("F1")} %"); sb.AppendLine("======================"); if (options.LineScale) { sb.AppendLine("1 : Line number (tag)"); sb.AppendLine("2 : Nominal value / µm"); sb.AppendLine("3 : Position deviation / µm"); sb.AppendLine("4 : StdDev of line position values / µm"); sb.AppendLine("5 : Range of line position values / µm"); sb.AppendLine("6 : Line width / µm"); sb.AppendLine("7 : StdDev of line widths / µm"); sb.AppendLine("8 : Range of line widths / µm"); sb.AppendLine("@@@@"); if (result.SampleSize == 0) { sb.AppendLine("*** No matching intensity pattern found ***"); } else { foreach (var line in result.LineMarks) { double deltaL = ThermalCorrection(line.NominalPosition); sb.AppendLine($"{line.Tag.ToString().PadLeft(5)}" + $"{line.NominalPosition.ToString("F0").PadLeft(10)}" + $"{(line.Deviation + deltaL).ToString(outFormater).PadLeft(10)}" + $"{line.LineCenterStdDev.ToString(outFormater).PadLeft(10)}" + $"{line.LineCenterRange.ToString(outFormater).PadLeft(10)}" + $"{line.AverageLineWidth.ToString(outFormater).PadLeft(10)}" + $"{line.LineWidthStdDev.ToString(outFormater).PadLeft(10)}" + $"{(line.LineWidthRange).ToString(outFormater).PadLeft(10)}"); } } } if (options.EdgeOnly) { sb.Append(edgesOnlyOutput); } #endregion #region File output string outFileName; if (fileNames.Length >= 2) { outFileName = fileNames[1]; // path and extension must be explicitely given } else { outFileName = nmmFileNameObject.GetFreeFileNameWithIndex(".prn"); // extension will be added by WriteToFile() } ConsoleUI.WriteLine(); ConsoleUI.WritingFile(outFileName); StreamWriter hOutFile = File.CreateText(outFileName); hOutFile.Write(sb); hOutFile.Close(); ConsoleUI.Done(); #endregion }