#![warn(missing_docs)]
#![allow(clippy::too_many_arguments)]
#[cfg(not(target_arch = "wasm32"))]
use std::io::Write;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::mpsc;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;
#[macro_use]
extern crate arrayref;
extern crate num_cpus;
use anyhow::{bail, Error};
#[cfg(not(target_arch = "wasm32"))]
use indicatif::ParallelProgressIterator;
#[cfg(not(target_arch = "wasm32"))]
use rayon::prelude::*;
pub mod cli;
#[cfg(not(target_arch = "wasm32"))]
use crate::cli::*;
#[cfg(target_arch = "wasm32")]
use crate::cli::{InvertedQueryType, DEFAULT_MINCOUNT, DEFAULT_MINQUAL};
#[cfg(not(target_arch = "wasm32"))]
use crate::hashing::HashType;
#[cfg(not(target_arch = "wasm32"))]
use hashbrown::{HashMap, HashSet};
pub mod sketch;
#[cfg(not(target_arch = "wasm32"))]
use crate::sketch::multisketch::MultiSketch;
#[cfg(not(target_arch = "wasm32"))]
use crate::sketch::sketch_datafile::SketchArrayReader;
#[cfg(not(target_arch = "wasm32"))]
use crate::sketch::{num_bins, sketch_files};
pub mod inverted;
use crate::inverted::Inverted;
pub mod distances;
#[cfg(not(target_arch = "wasm32"))]
use crate::distances::*;
pub mod io;
#[cfg(not(target_arch = "wasm32"))]
use crate::io::{
get_input_list, parse_kmers, parse_metadata_info, read_completeness_file, read_subset_names,
reorder_input_files, set_ostream,
};
pub mod structures;
pub mod hashing;
pub mod utils;
use crate::utils::get_progress_bar;
#[cfg(not(target_arch = "wasm32"))]
use crate::utils::strip_sketch_extension;
pub use utils::save_sketch_data;
#[cfg(target_arch = "wasm32")]
pub mod fastx_wasm;
#[cfg(not(target_arch = "wasm32"))]
use std::fs::{File, OpenOptions};
#[cfg(not(target_arch = "wasm32"))]
use std::io::copy;
#[cfg(not(target_arch = "wasm32"))]
use std::io::BufRead;
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
pub const DEFAULT_KMER: usize = 21;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
extern crate console_error_panic_hook;
#[doc(hidden)]
#[cfg(not(target_arch = "wasm32"))]
pub fn main() -> Result<(), Error> {
let args = cli_args();
if args.quiet {
simple_logger::init_with_level(log::Level::Error).unwrap();
} else if args.verbose {
simple_logger::init_with_level(log::Level::Info).unwrap();
} else {
simple_logger::init_with_level(log::Level::Warn).unwrap();
}
let mut print_success = true;
let start = Instant::now();
let result = match &args.command {
Commands::Sketch {
seq_files,
file_list,
concat_fasta,
#[cfg(feature = "3di")]
convert_pdb,
output,
kmers,
sketch_size,
seq_type,
level,
single_strand,
min_count,
min_qual,
threads,
} => {
if *concat_fasta && matches!(*seq_type, HashType::DNA | HashType::PDB) {
panic!("--concat-fasta currently only supported with --seq-type aa");
}
set_threads_with_writer(*threads);
log::info!("Getting input files");
let input_files = get_input_list(file_list, seq_files);
log::info!("Parsed {} samples in input list", input_files.len());
let kmers = parse_kmers(kmers);
let rc = !*single_strand;
let seq_type = if let HashType::AA(_) = seq_type {
HashType::AA(level.clone())
} else {
seq_type.clone()
};
let (_, sketch_bins, _) = num_bins(*sketch_size);
log::info!(
"Running sketching: k:{kmers:?}; sketch_size:{sketch_bins}; seq:{seq_type:?}; threads:{threads}"
);
let mut sketches = sketch_files(
output,
&input_files,
*concat_fasta,
#[cfg(feature = "3di")]
*convert_pdb,
&kmers,
sketch_bins,
&seq_type,
rc,
*min_count,
*min_qual,
args.quiet,
);
let sketch_vec = MultiSketch::new(&mut sketches, sketch_bins, &kmers, seq_type);
sketch_vec
.save_metadata(output)
.expect("Error saving metadata");
Ok(())
}
Commands::Dist {
ref_db,
query_db,
output,
knn,
subset,
kmer,
ani,
threads,
ref_completeness_file,
query_completeness_file,
completeness_cutoff,
} => {
if knn.is_none() {
set_threads_with_writer(*threads);
} else {
check_and_set_threads(*threads);
}
let mut output_file = set_ostream(output);
let ref_db_name = utils::strip_sketch_extension(ref_db);
let mut references = MultiSketch::load_metadata(ref_db_name)
.unwrap_or_else(|_| panic!("Could not read sketch metadata from {ref_db}.skm"));
log::info!("Loading sketch data from {ref_db_name}.skd");
if let Some(subset_file) = subset {
let subset_names = read_subset_names(subset_file);
references.read_sketch_data_block(ref_db_name, &subset_names);
} else {
references.read_sketch_data(ref_db_name);
}
log::info!("Read reference sketches:\n{references:?}");
let n = references.number_samples_loaded();
let ref_completeness_vec: Option<Vec<f64>> =
if let Some(file_path) = ref_completeness_file {
Some(read_completeness_file(file_path, &references)?)
} else {
None
};
let dist_type = set_k(&references, *kmer, *ani).unwrap_or_else(|e| {
panic!("Error setting k size: {e}");
});
let queries = if let Some(query_db_name) = query_db {
log::info!("Loading query sketches from {query_db_name}.skm/.skd");
let queries = MultiSketch::load(query_db_name)
.unwrap_or_else(|_| panic!("Could not read sketch data from {query_db_name}"));
log::info!("Read query sketches:\n{queries:?}");
if references.is_legacy_format() != queries.is_legacy_format() {
bail!(
"Cannot compare reference and query databases with different sketch generations (reference is_legacy={}, query is_legacy={}): legacy (pre-v0.4) and new-format databases use incompatible bin-packing schemes and cannot be directly compared. Please re-sketch both databases with the current version.",
references.is_legacy_format(),
queries.is_legacy_format()
);
}
Some(queries)
} else {
None
};
match queries {
None => {
match knn {
None => {
log::info!("Calculating all ref vs ref distances");
log::info!("Streaming out in long matrix form");
self_dists_all_stream(
&mut output_file,
&references,
n,
dist_type,
args.quiet,
ref_completeness_vec.as_ref(),
*completeness_cutoff,
*threads,
)?;
}
Some(mut nn) => {
if nn >= n {
log::warn!("knn={nn} is higher than number of samples={n}");
nn = n - 1;
}
log::info!("Calculating sparse ref vs ref distances with {nn} nearest neighbours");
let distances = self_dists_knn(
&references,
n,
nn,
dist_type,
args.quiet,
ref_completeness_vec.as_ref(),
*completeness_cutoff,
);
log::info!("Writing out in sparse matrix form");
write!(output_file, "{distances}")
.expect("Error writing output distances");
}
}
}
Some(query_db) => {
let query_completeness_vec: Option<Vec<f64>> =
if let Some(file_path) = query_completeness_file {
Some(read_completeness_file(file_path, &query_db)?)
} else {
None
};
let n_query = query_db.number_samples_loaded();
match knn {
Some(mut nn) => {
if nn > n {
log::warn!(
"knn={nn} is higher than number of reference samples={n}"
);
nn = n;
}
log::info!("Calculating sparse ref vs query distances with {nn} nearest neighbours");
let distances = cross_dists_knn(
&references,
&query_db,
n,
n_query,
nn,
dist_type,
args.quiet,
ref_completeness_vec.as_ref(),
query_completeness_vec.as_ref(),
*completeness_cutoff,
);
log::info!("Writing out in sparse matrix form");
write!(output_file, "{distances}")
.expect("Error writing output distances");
}
None => {
log::info!("Calculating all ref vs query distances");
log::info!("Streaming out in long matrix form");
cross_dists_all_stream(
&mut output_file,
&references,
&query_db,
n,
n_query,
dist_type,
args.quiet,
ref_completeness_vec.as_ref(),
query_completeness_vec.as_ref(),
*completeness_cutoff,
*threads,
)?;
}
}
}
}
Ok(())
}
Commands::Merge { db1, db2, output } => {
let ref_db_name1 = utils::strip_sketch_extension(db1);
let ref_db_name2 = utils::strip_sketch_extension(db2);
log::info!("Reading input metadata");
let mut sketches1: MultiSketch = MultiSketch::load_metadata(ref_db_name1)
.unwrap_or_else(|_| {
panic!("Could not read sketch metadata from {ref_db_name1}.skm")
});
let sketches2: MultiSketch =
MultiSketch::load_metadata(ref_db_name2).unwrap_or_else(|_| {
panic!("Could not read sketch metadata from {ref_db_name2}.skm")
});
if !sketches1.is_compatible_with(&sketches2) {
panic!("Databases are not compatible for merging.")
}
if sketches1.is_legacy_format() {
log::warn!("Merging legacy-format (pre-v0.4) sketch databases; distances calculated against the result will use legacy mode. Resketching advised.");
}
log::info!("Merging metadata to {output}.skm");
let merged_sketch = sketches1.merge_sketches(&sketches2);
merged_sketch
.save_metadata(output)
.unwrap_or_else(|_| panic!("Couldn't save metadata to {output}"));
log::info!("Merging and saving sketch data to {output}.skd");
utils::save_sketch_data(ref_db_name1, ref_db_name2, output)
}
Commands::Inverted { command } => match command {
InvertedCommands::Build {
seq_files,
file_list,
output,
write_skq,
species_names,
metadata,
single_strand,
min_count,
min_qual,
threads,
sketch_size,
kmer_length,
} => {
set_threads_with_writer(*threads);
log::info!("Getting input files");
let input_files: Vec<(String, Vec<String>)> = get_input_list(file_list, seq_files);
log::info!("Parsed {} samples in input list", input_files.len());
let mut differentsamples: HashSet<String> = HashSet::new();
for i in input_files.iter() {
differentsamples.insert(i.0.clone());
}
let (file_order, map_names_labels) = if let Some(species_name_file) = species_names
{
reorder_input_files(&input_files, species_name_file)
} else {
let tmpnamesset = input_files
.iter()
.map(|x| x.0.clone())
.collect::<HashSet<String>>();
if tmpnamesset.len() == input_files.len() {
((0..input_files.len()).collect(), None)
} else {
let mut tmpoutvec: Vec<usize> = vec![0; input_files.len()];
let mut tmpmap: HashMap<String, usize> = HashMap::new();
for (i, name) in tmpnamesset.iter().enumerate() {
tmpmap.insert(name.clone(), i);
}
for i in 0..tmpoutvec.len() {
tmpoutvec[i] = tmpmap[&input_files[i].0];
}
(tmpoutvec, None)
}
};
let species_labels_vec = if let Some(themaplabels) = map_names_labels {
let mut tmpvec: Vec<String> = vec!["".to_string(); differentsamples.len()];
file_order
.iter()
.zip(&input_files)
.for_each(|(idx, (name, _))| {
tmpvec[*idx] = themaplabels.get(name).unwrap_or(&"".to_owned()).clone();
});
Some(tmpvec)
} else {
None
};
let metadata_vec;
if let Some(metadata_file) = metadata {
let tmpdict = parse_metadata_info(metadata_file);
let mut tmpvec: Vec<String> = vec!["".to_string(); differentsamples.len()];
file_order
.iter()
.zip(&input_files)
.for_each(|(idx, (name, _))| tmpvec[*idx] = tmpdict[name].clone());
metadata_vec = Some(tmpvec);
} else {
metadata_vec = None;
};
let skq_file = if *write_skq {
Some(format!("{output}.skq"))
} else {
None
};
let rc = !*single_strand;
let seq_type = &HashType::DNA;
let inverted = Inverted::new(
&input_files,
skq_file,
&file_order,
*kmer_length,
*sketch_size, seq_type,
rc,
*min_count,
*min_qual,
args.quiet,
&metadata_vec,
&species_labels_vec,
);
inverted.save(output)?;
log::info!("Index info:\n{inverted:?}");
Ok(())
}
InvertedCommands::Query {
ski,
seq_files,
file_list,
output,
query_type,
min_count,
min_qual,
threads,
} => {
let mut output_file = set_ostream(output);
let inverted_index = Inverted::load(strip_sketch_extension(ski))?;
log::info!("Read inverted index:\n{inverted_index:?}");
log::info!("Getting input queries");
let input_files: Vec<(String, Vec<String>)> = get_input_list(file_list, seq_files);
log::info!("Parsed {} samples in input query list", input_files.len());
log::info!("Sketching input queries");
set_threads_with_writer(*threads); let (queries, query_names) =
inverted_index.sketch_queries(&input_files, *min_count, *min_qual, args.quiet);
log::info!("Running queries in mode: {query_type}");
write!(output_file, "Query")?;
if *query_type == InvertedQueryType::MatchCount {
for name in inverted_index.sample_names() {
write!(output_file, "\t{name}")?;
}
writeln!(output_file)?;
} else {
writeln!(output_file, "\tMatches")?;
}
let (tx, rx) = mpsc::channel();
let percent = false;
let progress_bar = get_progress_bar(queries.len(), percent, args.quiet);
rayon::scope(|s| {
s.spawn(|_| {
queries
.par_iter()
.progress_with(progress_bar)
.zip(query_names)
.map(|(q, q_name)| match query_type {
InvertedQueryType::MatchCount => {
(q_name, inverted_index.query_against_inverted_index(q))
}
InvertedQueryType::AllBins => {
(q_name, inverted_index.all_shared_bins(q))
}
InvertedQueryType::AnyBins => {
(q_name, inverted_index.any_shared_bins(q))
}
})
.for_each_with(tx, |tx, dists| {
let _ = tx.send(dists);
});
});
});
for (q_name, dist) in rx {
write!(output_file, "{q_name}")?;
if *query_type == InvertedQueryType::MatchCount {
for distance in dist {
write!(output_file, "\t{distance}")?;
}
} else if !dist.is_empty() {
write!(
output_file,
"\t{}",
inverted_index.sample_at(dist[0] as usize)
)?;
for r_name in dist
.iter()
.skip(1)
.map(|idx| inverted_index.sample_at(*idx as usize))
{
write!(output_file, ",{r_name}")?;
}
}
writeln!(output_file)?;
}
Ok(())
}
InvertedCommands::Precluster {
ski,
skd,
count,
output,
mut knn,
ani,
threads,
ref_completeness_file,
completeness_cutoff,
retain_unmatched,
} => {
check_and_set_threads(*threads);
let input_prefix = strip_sketch_extension(ski);
let inverted_index = Inverted::load(input_prefix)?;
if *count {
let prefilter_pairs = inverted_index.any_shared_bin_list(args.quiet);
println!(
"Identified {} prefilter pairs from a max of {}",
prefilter_pairs.len(),
inverted_index.sample_names().len()
* (inverted_index.sample_names().len() - 1)
/ 2
);
} else if let Some(ref_db_input) = skd {
let mut output_file = set_ostream(output);
let skq_filename = &format!("{input_prefix}.skq");
log::info!("Loading queries from {skq_filename}");
let (mmap, bin_stride, kmer_stride, sample_stride) =
(false, 1, 1, inverted_index.sketch_size());
let mut skq_reader = SketchArrayReader::open(
skq_filename,
mmap,
bin_stride,
kmer_stride,
sample_stride,
);
let skq_bins =
skq_reader.read_all_from_skq(sample_stride * inverted_index.sketch_size());
let ref_db_name = utils::strip_sketch_extension(ref_db_input);
log::info!("Loading reference sketches from {ref_db_name}.skm/.skd");
let references = MultiSketch::load(ref_db_name).unwrap_or_else(|_| {
panic!("Could not read sketch data from {ref_db_name}")
});
log::info!("Read reference sketches:\n{references:?}");
let n = references.number_samples_loaded();
if knn >= n {
log::warn!("knn={knn} is higher than number of samples={n}");
knn = n - 1;
}
let kmer = inverted_index.kmer();
let dist_type = set_k(&references, Some(kmer), *ani).unwrap_or_else(|e| {
panic!("K-mer size {kmer} used for .ski not found in .skd: {e}");
});
let ref_completeness_vec: Option<Vec<f64>> =
if let Some(file_path) = ref_completeness_file {
Some(read_completeness_file(file_path, &references)?)
} else {
None
};
log::info!(
"Calculating sparse ref vs ref distances with {knn} nearest neighbours"
);
log::info!(
"Preclustering with k={} and s={}",
kmer,
inverted_index.sketch_size()
);
if let Some(ref mode) = retain_unmatched {
log::info!("Retain unmatched mode: {mode}");
}
let distances = self_dists_knn_precluster(
&references,
&inverted_index,
&skq_bins,
skq_reader.sample_stride,
n,
knn,
dist_type,
args.quiet,
ref_completeness_vec.as_ref(),
*completeness_cutoff,
retain_unmatched,
);
log::info!("Writing out in sparse matrix form");
write!(output_file, "{distances}").expect("Error writing output distances");
}
Ok(())
}
},
Commands::Append {
db,
seq_files,
file_list,
output,
single_strand,
min_count,
min_qual,
concat_fasta,
threads,
level,
} => {
set_threads_with_writer(*threads);
log::info!("Getting input files");
let input_files: Vec<(String, Vec<String>)> = get_input_list(file_list, seq_files);
log::info!("Parsed {} samples in input list", input_files.len());
let db_metadata: MultiSketch = MultiSketch::load_metadata(db)?;
if !db_metadata.append_compatibility(&input_files) {
panic!("Databases are not compatible for merging.")
}
log::info!("Passed concat check");
let kmers = db_metadata.kmer_lengths();
let rc = !*single_strand;
let sketch_size = db_metadata.sketch_size;
let seq_type = db_metadata.get_hash_type();
if *concat_fasta && matches!(*seq_type, HashType::DNA | HashType::PDB) {
panic!("--concat-fasta currently only supported with --seq-type aa");
}
log::info!(
"Running sketching: k:{:?}; sketch_size:{}; seq:{:?}; threads:{}",
kmers,
sketch_size * u64::BITS as u64,
seq_type,
threads,
);
let seq_type = if let HashType::AA(_) = seq_type {
HashType::AA(level.clone())
} else {
seq_type.clone()
};
let mut db2_sketches = sketch_files(
output,
&input_files,
*concat_fasta,
#[cfg(feature = "3di")]
false,
kmers,
sketch_size,
&seq_type,
rc,
*min_count,
*min_qual,
args.quiet,
);
let mut db2_metadata =
MultiSketch::new(&mut db2_sketches, sketch_size, kmers, seq_type);
log::info!("Merging and saving sketch data to {output}.skd");
let mut output_file = OpenOptions::new()
.create(true)
.append(true)
.open(format!("{output}.skd"))?;
let mut db_sketch = File::open(format!("{db}.skd"))?;
copy(&mut db_sketch, &mut output_file)?;
let concat_metadata = db2_metadata.merge_sketches(&db_metadata);
concat_metadata
.save_metadata(output)
.unwrap_or_else(|_| panic!("Could not save metadata to {output}"));
Ok(())
}
Commands::Delete {
db,
samples,
output_file,
} => {
let ref_db = utils::strip_sketch_extension(db);
log::info!("Reading input genomes");
let path = Path::new(samples);
let file = File::open(path)?;
let reader = std::io::BufReader::new(file);
let ids: Vec<String> = reader.lines().map_while(Result::ok).collect();
log::info!("Reading input metadata");
let mut sketches: MultiSketch = MultiSketch::load_metadata(ref_db)
.unwrap_or_else(|_| panic!("Could not read sketch metadata from {ref_db}.skm"));
log::info!("Remove genomes and writing output");
sketches.remove_genomes(ref_db, output_file, &ids)?;
sketches.remove_metadata(output_file, &ids)?;
log::info!("Finished writing filtered sketch data to {output_file}");
Ok(())
}
Commands::Info {
skm_file,
sample_info,
} => {
if skm_file.ends_with(".ski") {
let ski_file = &skm_file[0..skm_file.len() - 4];
let index = Inverted::load(ski_file).unwrap_or_else(|err| {
println!("Read error: {err}");
panic!("Could not read inverted index from {ski_file}.ski")
});
if *sample_info {
log::info!("Printing sample info");
println!("{index}");
} else {
log::info!("Printing inverted index info");
println!("{index:?}");
}
} else {
let ref_db_name = if skm_file.ends_with(".skm") || skm_file.ends_with(".skd") {
&skm_file[0..skm_file.len() - 4]
} else {
skm_file.as_str()
};
let sketches = MultiSketch::load_metadata(ref_db_name).unwrap_or_else(|_| {
panic!("Could not read sketch metadata from {ref_db_name}.skm")
});
if *sample_info {
log::info!("Printing sample info");
println!("{sketches}");
} else {
log::info!("Printing database info");
println!("{sketches:?}");
}
}
print_success = false; Ok(())
}
};
let end = Instant::now();
log::info!("Complete");
if print_success && !args.quiet {
eprintln!(
"🧬🖋️ sketchlib done in {}s",
end.duration_since(start).as_secs()
);
}
result
}
#[cfg(target_arch = "wasm32")]
#[doc(hidden)]
pub fn main() {
panic!("You've compiled sketchlib.rust for WebAssembly support, you cannot use it as a normal binary anymore!");
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn init_panic_hook() {
console_error_panic_hook::set_once();
}
#[cfg(target_arch = "wasm32")]
pub fn logw(text: &str, typ: Option<&str>) {
if let Some(thetyp) = typ {
log((String::from("sketchlib.rust::") + thetyp + "::" + text).as_str());
} else {
log(text);
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub struct SketchlibData {
out_probs: Vec<(f64, usize)>,
index: Inverted,
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
impl SketchlibData {
pub fn new(skifile: web_sys::File) -> Self {
let inverted_index = Inverted::load(&skifile).expect("Failed loading Sketchlib index");
logw(
format!("Read inverted index:\n{inverted_index:?}").as_str(),
Some("info"),
);
Self {
out_probs: Vec::new(),
index: inverted_index,
}
}
pub fn query(&mut self, file1: web_sys::File, file2: Option<web_sys::File>) {
let min_count = &DEFAULT_MINCOUNT;
let min_qual = &DEFAULT_MINQUAL;
let query_type = &InvertedQueryType::MatchCount;
let (queries, _query_names) =
self.index
.sketch_queries((&file1, file2.as_ref()), *min_count, *min_qual, false);
logw(
format!("Running query in mode: {query_type}").as_str(),
Some("info"),
);
let dist = match query_type {
InvertedQueryType::MatchCount => self
.index
.query_against_inverted_index(queries[0].as_slice()),
InvertedQueryType::AllBins => self.index.all_shared_bins(queries[0].as_slice()),
InvertedQueryType::AnyBins => self.index.any_shared_bins(queries[0].as_slice()),
};
let mut outvec: Vec<(f64, usize)> = Vec::with_capacity(dist.len());
for (i, d) in dist.iter().enumerate() {
outvec.push((
(*d as f64) / ((2 * self.index.sketch_size()) as f64 - *d as f64),
i,
));
}
outvec.sort_by(|a, b| a.0.partial_cmp(&b.0).expect("NaN obtained!"));
outvec.reverse();
self.out_probs = outvec;
}
pub fn get_probs(&self, nouts: usize) -> String {
if self.out_probs.is_empty() {
panic!("No probabilities calculated!");
}
let mut results = json::JsonValue::new_array();
logw(
format!("Probabilities: {:?}", self.out_probs).as_str(),
Some("info"),
);
results["probs"] = json::JsonValue::Array(
self.out_probs
.iter()
.take(nouts)
.map(|x| json::JsonValue::Number(x.0.into()))
.collect(),
);
results["names"] = json::JsonValue::Array(
self.out_probs
.iter()
.take(nouts)
.map(|x| {
if let Some(labelsvec) = self.index.get_sample_labels() {
json::JsonValue::String(labelsvec[x.1].clone())
} else {
json::JsonValue::String("".to_string())
}
})
.collect(),
);
results["metadata"] = json::JsonValue::Array(
self.out_probs
.iter()
.take(nouts)
.map(|x| {
if let Some(metadatavec) = self.index.get_metadata() {
json::JsonValue::String(metadatavec[x.1].clone())
} else {
json::JsonValue::String("".to_string())
}
})
.collect(),
);
logw(results.dump().as_str(), Some("debug"));
results.dump()
}
}