/// <summary> /// Converts a DataTable of events to a datatable where one row = one minute of indices /// </summary> /// <param name="dt"></param> /// <returns></returns> public DataTable ConvertEvents2Indices(DataTable dt, TimeSpan unitTime, TimeSpan sourceDuration, double scoreThreshold) { double units = sourceDuration.TotalSeconds / unitTime.TotalSeconds; int unitCount = (int)(units / 1); //get whole minutes if (units % 1 > 0.0) { unitCount += 1; //add fractional minute } int[] eventsPerUnitTime = new int[unitCount]; //to store event counts int[] bigEvsPerUnitTime = new int[unitCount]; //to store counts of high scoring events foreach (DataRow ev in dt.Rows) { double eventStart = (double)ev[AnalysisKeys.EventStartSec]; double eventScore = (double)ev[AnalysisKeys.EventNormscore]; int timeUnit = (int)(eventStart / unitTime.TotalSeconds); eventsPerUnitTime[timeUnit]++; if (eventScore > scoreThreshold) { bigEvsPerUnitTime[timeUnit]++; } } string[] headers = { AnalysisKeys.KeyStartMinute, AnalysisKeys.EventTotal, ("#Ev>" + scoreThreshold) }; Type[] types = { typeof(int), typeof(int), typeof(int) }; var newtable = DataTableTools.CreateTable(headers, types); for (int i = 0; i < eventsPerUnitTime.Length; i++) { int unitID = (int)(i * unitTime.TotalMinutes); newtable.Rows.Add(unitID, eventsPerUnitTime[i], bigEvsPerUnitTime[i]); } return(newtable); }
} //DrawSonogram() public static DataTable WriteEvents2DataTable(double segmentStartMinute, TimeSpan tsSegmentDuration, List <AcousticEvent> predictedEvents) { if ((predictedEvents == null) || (predictedEvents.Count == 0)) { return(null); } GetTableHeadersAndTypesForEvents(); var dataTable = DataTableTools.CreateTable(EVENT_HEADERS, EVENT_COL_TYPES); //int count = 0; foreach (var ev in predictedEvents) { int segmentStartSec = (int)(segmentStartMinute * 60); int eventStartAbsoluteSec = (int)(segmentStartSec + ev.TimeStart); int eventStartMin = eventStartAbsoluteSec / 60; int eventStartSec = eventStartAbsoluteSec % 60; string segmentDuration = DataTools.Time_ConvertSecs2Mins(tsSegmentDuration.TotalSeconds); DataRow row = dataTable.NewRow(); row[key_START_ABS] = eventStartAbsoluteSec; //EvStartAbsolute - from start of source ifle row[key_START_MIN] = eventStartMin; //EvStartMin row[key_START_SEC] = eventStartSec; //EvStartSec row[key_SEGMENT_DURATION] = tsSegmentDuration.TotalSeconds; //segment Duration in seconds row[key_CALL_DENSITY] = predictedEvents.Count; //Density row[key_CALL_SCORE] = ev.Score; //Score dataTable.Rows.Add(row); // count++; } return(dataTable); }
} //DrawSonogram() public static DataTable WriteEvents2DataTable(List <AcousticEvent> predictedEvents) { if (predictedEvents == null) { return(null); } string[] headers = { AudioAnalysisTools.Keys.EVENT_COUNT, AudioAnalysisTools.Keys.EVENT_START_MIN, AudioAnalysisTools.Keys.EVENT_START_SEC, AudioAnalysisTools.Keys.EVENT_START_ABS, AudioAnalysisTools.Keys.SEGMENT_TIMESPAN, AudioAnalysisTools.Keys.EVENT_DURATION, AudioAnalysisTools.Keys.EVENT_INTENSITY, AudioAnalysisTools.Keys.EVENT_NAME, LSKiwiHelper.key_BANDWIDTH_SCORE, LSKiwiHelper.key_DELTA_SCORE, LSKiwiHelper.key_PEAKS_SNR_SCORE, LSKiwiHelper.key_PEAKS_STD_SCORE, AudioAnalysisTools.Keys.EVENT_SCORE, AudioAnalysisTools.Keys.EVENT_NORMSCORE }; // 1 2 3 4 5 6 7 8 Type[] types = { typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(string), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double) }; var dataTable = DataTableTools.CreateTable(headers, types); if (predictedEvents.Count == 0) { return(dataTable); } foreach (var ev in predictedEvents) { DataRow row = dataTable.NewRow(); row[AudioAnalysisTools.Keys.EVENT_START_ABS] = (double)ev.TimeStart; //Set now - will overwrite later row[AudioAnalysisTools.Keys.EVENT_START_SEC] = (double)ev.TimeStart; //EvStartSec row[AudioAnalysisTools.Keys.EVENT_DURATION] = (double)ev.Duration; //duratio in seconds row[AudioAnalysisTools.Keys.EVENT_INTENSITY] = (double)ev.kiwi_intensityScore; // row[AudioAnalysisTools.Keys.EVENT_NAME] = (string)ev.Name; // row[LSKiwiHelper.key_BANDWIDTH_SCORE] = (double)ev.kiwi_bandWidthScore; row[LSKiwiHelper.key_DELTA_SCORE] = (double)ev.kiwi_deltaPeriodScore; row[LSKiwiHelper.key_PEAKS_SNR_SCORE] = (double)ev.kiwi_snrScore; row[LSKiwiHelper.key_PEAKS_STD_SCORE] = (double)ev.kiwi_sdPeakScore; row[AudioAnalysisTools.Keys.EVENT_NORMSCORE] = (double)ev.ScoreNormalised; row[AudioAnalysisTools.Keys.EVENT_SCORE] = (double)ev.Score; //Score dataTable.Rows.Add(row); } return(dataTable); }
} //DrawSonogram() public static DataTable WriteEvents2DataTable(List <AcousticEvent> predictedEvents) { if (predictedEvents == null) { return(null); } string[] headers = { AnalysisKeys.EventCount, //1 AnalysisKeys.EventStartMin, //2 AnalysisKeys.EventStartSec, //3 AnalysisKeys.EventStartAbs, //4 AnalysisKeys.KeySegmentDuration, //5 AnalysisKeys.EventDuration, //6 //AudioAnalysisTools.Keys.EVENT_INTENSITY, AnalysisKeys.EventName, //7 AnalysisKeys.DominantFrequency, AnalysisKeys.OscillationRate, AnalysisKeys.EventScore, AnalysisKeys.EventNormscore, }; // 1 2 3 4 5 6 7 8 Type[] types = { typeof(int), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(string), typeof(double), typeof(double), typeof(double), typeof(double) }; var dataTable = DataTableTools.CreateTable(headers, types); if (predictedEvents.Count == 0) { return(dataTable); } foreach (var ev in predictedEvents) { DataRow row = dataTable.NewRow(); row[AnalysisKeys.EventStartAbs] = (double)ev.TimeStart; //Set now - will overwrite later row[AnalysisKeys.EventStartSec] = (double)ev.TimeStart; //EvStartSec row[AnalysisKeys.EventDuration] = (double)ev.EventDurationSeconds; //duration in seconds //row[AudioAnalysisTools.Keys.EVENT_INTENSITY] = (double)ev.kiwi_intensityScore; // row[AnalysisKeys.EventName] = (string)ev.Name; // row[AnalysisKeys.DominantFrequency] = (double)ev.DominantFreq; row[AnalysisKeys.OscillationRate] = 1 / (double)ev.Periodicity; row[AnalysisKeys.EventScore] = (double)ev.Score; //Score row[AnalysisKeys.EventNormscore] = (double)ev.ScoreNormalised; dataTable.Rows.Add(row); } return(dataTable); }
} // ProcessCsvFile() /// <summary> /// takes a data table of indices and normalises column values to values in [0,1]. /// </summary> /// <param name="dt"></param> /// <returns></returns> public static DataTable NormaliseColumnsOfDataTable(DataTable dt) { string[] headers = DataTableTools.GetColumnNames(dt); string[] newHeaders = new string[headers.Length]; List <double[]> newColumns = new List <double[]>(); for (int i = 0; i < headers.Length; i++) { double[] values = DataTableTools.Column2ArrayOfDouble(dt, headers[i]); //get list of values if ((values == null) || (values.Length == 0)) { continue; } double min = 0; double max = 1; if (headers[i].Equals(Keys.AV_AMPLITUDE)) { min = -50; max = -5; newColumns.Add(DataTools.NormaliseInZeroOne(values, min, max)); newHeaders[i] = headers[i] + " (-50..-5dB)"; } else //default is to normalise in [0,1] { newColumns.Add(DataTools.normalise(values)); //normalise all values in [0,1] newHeaders[i] = headers[i]; } } //for loop //convert type int to type double due to normalisation Type[] types = new Type[newHeaders.Length]; for (int i = 0; i < newHeaders.Length; i++) { types[i] = typeof(double); } var processedtable = DataTableTools.CreateTable(newHeaders, types, newColumns); return(processedtable); }
} //DrawSonogram() public static DataTable WriteEvents2DataTable(List <AcousticEvent> predictedEvents) { if (predictedEvents == null) { return(null); } string[] headers = { AnalysisKeys.EventCount, AnalysisKeys.EventStartMin, AnalysisKeys.EventStartSec, AnalysisKeys.EventStartAbs, AnalysisKeys.KeySegmentDuration, AnalysisKeys.EventDuration, AnalysisKeys.EventIntensity, AnalysisKeys.EventName, AnalysisKeys.EventScore, AnalysisKeys.EventNormscore, }; // 1 2 3 4 5 6 7 8 Type[] types = { typeof(int), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(string), typeof(double), typeof(double) }; var dataTable = DataTableTools.CreateTable(headers, types); if (predictedEvents.Count == 0) { return(dataTable); } foreach (var ev in predictedEvents) { DataRow row = dataTable.NewRow(); row[AnalysisKeys.EventStartAbs] = (double)ev.TimeStart; //Set now - will overwrite later row[AnalysisKeys.EventStartSec] = (double)ev.TimeStart; //EvStartSec row[AnalysisKeys.EventDuration] = (double)ev.EventDurationSeconds; //duratio in seconds row[AnalysisKeys.EventIntensity] = (double)ev.kiwi_intensityScore; // row[AnalysisKeys.EventName] = (string)ev.Name; // row[AnalysisKeys.EventNormscore] = (double)ev.ScoreNormalised; row[AnalysisKeys.EventScore] = (double)ev.Score; //Score dataTable.Rows.Add(row); } return(dataTable); }
} //AddContext2Table() public Tuple <DataTable, DataTable> ProcessCsvFile(FileInfo fiCsvFile, FileInfo fiConfigFile) { DataTable dt = CsvTools.ReadCSVToTable(fiCsvFile.FullName, true); //get original data table if ((dt == null) || (dt.Rows.Count == 0)) { return(null); } //get its column headers var dtHeaders = new List <string>(); var dtTypes = new List <Type>(); foreach (DataColumn col in dt.Columns) { dtHeaders.Add(col.ColumnName); dtTypes.Add(col.DataType); } List <string> displayHeaders = null; //check if config file contains list of display headers if (fiConfigFile != null) { var configuration = new ConfigDictionary(fiConfigFile.FullName); Dictionary <string, string> configDict = configuration.GetTable(); if (configDict.ContainsKey(Keys.DISPLAY_COLUMNS)) { displayHeaders = configDict[Keys.DISPLAY_COLUMNS].Split(',').ToList(); } } //if config file does not exist or does not contain display headers then use the original headers if (displayHeaders == null) { displayHeaders = dtHeaders; //use existing headers if user supplies none. } //now determine how to display tracks in display datatable Type[] displayTypes = new Type[displayHeaders.Count]; bool[] canDisplay = new bool[displayHeaders.Count]; for (int i = 0; i < displayTypes.Length; i++) { displayTypes[i] = typeof(double); canDisplay[i] = false; if (dtHeaders.Contains(displayHeaders[i])) { canDisplay[i] = true; } } DataTable table2Display = DataTableTools.CreateTable(displayHeaders.ToArray(), displayTypes); foreach (DataRow row in dt.Rows) { DataRow newRow = table2Display.NewRow(); for (int i = 0; i < canDisplay.Length; i++) { if (canDisplay[i]) { newRow[displayHeaders[i]] = row[displayHeaders[i]]; } else { newRow[displayHeaders[i]] = 0.0; } } table2Display.Rows.Add(newRow); } //order the table if possible if (dt.Columns.Contains(AudioAnalysisTools.Keys.EVENT_START_ABS)) { dt = DataTableTools.SortTable(dt, AudioAnalysisTools.Keys.EVENT_START_ABS + " ASC"); } else if (dt.Columns.Contains(AudioAnalysisTools.Keys.EVENT_COUNT)) { dt = DataTableTools.SortTable(dt, AudioAnalysisTools.Keys.EVENT_COUNT + " ASC"); } else if (dt.Columns.Contains(AudioAnalysisTools.Keys.INDICES_COUNT)) { dt = DataTableTools.SortTable(dt, AudioAnalysisTools.Keys.INDICES_COUNT + " ASC"); } else if (dt.Columns.Contains(AudioAnalysisTools.Keys.START_MIN)) { dt = DataTableTools.SortTable(dt, AudioAnalysisTools.Keys.START_MIN + " ASC"); } table2Display = NormaliseColumnsOfDataTable(table2Display); return(System.Tuple.Create(dt, table2Display)); } // ProcessCsvFile()
public Tuple <DataTable, DataTable> ProcessCsvFile(FileInfo fiCsvFile, FileInfo fiConfigFile) { DataTable dt = CsvTools.ReadCSVToTable(fiCsvFile.FullName, true); //get original data table if ((dt == null) || (dt.Rows.Count == 0)) { return(null); } //get its column headers var dtHeaders = new List <string>(); var dtTypes = new List <Type>(); foreach (DataColumn col in dt.Columns) { dtHeaders.Add(col.ColumnName); dtTypes.Add(col.DataType); } List <string> displayHeaders = null; //check if config file contains list of display headers if (fiConfigFile != null) { var configuration = new ConfigDictionary(fiConfigFile.FullName); Dictionary <string, string> configDict = configuration.GetTable(); if (configDict.ContainsKey(AnalysisKeys.DisplayColumns)) { displayHeaders = configDict[AnalysisKeys.DisplayColumns].Split(',').ToList(); } } //if config file does not exist or does not contain display headers then use the original headers if (displayHeaders == null) { displayHeaders = dtHeaders; //use existing headers if user supplies none. } //now determine how to display tracks in display datatable Type[] displayTypes = new Type[displayHeaders.Count]; bool[] canDisplay = new bool[displayHeaders.Count]; for (int i = 0; i < displayTypes.Length; i++) { displayTypes[i] = typeof(double); canDisplay[i] = false; if (dtHeaders.Contains(displayHeaders[i])) { canDisplay[i] = true; } } DataTable table2Display = DataTableTools.CreateTable(displayHeaders.ToArray(), displayTypes); foreach (DataRow row in dt.Rows) { DataRow newRow = table2Display.NewRow(); for (int i = 0; i < canDisplay.Length; i++) { if (canDisplay[i]) { newRow[displayHeaders[i]] = row[displayHeaders[i]]; } else { newRow[displayHeaders[i]] = 0.0; } } table2Display.Rows.Add(newRow); } //order the table if possible if (dt.Columns.Contains(AnalysisKeys.EventStartAbs)) { dt = DataTableTools.SortTable(dt, AnalysisKeys.EventStartAbs + " ASC"); } else if (dt.Columns.Contains(AnalysisKeys.EventCount)) { dt = DataTableTools.SortTable(dt, AnalysisKeys.EventCount + " ASC"); } else if (dt.Columns.Contains(AnalysisKeys.KeyRankOrder)) { dt = DataTableTools.SortTable(dt, AnalysisKeys.KeyRankOrder + " ASC"); } else if (dt.Columns.Contains(AnalysisKeys.KeyStartMinute)) { dt = DataTableTools.SortTable(dt, AnalysisKeys.KeyStartMinute + " ASC"); } //this depracted now that use class indexProperties to do normalisation //table2Display = NormaliseColumnsOfDataTable(table2Display); //add in column of weighted indices //bool addColumnOfweightedIndices = true; //if (addColumnOfweightedIndices) //{ // double[] comboWts = IndexCalculate.CalculateComboWeights(); // double[] weightedIndices = IndexCalculate.GetArrayOfWeightedAcousticIndices(dt, comboWts); // string colName = "WeightedIndex"; // DataTableTools.AddColumnOfDoubles2Table(table2Display, colName, weightedIndices); //} return(Tuple.Create(dt, table2Display)); } // ProcessCsvFile()
public static DataTable CalculateRecallPrecision(FileInfo fiPredictions, FileInfo fiGroundTruth) { string header_trueSex = "truSex"; string header_predictedSex = "preSex"; string header_Harmonics = "Harmonics"; string header_Quality = "Quality"; string[] ROC_HEADERS = { AnalysisKeys.EventStartAbs, //typeof(double) AnalysisKeys.EventStartMin, AnalysisKeys.EventStartSec, AnalysisKeys.EventIntensity, LSKiwiHelper.key_GRID_SCORE, LSKiwiHelper.key_DELTA_SCORE, LSKiwiHelper.key_CHIRP_SCORE, LSKiwiHelper.key_PEAKS_SNR_SCORE, LSKiwiHelper.key_BANDWIDTH_SCORE, AnalysisKeys.EventScore, AnalysisKeys.EventNormscore, header_predictedSex, header_Harmonics, header_trueSex, header_Quality, "TP", "FP","FN", }; //string[] ROC_HEADERS = { "startSec", "min", "secOffset", "intensity", "gridScore", "deltaScore", "chirpScore", "PeaksSnrScore" "bwScore", "comboScore", "normScore", "preSex", "Harmonics", "truSex", "Quality", "TP", "FP", "FN"}; Type[] ROC_COL_TYPES = { typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(string), typeof(int), typeof(string), typeof(int), typeof(int), typeof(int), typeof(int) }; //ANDREW'S HEADERS: Selection, View, Channel, Begin Time (s), End Time (s), Low Freq (Hz), High Freq (Hz), Begin File, Species, Sex, Harmonics, Quality Type[] ANDREWS_TYPES = { typeof(string), typeof(string), typeof(int), typeof(double), typeof(double), typeof(double), typeof(double), typeof(string), typeof(string), typeof(string), typeof(int), typeof(int) }; bool isFirstRowHeader = true; var dtGroundTruth = CsvTools.ReadCSVToTable(fiGroundTruth.FullName, isFirstRowHeader, ANDREWS_TYPES); var dtPredictions = CsvTools.ReadCSVToTable(fiPredictions.FullName, isFirstRowHeader); dtPredictions = LSKiwiHelper.MergeAdjacentPredictions(dtPredictions); //var weights = LSKiwiHelper.GetFeatureWeights(); //to try different weightings. //string colName = "Species"; //string value = "LSK"; //DataTableTools.DeleteRows(dtADResults, colName, value); //delete rows where Species name is not "LSK" var dtOutput = DataTableTools.CreateTable(ROC_HEADERS, ROC_COL_TYPES); int TP = 0; int FP = 0; int FN = 0; foreach (DataRow myRow in dtPredictions.Rows) { double myStartSecAbs = (double)myRow[AnalysisKeys.EventStartAbs]; double startMin = (double)myRow[AnalysisKeys.EventStartMin]; double startSecOffset = (double)myRow[AnalysisKeys.EventStartSec]; double intensityScore = (double)myRow[AnalysisKeys.EventIntensity]; string name = (string)myRow[AnalysisKeys.EventName]; //double snrScore = (double)myRow[LSKiwiHelper.key_PEAKS_SNR_SCORE]; //double sdPeakScore = (double)myRow[LSKiwiHelper.key_PEAKS_STD_SCORE]; //standard deviation of peak snr's //double periodicityScore = (double)myRow[LSKiwiHelper.key_DELTA_SCORE]; double gridScore = (double)myRow[LSKiwiHelper.key_GRID_SCORE]; double deltScore = (double)myRow[LSKiwiHelper.key_DELTA_SCORE]; double chrpScore = (double)myRow[LSKiwiHelper.key_CHIRP_SCORE]; double peakSnrScore = (double)myRow[LSKiwiHelper.key_PEAKS_SNR_SCORE]; //average peak double bandWidthScore = (double)myRow[LSKiwiHelper.key_BANDWIDTH_SCORE]; //double comboScore = (double)myRow[LSKiwiHelper.key_COMBO_SCORE]; double eventScore = (double)myRow[AnalysisKeys.EventScore]; double normScore = (double)myRow[AnalysisKeys.EventNormscore]; string predictedSex; if (name.EndsWith("(m)")) { predictedSex = "M"; } else if (name.EndsWith("(f)")) { predictedSex = "F"; } else { predictedSex = "???"; } //List<string[]> excludeRules = LSKiwiHelper.GetExcludeRules(); //if (FilterEvent(myRow, excludeRules) == null) continue; DataRow opRow = dtOutput.NewRow(); opRow[AnalysisKeys.EventStartAbs] = myStartSecAbs; opRow[AnalysisKeys.EventStartMin] = startMin; opRow[AnalysisKeys.EventStartSec] = startSecOffset; opRow[AnalysisKeys.EventIntensity] = intensityScore; opRow[LSKiwiHelper.key_GRID_SCORE] = gridScore; opRow[LSKiwiHelper.key_DELTA_SCORE] = deltScore; opRow[LSKiwiHelper.key_CHIRP_SCORE] = chrpScore; opRow[LSKiwiHelper.key_PEAKS_SNR_SCORE] = peakSnrScore; opRow[LSKiwiHelper.key_BANDWIDTH_SCORE] = bandWidthScore; //opRow[LSKiwiHelper.key_COMBO_SCORE] = comboScore; opRow[AnalysisKeys.EventScore] = eventScore; opRow[AnalysisKeys.EventNormscore] = normScore; opRow[header_Quality] = 0; //fill in with blanks opRow[header_predictedSex] = predictedSex; opRow[header_trueSex] = "???"; opRow["TP"] = 0; opRow["FP"] = 0; opRow["FN"] = 0; bool isTP = false; foreach (DataRow trueEvent in dtGroundTruth.Rows) { double trueStart = (double)trueEvent["Begin Time (s)"]; string trueSex = (string)trueEvent["Sex"]; if (trueStart >= myStartSecAbs - 10 && trueStart <= myStartSecAbs + 20 && predictedSex == trueSex) //myStart is close to trueStart AND same sex THERFORE TRUE POSTIIVE { isTP = true; trueEvent["Begin Time (s)"] = double.NaN; //mark so that will not use again opRow[header_Quality] = trueEvent[header_Quality]; opRow[header_trueSex] = trueEvent["Sex"]; opRow[header_Harmonics] = trueEvent[header_Harmonics]; break; } } //foreach - AD loop if (isTP) { opRow["TP"] = 1; TP++; } else //FALSE POSITIVE { opRow["FP"] = 1; FP++; } dtOutput.Rows.Add(opRow); } //foreach - MY loop //now add in the false negatives foreach (DataRow trueEvent in dtGroundTruth.Rows) { double trueStart = (double)trueEvent["Begin Time (s)"]; if (!double.IsNaN(trueStart)) { DataRow row = dtOutput.NewRow(); row[AnalysisKeys.EventStartAbs] = trueStart; row[AnalysisKeys.EventStartMin] = (int)(trueStart / 60); row[AnalysisKeys.EventStartSec] = trueStart % 60; //row[Keys.EVENT_INTENSITY] = 0.0; //row[LSKiwiHelper.key_PEAKS_SNR_SCORE] = 0.0; //row[LSKiwiHelper.key_PEAKS_STD_SCORE] = 0.0; //row[LSKiwiHelper.key_DELTA_SCORE] = 0.0; //row[LSKiwiHelper.key_BANDWIDTH_SCORE] = 0.0; //row[Keys.EVENT_NORMSCORE] = 0.0; //row[LSKiwiHelper.key_NEW_COMBO_SCORE] = 0.0; row[header_predictedSex] = "???"; row["Harmonics"] = trueEvent["Harmonics"]; row["Quality"] = trueEvent["Quality"]; row[header_trueSex] = trueEvent["Sex"]; row["TP"] = 0; row["FP"] = 0; row["FN"] = 1; dtOutput.Rows.Add(row); FN++; } } double recall = TP / (double)(TP + FN); double specificity = TP / (double)(TP + FP); LoggedConsole.WriteLine("TP={0}, FP={1}, FN={2}", TP, FP, FN); LoggedConsole.WriteLine("RECALL={0:f3}, SPECIFICITY={1:f3}", recall, specificity); //use normalised score as the threshold to determine area under ROC curve int totalPositiveCount = dtGroundTruth.Rows.Count; int totalNegativeCount = FP; string sortString = AnalysisKeys.EventNormscore + " desc"; ROCCurve(dtOutput, totalPositiveCount, totalNegativeCount, sortString); //write ROC area above curve return(dtOutput); } //CalculateRecallPrecision()