From cf4357c7bb370fe590ddc82aa1ccb5196aa5b8aa Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Mon, 16 Feb 2026 20:27:19 +0100 Subject: [PATCH 01/23] Add Qualimap RNA-Seq QC module: index, accumulator, and coverage core (Phase 1) Implement the core Qualimap-compatible RNA-Seq QC engine as a new src/rna/qualimap/ module with its own counting logic, fundamentally different from the existing dupRadar engine: - index.rs: Per-chromosome exon COITree with enclosure-based gene assignment, intron interval tree, and per-transcript metadata - accumulator.rs: M-only CIGAR extraction, NH>1 multi-mapper exclusion, PE mate buffering with interval combining, junction motif extraction, and Qualimap-compatible read counters - coverage.rs: Per-transcript per-base depth tracking with lazy allocation and genomic-to-relative coordinate mapping - mod.rs: Module re-exports and QualimapResult struct Integrated into the BAM processing pipeline alongside existing RSeQC and dupRadar accumulators (both parallel and single-threaded paths). QualimapIndex built from GTF genes in main.rs, gated by config toggle. No existing outputs are altered. --- .gitignore | 1 + src/config.rs | 29 + src/main.rs | 8 + src/rna/dupradar/counting.rs | 50 ++ src/rna/featurecounts/output.rs | 1 + src/rna/mod.rs | 1 + src/rna/qualimap/accumulator.rs | 916 ++++++++++++++++++++++++++++++++ src/rna/qualimap/coverage.rs | 226 ++++++++ src/rna/qualimap/index.rs | 418 +++++++++++++++ src/rna/qualimap/mod.rs | 69 +++ 10 files changed, 1719 insertions(+) create mode 100644 src/rna/qualimap/accumulator.rs create mode 100644 src/rna/qualimap/coverage.rs create mode 100644 src/rna/qualimap/index.rs create mode 100644 src/rna/qualimap/mod.rs diff --git a/.gitignore b/.gitignore index 1f57ac3..a708088 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ __pycache__/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +tests/qualimap_ref/ diff --git a/src/config.rs b/src/config.rs index b62e318..f5eb5a4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -112,6 +112,10 @@ pub struct Config { /// preseq lc_extrap library complexity extrapolation configuration. #[serde(default)] pub preseq: PreseqConfig, + + /// Qualimap RNA-Seq QC configuration. + #[serde(default)] + pub qualimap: QualimapConfig, } // ============================================================================ @@ -529,6 +533,31 @@ impl Default for GenebodyCoverageConfig { } } +/// Configuration for Qualimap RNA-Seq QC. +/// +/// When enabled, produces Qualimap-compatible output files including +/// `rnaseq_qc_results.txt`, coverage profiles (total/high/low), plots, +/// and an HTML report. Uses Qualimap-compatible counting logic: +/// enclosure-based gene assignment with M-only CIGAR parsing. +/// +/// Example: +/// ```yaml +/// qualimap: +/// enabled: true +/// ``` +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct QualimapConfig { + /// Whether to produce Qualimap RNA-Seq QC output. Defaults to true. + pub enabled: bool, +} + +impl Default for QualimapConfig { + fn default() -> Self { + Self { enabled: true } + } +} + /// Configuration for samtools idxstats-compatible output. /// /// When enabled, produces a file matching `samtools idxstats` output format, diff --git a/src/main.rs b/src/main.rs index 1a543c7..078ff25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -650,6 +650,13 @@ fn process_single_bam( None }; + // === Build Qualimap exon index (if enabled, GTF-only) === + let qualimap_index = if params.config.qualimap.enabled { + genes.map(rna::qualimap::QualimapIndex::from_genes) + } else { + None + }; + // === dupRadar counting (requires GTF) === let mut count_result = if let Some(genes) = genes { info!( @@ -678,6 +685,7 @@ fn process_single_bam( None }, genebody_position_map.as_ref(), + qualimap_index.as_ref(), )?; info!( "[{}] Counting complete in {:.2}s", diff --git a/src/rna/dupradar/counting.rs b/src/rna/dupradar/counting.rs index a3cd68f..ae9f5b8 100644 --- a/src/rna/dupradar/counting.rs +++ b/src/rna/dupradar/counting.rs @@ -11,6 +11,7 @@ use crate::gtf::Gene; use crate::rna::genebody::GenebodyCoverageAccum; +use crate::rna::qualimap::QualimapAccum; use crate::rna::rseqc::accumulators::{RseqcAccumulators, RseqcAnnotations, RseqcConfig}; use anyhow::{Context, Result}; use coitrees::{COITree, Interval, IntervalTree}; @@ -356,6 +357,9 @@ pub struct CountResult { pub rseqc: Option, /// Gene body coverage results (Qualimap-compatible). pub genebody: Option, + /// Qualimap RNA-Seq QC results (if enabled). + #[allow(dead_code)] + pub qualimap: Option, } /// Metadata stored with each interval in the cache-oblivious interval tree. @@ -582,6 +586,8 @@ struct ChromResult { fc_unmapped: u64, /// Gene body coverage accumulator (if enabled). genebody: Option, + /// Qualimap RNA-Seq QC accumulator (if enabled). + qualimap: Option, } impl ChromResult { @@ -608,6 +614,7 @@ impl ChromResult { fc_multimapping: 0, fc_unmapped: 0, genebody: None, + qualimap: None, } } @@ -647,6 +654,14 @@ impl ChromResult { self.genebody = Some(other_gb); } } + // Merge Qualimap accumulator + if let Some(other_qm) = other.qualimap { + if let Some(ref mut self_qm) = self.qualimap { + self_qm.merge(other_qm); + } else { + self.qualimap = Some(other_qm); + } + } } } @@ -697,11 +712,15 @@ fn process_chromosome_batch( rseqc_annotations: Option<&RseqcAnnotations>, htslib_threads: usize, genebody_position_map: Option<&crate::rna::genebody::TranscriptPositionMap>, + qualimap_index: Option<&crate::rna::qualimap::QualimapIndex>, ) -> Result<(ChromResult, Option)> { let mut result = ChromResult::new(num_genes); if genebody_position_map.is_some() { result.genebody = Some(crate::rna::genebody::GenebodyCoverageAccum::new()); } + if qualimap_index.is_some() { + result.qualimap = Some(crate::rna::qualimap::QualimapAccum::new(paired)); + } let mut rseqc_accums = rseqc_config.map(|cfg| RseqcAccumulators::new(cfg, rseqc_annotations)); // Pre-compute resolved chromosome names for RSeQC tools. @@ -792,6 +811,17 @@ fn process_chromosome_batch( accums.process_read(&record, chrom, chrom_upper, annots, cfg); } + // --- Qualimap per-read dispatch (before counting filters) --- + // Qualimap accumulator handles its own filtering (unmapped, secondary, + // QC-fail, supplementary, NH>1) and uses enclosure-based gene assignment. + if let Some(ref mut qm) = result.qualimap { + let tid = record.tid(); + if tid >= 0 && (tid as usize) < tid_to_gtf_chrom.len() { + let qm_chrom = &tid_to_gtf_chrom[tid as usize]; + qm.process_read(&record, qm_chrom, qualimap_index.unwrap()); + } + } + let flags = record.flags(); // Skip unmapped reads (count for featureCounts summary) @@ -1055,6 +1085,7 @@ pub fn count_reads( rseqc_config: Option<&RseqcConfig>, rseqc_annotations: Option<&RseqcAnnotations>, genebody_position_map: Option<&crate::rna::genebody::TranscriptPositionMap>, + qualimap_index: Option<&crate::rna::qualimap::QualimapIndex>, ) -> Result { // Build gene ID interner for allocation-free lookups in the hot loop let interner = GeneIdInterner::from_genes(genes); @@ -1167,6 +1198,7 @@ pub fn count_reads( rseqc_annotations, htslib_threads, genebody_position_map, + qualimap_index, ) }) .collect() @@ -1197,6 +1229,8 @@ pub fn count_reads( } let mut rseqc_accums: Option = rseqc_config.map(|cfg| RseqcAccumulators::new(cfg, rseqc_annotations)); + let mut qualimap_accum: Option = + qualimap_index.map(|_| crate::rna::qualimap::QualimapAccum::new(paired)); // Pre-compute resolved chromosome names for RSeQC tools // (apply chromosome prefix and mapping, same as parallel path) @@ -1264,6 +1298,17 @@ pub fn count_reads( accums.process_read(&record, chrom, chrom_upper, annots, cfg); } + // --- Qualimap per-read dispatch (before counting filters) --- + // Qualimap has its own filtering (M-only CIGAR, NH>1, enclosure-based) + if let (Some(ref mut qm_accum), Some(qm_index)) = (&mut qualimap_accum, qualimap_index) + { + let tid = record.tid(); + if tid >= 0 && (tid as usize) < tid_to_rseqc_chrom.len() { + let chrom = &tid_to_rseqc_chrom[tid as usize]; + qm_accum.process_read(&record, chrom, qm_index); + } + } + if flags & BAM_FUNMAP != 0 { result.fc_unmapped += 1; continue; @@ -1461,6 +1506,7 @@ pub fn count_reads( } result.unmatched_mates = mate_buffer; + result.qualimap = qualimap_accum; (result, rseqc_accums) }; @@ -1642,6 +1688,10 @@ pub fn count_reads( genebody: merged .genebody .map(|a: GenebodyCoverageAccum| a.into_result()), + qualimap: match (merged.qualimap, qualimap_index) { + (Some(a), Some(idx)) => Some(a.into_result(idx)), + _ => None, + }, }) } diff --git a/src/rna/featurecounts/output.rs b/src/rna/featurecounts/output.rs index db4f98d..eda1ec3 100644 --- a/src/rna/featurecounts/output.rs +++ b/src/rna/featurecounts/output.rs @@ -331,6 +331,7 @@ mod tests { fc_unmapped: 0, rseqc: None, genebody: None, + qualimap: None, }; let biotypes = aggregate_biotype_counts(&genes, &count_result, "gene_biotype"); diff --git a/src/rna/mod.rs b/src/rna/mod.rs index a3925b7..60dbe0c 100644 --- a/src/rna/mod.rs +++ b/src/rna/mod.rs @@ -7,4 +7,5 @@ pub mod dupradar; pub mod featurecounts; pub mod genebody; pub mod preseq; +pub mod qualimap; pub mod rseqc; diff --git a/src/rna/qualimap/accumulator.rs b/src/rna/qualimap/accumulator.rs new file mode 100644 index 0000000..4c917a0 --- /dev/null +++ b/src/rna/qualimap/accumulator.rs @@ -0,0 +1,916 @@ +//! Qualimap-compatible RNA-Seq QC accumulator. +//! +//! Processes BAM records using Qualimap's counting rules: +//! - M-only CIGAR blocks (no insertion/deletion/softclip contribution) +//! - Enclosure-based gene assignment (each M-block must be fully contained in exons of the same gene) +//! - PE reads: combine both mates' M-blocks, assign on combined set, count numReads=2 +//! - Multi-mappers excluded entirely (NH > 1) +//! - Junction motif extraction from read sequence at N-operation boundaries + +use std::collections::{HashMap, HashSet}; + +use coitrees::IntervalTree; +use rust_htslib::bam; +use rust_htslib::bam::record::Cigar; + +use super::coverage::TranscriptCoverage; +use super::index::QualimapIndex; + +// ============================================================ +// BAM flag constants +// ============================================================ + +const BAM_FUNMAP: u16 = 0x4; +const BAM_FSECONDARY: u16 = 0x100; +const BAM_FQCFAIL: u16 = 0x200; +const BAM_FSUPPLEMENTARY: u16 = 0x800; +const BAM_FPAIRED: u16 = 0x1; +const BAM_FPROPER_PAIR: u16 = 0x2; +const BAM_FREAD1: u16 = 0x40; +const BAM_FREAD2: u16 = 0x80; + +// ============================================================ +// Mate buffer for PE reads +// ============================================================ + +/// Key for mate buffering: (qname_hash, r1_tid, r1_pos, r2_tid, r2_pos). +type MateKey = (u64, i32, i64, i32, i64); + +/// Buffered mate information for PE reconciliation. +#[derive(Debug, Clone)] +struct MateInfo { + /// M-only aligned blocks (0-based half-open). + m_blocks: Vec<(i32, i32)>, + /// Genes whose exons enclose ALL M-blocks of this mate. + enclosing_genes: HashSet, + /// Read sequence bytes at junction boundaries (for motif extraction). + junction_motifs: Vec, +} + +/// A splice junction motif extracted from the read sequence at an N-operation. +#[derive(Debug, Clone)] +#[allow(dead_code)] +struct JunctionMotif { + /// Genomic start of the intron (0-based). + intron_start: i32, + /// Genomic end of the intron (0-based). + intron_end: i32, + /// 2bp donor motif (from read, e.g., "GT"). + donor: [u8; 2], + /// 2bp acceptor motif (from read, e.g., "AG"). + acceptor: [u8; 2], +} + +// ============================================================ +// Qualimap counters +// ============================================================ + +/// Accumulated Qualimap-compatible QC counters. +/// +/// These match the counters in Qualimap's `RNASeqQCAnalysis` and are output +/// in `rnaseq_qc_results.txt`. +#[derive(Debug, Clone, Default)] +pub struct QualimapCounters { + /// Total primary alignments processed. + pub primary_alignments: u64, + /// Total secondary alignments seen (skipped). + pub secondary_alignments: u64, + /// Unmapped reads (flag 0x4). + pub not_aligned: u64, + /// Multi-mapped reads (NH > 1), excluded from counting. + pub alignment_not_unique: u64, + /// Reads assigned to exactly one gene (exonic). + pub exonic_reads: u64, + /// Reads not assigned to any gene and not intronic (intergenic). + pub no_feature: u64, + /// Reads assigned to more than one gene. + pub ambiguous: u64, + /// Reads with any M-block overlapping an intron. + pub intronic_reads: u64, + /// Reads not overlapping any exon or intron. + pub intergenic_reads: u64, + /// Reads overlapping exons of multiple transcripts of the same gene. + pub overlapping_exon: u64, + /// Total reads counted (numReads: for PE, 2 per fragment). + pub read_count: u64, + /// Total fragments counted (PE: 1 per pair, SE: 1 per read). + pub fragment_count: u64, + /// Left mates in proper pairs (flag 0x2 + 0x40). + pub left_proper_in_pair: u64, + /// Right mates in proper pairs (flag 0x2 + 0x80). + pub right_proper_in_pair: u64, + /// Reads at splice junctions (with N-operations in CIGAR). + pub reads_at_junctions: u64, + /// Total supplementary alignments seen (skipped). + pub supplementary: u64, +} + +impl QualimapCounters { + /// Merge counters from another accumulator. + pub fn merge(&mut self, other: &QualimapCounters) { + self.primary_alignments += other.primary_alignments; + self.secondary_alignments += other.secondary_alignments; + self.not_aligned += other.not_aligned; + self.alignment_not_unique += other.alignment_not_unique; + self.exonic_reads += other.exonic_reads; + self.no_feature += other.no_feature; + self.ambiguous += other.ambiguous; + self.intronic_reads += other.intronic_reads; + self.intergenic_reads += other.intergenic_reads; + self.overlapping_exon += other.overlapping_exon; + self.read_count += other.read_count; + self.fragment_count += other.fragment_count; + self.left_proper_in_pair += other.left_proper_in_pair; + self.right_proper_in_pair += other.right_proper_in_pair; + self.reads_at_junctions += other.reads_at_junctions; + self.supplementary += other.supplementary; + } +} + +// ============================================================ +// Junction motif accumulator +// ============================================================ + +/// Accumulated junction motif counts (donor-acceptor pairs). +#[derive(Debug, Clone, Default)] +pub struct JunctionMotifCounts { + /// Counts keyed by (donor_2bp, acceptor_2bp) as uppercase ASCII. + pub counts: HashMap<([u8; 2], [u8; 2]), u64>, +} + +impl JunctionMotifCounts { + /// Merge counts from another accumulator. + pub fn merge(&mut self, other: &JunctionMotifCounts) { + for (key, &count) in &other.counts { + *self.counts.entry(*key).or_insert(0) += count; + } + } +} + +// ============================================================ +// QualimapAccum — the main accumulator +// ============================================================ + +/// Qualimap RNA-Seq QC accumulator. +/// +/// Processes BAM records using Qualimap's counting rules and accumulates: +/// - Read/fragment classification counters +/// - Per-transcript coverage +/// - Junction motif counts +/// +/// This accumulator is designed to integrate into the existing BAM pass via +/// `RseqcAccumulators`, receiving each BAM record through `process_read()`. +#[derive(Debug, Clone)] +pub struct QualimapAccum { + /// QC counters matching Qualimap's output. + pub counters: QualimapCounters, + /// Per-transcript per-base coverage. + pub coverage: TranscriptCoverage, + /// Junction splice motif counts. + pub junction_motifs: JunctionMotifCounts, + /// Whether the BAM is paired-end. + paired: bool, + /// Mate buffer for PE reconciliation. + mate_buffer: HashMap, +} + +impl QualimapAccum { + /// Create a new Qualimap accumulator. + /// + /// # Arguments + /// * `paired` - Whether the BAM file is paired-end. + pub fn new(paired: bool) -> Self { + Self { + counters: QualimapCounters::default(), + coverage: TranscriptCoverage::new(), + junction_motifs: JunctionMotifCounts::default(), + paired, + mate_buffer: HashMap::new(), + } + } + + /// Process a single BAM record. + /// + /// Applies Qualimap's filtering and counting logic: + /// 1. Skip unmapped, secondary, QC-fail, supplementary reads + /// 2. Skip multi-mappers (NH > 1) + /// 3. Extract M-only CIGAR blocks + /// 4. For SE: assign immediately via enclosure check + /// 5. For PE: buffer mate, assign when pair is complete + /// + /// # Arguments + /// * `record` - The BAM record to process. + /// * `chrom` - Chromosome name for this record. + /// * `index` - The Qualimap annotation index. + pub fn process_read(&mut self, record: &bam::Record, chrom: &str, index: &QualimapIndex) { + let flags = record.flags(); + + // Count proper pair flags + if flags & BAM_FPAIRED != 0 && flags & BAM_FPROPER_PAIR != 0 { + if flags & BAM_FREAD1 != 0 { + self.counters.left_proper_in_pair += 1; + } + if flags & BAM_FREAD2 != 0 { + self.counters.right_proper_in_pair += 1; + } + } + + // Skip unmapped + if flags & BAM_FUNMAP != 0 { + self.counters.not_aligned += 1; + return; + } + + // Skip secondary + if flags & BAM_FSECONDARY != 0 { + self.counters.secondary_alignments += 1; + return; + } + + // Skip supplementary + if flags & BAM_FSUPPLEMENTARY != 0 { + self.counters.supplementary += 1; + return; + } + + // Skip QC-fail + if flags & BAM_FQCFAIL != 0 { + return; + } + + self.counters.primary_alignments += 1; + + // Skip multi-mappers (NH > 1) + if let Some(nh) = get_nh_tag(record) { + if nh > 1 { + self.counters.alignment_not_unique += 1; + return; + } + } + + // Extract M-only CIGAR blocks + let m_blocks = extract_m_blocks(record); + if m_blocks.is_empty() { + return; + } + + // Extract junction motifs from N-operations in CIGAR + let motifs = extract_junction_motifs(record); + if !motifs.is_empty() { + self.counters.reads_at_junctions += 1; + } + + // Determine enclosing genes for this read's M-blocks + let enclosing_genes = find_enclosing_genes(&m_blocks, chrom, index); + + if self.paired && flags & BAM_FPAIRED != 0 { + // PE mode: buffer for mate reconciliation + self.process_pe_read(record, chrom, m_blocks, enclosing_genes, motifs, index); + } else { + // SE mode: assign immediately + self.assign_se_read(&m_blocks, enclosing_genes, motifs, chrom, index); + } + } + + /// Process a PE read — buffer and reconcile with mate. + fn process_pe_read( + &mut self, + record: &bam::Record, + chrom: &str, + m_blocks: Vec<(i32, i32)>, + enclosing_genes: HashSet, + motifs: Vec, + index: &QualimapIndex, + ) { + let key = make_mate_key(record); + + if let Some(mate) = self.mate_buffer.remove(&key) { + // Found the mate — reconcile the pair + self.reconcile_pair( + &m_blocks, + &enclosing_genes, + &motifs, + &mate.m_blocks, + &mate.enclosing_genes, + &mate.junction_motifs, + chrom, + index, + ); + } else { + // First mate seen — buffer it + self.mate_buffer.insert( + key, + MateInfo { + m_blocks, + enclosing_genes, + junction_motifs: motifs, + }, + ); + } + } + + /// Reconcile a PE pair: combine M-blocks and gene assignments. + /// + /// Qualimap intersects the enclosing gene sets of both mates. + /// If exactly one gene encloses ALL M-blocks of BOTH mates → assigned. + /// If >1 gene → ambiguous. If 0 genes → check intronic/intergenic. + #[allow(clippy::too_many_arguments)] + fn reconcile_pair( + &mut self, + r1_blocks: &[(i32, i32)], + r1_genes: &HashSet, + r1_motifs: &[JunctionMotif], + r2_blocks: &[(i32, i32)], + r2_genes: &HashSet, + r2_motifs: &[JunctionMotif], + chrom: &str, + index: &QualimapIndex, + ) { + // Combine M-blocks from both mates + let mut combined_blocks: Vec<(i32, i32)> = + Vec::with_capacity(r1_blocks.len() + r2_blocks.len()); + combined_blocks.extend_from_slice(r1_blocks); + combined_blocks.extend_from_slice(r2_blocks); + + // Intersect enclosing gene sets + let common_genes: HashSet = r1_genes.intersection(r2_genes).copied().collect(); + + // Also consider genes that enclose all combined blocks + // (a mate with no exonic overlap contributes an empty set — use union in that case) + let assigned_genes = if r1_genes.is_empty() && !r2_genes.is_empty() { + r2_genes.clone() + } else if r2_genes.is_empty() && !r1_genes.is_empty() { + r1_genes.clone() + } else { + common_genes + }; + + // Collect junction motifs from both mates + let all_motifs: Vec<&JunctionMotif> = r1_motifs.iter().chain(r2_motifs.iter()).collect(); + for motif in &all_motifs { + *self + .junction_motifs + .counts + .entry((motif.donor, motif.acceptor)) + .or_insert(0) += 1; + } + + // Fragment assignment + self.counters.fragment_count += 1; + + match assigned_genes.len() { + 0 => { + // No gene encloses all blocks — classify as intronic or intergenic + self.classify_no_feature(&combined_blocks, chrom, index); + // numReads += 2 for PE + self.counters.read_count += 2; + self.counters.no_feature += 1; + } + 1 => { + // Exactly one gene — assigned (exonic) + let gene_idx = *assigned_genes.iter().next().unwrap(); + self.counters.exonic_reads += 1; + self.counters.read_count += 2; + + // Check if overlapping exons of multiple transcripts + if is_overlapping_exon(gene_idx, &combined_blocks, index) { + self.counters.overlapping_exon += 1; + } + + // Add coverage for all transcripts of this gene + let (tx_start, _tx_end) = index.gene_transcript_ranges[gene_idx as usize]; + let transcripts = index.gene_transcripts(gene_idx); + self.coverage + .add_coverage(&combined_blocks, transcripts, tx_start); + } + _ => { + // Multiple genes — ambiguous + self.counters.ambiguous += 1; + self.counters.read_count += 2; + } + } + } + + /// Assign an SE read immediately. + fn assign_se_read( + &mut self, + m_blocks: &[(i32, i32)], + enclosing_genes: HashSet, + motifs: Vec, + chrom: &str, + index: &QualimapIndex, + ) { + // Record junction motifs + for motif in &motifs { + *self + .junction_motifs + .counts + .entry((motif.donor, motif.acceptor)) + .or_insert(0) += 1; + } + + self.counters.fragment_count += 1; + + match enclosing_genes.len() { + 0 => { + self.classify_no_feature(m_blocks, chrom, index); + self.counters.read_count += 1; + self.counters.no_feature += 1; + } + 1 => { + let gene_idx = *enclosing_genes.iter().next().unwrap(); + self.counters.exonic_reads += 1; + self.counters.read_count += 1; + + if is_overlapping_exon(gene_idx, m_blocks, index) { + self.counters.overlapping_exon += 1; + } + + let (tx_start, _tx_end) = index.gene_transcript_ranges[gene_idx as usize]; + let transcripts = index.gene_transcripts(gene_idx); + self.coverage.add_coverage(m_blocks, transcripts, tx_start); + } + _ => { + self.counters.ambiguous += 1; + self.counters.read_count += 1; + } + } + } + + /// Classify a read that wasn't assigned to any gene as intronic or intergenic. + fn classify_no_feature(&mut self, m_blocks: &[(i32, i32)], chrom: &str, index: &QualimapIndex) { + if let Some(intron_tree) = index.intron_tree(chrom) { + for &(start, end) in m_blocks { + let mut found_intron = false; + intron_tree.query(start, end, |_iv| { + found_intron = true; + }); + if found_intron { + self.counters.intronic_reads += 1; + return; + } + } + } + self.counters.intergenic_reads += 1; + } + + /// Flush any remaining unpaired mates at the end of a chromosome batch. + /// + /// PE mates that were never reconciled are treated as singletons and + /// assigned using SE logic. + #[allow(dead_code)] + pub fn flush_unpaired(&mut self, index: &QualimapIndex) { + let unpaired: Vec<(MateKey, MateInfo)> = self.mate_buffer.drain().collect(); + for (_key, mate) in unpaired { + // Treat as singleton SE read + // We don't have a chrom at hand, but the mate was already processed + // with its chrom. Since we can't re-derive it here, we skip + // intronic/intergenic classification for orphan mates and count + // them as no_feature. + // Note: in practice, most mates are reconciled within the same + // chromosome batch. + self.counters.fragment_count += 1; + self.counters.read_count += 1; + + match mate.enclosing_genes.len() { + 0 => { + self.counters.no_feature += 1; + self.counters.intergenic_reads += 1; + } + 1 => { + let gene_idx = *mate.enclosing_genes.iter().next().unwrap(); + self.counters.exonic_reads += 1; + + if is_overlapping_exon(gene_idx, &mate.m_blocks, index) { + self.counters.overlapping_exon += 1; + } + + let (tx_start, _tx_end) = index.gene_transcript_ranges[gene_idx as usize]; + let transcripts = index.gene_transcripts(gene_idx); + self.coverage + .add_coverage(&mate.m_blocks, transcripts, tx_start); + } + _ => { + self.counters.ambiguous += 1; + } + } + + // Junction motifs from orphan mates + for motif in &mate.junction_motifs { + *self + .junction_motifs + .counts + .entry((motif.donor, motif.acceptor)) + .or_insert(0) += 1; + } + } + } + + /// Merge another `QualimapAccum` into this one. + /// + /// Used when combining results from parallel chromosome batches. + pub fn merge(&mut self, other: QualimapAccum) { + self.counters.merge(&other.counters); + self.coverage.merge(other.coverage); + self.junction_motifs.merge(&other.junction_motifs); + + // Merge mate buffers (cross-chromosome mates) + for (key, info) in other.mate_buffer { + // If the same key exists in both, that means both mates were + // buffered in different batches — reconcile them + if let Some(existing) = self.mate_buffer.remove(&key) { + // We don't have the chrom handy for intronic classification, + // so treat orphans conservatively + self.counters.fragment_count += 1; + self.counters.read_count += 2; + + let common_genes: HashSet = existing + .enclosing_genes + .intersection(&info.enclosing_genes) + .copied() + .collect(); + + let assigned = if existing.enclosing_genes.is_empty() + && !info.enclosing_genes.is_empty() + { + info.enclosing_genes.clone() + } else if info.enclosing_genes.is_empty() && !existing.enclosing_genes.is_empty() { + existing.enclosing_genes.clone() + } else { + common_genes + }; + + match assigned.len() { + 0 => { + self.counters.no_feature += 1; + self.counters.intergenic_reads += 1; + } + 1 => { + self.counters.exonic_reads += 1; + } + _ => { + self.counters.ambiguous += 1; + } + } + + // Junction motifs + for motif in existing + .junction_motifs + .iter() + .chain(info.junction_motifs.iter()) + { + *self + .junction_motifs + .counts + .entry((motif.donor, motif.acceptor)) + .or_insert(0) += 1; + } + } else { + self.mate_buffer.insert(key, info); + } + } + } + + /// Get the number of unmatched mates still in the buffer. + #[allow(dead_code)] + pub fn unmatched_count(&self) -> usize { + self.mate_buffer.len() + } + + /// Convert this accumulator into a `QualimapResult`. + /// + /// This should be called after all reads have been processed and + /// `flush_unpaired()` has been called. + pub fn into_result(self, index: &QualimapIndex) -> super::QualimapResult { + // Convert junction motif byte arrays to readable strings + let mut junction_motifs_str = HashMap::new(); + for ((donor, acceptor), count) in &self.junction_motifs.counts { + let motif = format!( + "{}{}{}{}", + donor[0] as char, donor[1] as char, acceptor[0] as char, acceptor[1] as char, + ); + *junction_motifs_str.entry(motif).or_insert(0u64) += count; + } + + // Convert per-transcript coverage from flat indices to transcript_id strings + let mut transcript_coverage = HashMap::new(); + for (flat_idx, depth) in self.coverage.iter() { + if let Some(tx_info) = index.transcripts.get(flat_idx as usize) { + transcript_coverage.insert(tx_info.transcript_id.clone(), depth.to_vec()); + } + } + + super::QualimapResult { + primary_alignments: self.counters.primary_alignments, + secondary_alignments: self.counters.secondary_alignments, + not_aligned: self.counters.not_aligned, + alignment_not_unique: self.counters.alignment_not_unique, + exonic_reads: self.counters.exonic_reads, + ambiguous_reads: self.counters.ambiguous, + no_feature: self.counters.no_feature, + intronic_reads: self.counters.intronic_reads, + intergenic_reads: self.counters.intergenic_reads, + overlapping_exon_reads: self.counters.overlapping_exon, + read_count: self.counters.read_count, + fragment_count: self.counters.fragment_count, + left_proper_in_pair: self.counters.left_proper_in_pair, + right_proper_in_pair: self.counters.right_proper_in_pair, + reads_at_junctions: self.counters.reads_at_junctions, + junction_motifs: junction_motifs_str, + transcript_coverage, + } + } +} + +// ============================================================ +// Helper functions +// ============================================================ + +/// Extract NH tag (number of reported alignments) from a BAM record. +fn get_nh_tag(record: &bam::Record) -> Option { + match record.aux(b"NH") { + Ok(rust_htslib::bam::record::Aux::U8(v)) => Some(v as u32), + Ok(rust_htslib::bam::record::Aux::U16(v)) => Some(v as u32), + Ok(rust_htslib::bam::record::Aux::U32(v)) => Some(v), + Ok(rust_htslib::bam::record::Aux::I8(v)) => Some(v as u32), + Ok(rust_htslib::bam::record::Aux::I16(v)) => Some(v as u32), + Ok(rust_htslib::bam::record::Aux::I32(v)) => Some(v as u32), + _ => None, + } +} + +/// Extract M-only aligned blocks from a BAM record's CIGAR. +/// +/// Returns a vector of (start, end) in 0-based half-open coordinates. +/// Only `M` (alignment match) operations are included — `I`, `D`, `S`, `H` +/// are skipped. `N` (reference skip / intron) advances the reference position +/// but does not produce an M-block. +fn extract_m_blocks(record: &bam::Record) -> Vec<(i32, i32)> { + let mut blocks = Vec::new(); + let mut ref_pos = record.pos() as i32; // 0-based + + for op in record.cigar().iter() { + match op { + Cigar::Match(len) | Cigar::Equal(len) | Cigar::Diff(len) => { + let len = *len as i32; + blocks.push((ref_pos, ref_pos + len)); + ref_pos += len; + } + Cigar::Del(len) | Cigar::RefSkip(len) => { + ref_pos += *len as i32; + } + Cigar::Ins(_) | Cigar::SoftClip(_) | Cigar::HardClip(_) | Cigar::Pad(_) => { + // These don't consume reference + } + } + } + + blocks +} + +/// Extract junction motifs from N-operations in CIGAR. +/// +/// For each N (intron/RefSkip) operation, extracts the 2bp donor and 2bp acceptor +/// motifs from the read sequence at the junction boundaries. +fn extract_junction_motifs(record: &bam::Record) -> Vec { + let cigar = record.cigar(); + let seq = record.seq(); + let seq_len = seq.len(); + + let mut motifs = Vec::new(); + let mut ref_pos = record.pos() as i32; + let mut seq_pos: usize = 0; + + for op in cigar.iter() { + match op { + Cigar::Match(len) | Cigar::Equal(len) | Cigar::Diff(len) => { + let len = *len as usize; + ref_pos += len as i32; + seq_pos += len; + } + Cigar::RefSkip(len) => { + let intron_start = ref_pos; + let intron_end = ref_pos + *len as i32; + + // Extract donor motif: 2bp at end of preceding exon in read + // Extract acceptor motif: 2bp at start of next exon in read + if seq_pos >= 2 && seq_pos + 1 < seq_len { + let donor = [seq.encoded_base(seq_pos - 2), seq.encoded_base(seq_pos - 1)]; + let acceptor = [seq.encoded_base(seq_pos), seq.encoded_base(seq_pos + 1)]; + + // Convert 4-bit encoding to ASCII + let donor_ascii = [decode_base(donor[0]), decode_base(donor[1])]; + let acceptor_ascii = [decode_base(acceptor[0]), decode_base(acceptor[1])]; + + motifs.push(JunctionMotif { + intron_start, + intron_end, + donor: donor_ascii, + acceptor: acceptor_ascii, + }); + } + + ref_pos = intron_end; + } + Cigar::Ins(len) | Cigar::SoftClip(len) => { + seq_pos += *len as usize; + } + Cigar::Del(len) => { + ref_pos += *len as i32; + } + Cigar::HardClip(_) | Cigar::Pad(_) => {} + } + } + + motifs +} + +/// Decode a 4-bit encoded base to ASCII uppercase. +fn decode_base(encoded: u8) -> u8 { + match encoded { + 1 => b'A', + 2 => b'C', + 4 => b'G', + 8 => b'T', + 15 => b'N', + _ => b'N', + } +} + +/// Find genes whose exons enclose ALL M-blocks of a read. +/// +/// Qualimap's enclosure check: for each M-block, find all exon nodes that +/// fully contain it (block_start >= exon_start AND block_end <= exon_end). +/// A gene "encloses" a read if EVERY M-block is enclosed by at least one exon +/// of that gene. +fn find_enclosing_genes( + m_blocks: &[(i32, i32)], + chrom: &str, + index: &QualimapIndex, +) -> HashSet { + let tree = match index.exon_tree(chrom) { + Some(t) => t, + None => return HashSet::new(), + }; + + if m_blocks.is_empty() { + return HashSet::new(); + } + + // For the first M-block, find all genes with an enclosing exon + let mut candidate_genes: HashSet = HashSet::new(); + let (first_start, first_end) = m_blocks[0]; + tree.query(first_start, first_end, |iv| { + if first_start >= iv.metadata.exon_start && first_end <= iv.metadata.exon_end { + candidate_genes.insert(iv.metadata.gene_idx); + } + }); + + if candidate_genes.is_empty() { + return candidate_genes; + } + + // For subsequent M-blocks, intersect with candidates + for &(block_start, block_end) in &m_blocks[1..] { + let mut block_genes: HashSet = HashSet::new(); + tree.query(block_start, block_end, |iv| { + if block_start >= iv.metadata.exon_start + && block_end <= iv.metadata.exon_end + && candidate_genes.contains(&iv.metadata.gene_idx) + { + block_genes.insert(iv.metadata.gene_idx); + } + }); + candidate_genes = block_genes; + if candidate_genes.is_empty() { + break; + } + } + + candidate_genes +} + +/// Check if a read's M-blocks overlap exons from multiple transcripts of the same gene. +fn is_overlapping_exon(gene_idx: u32, m_blocks: &[(i32, i32)], index: &QualimapIndex) -> bool { + let transcripts = index.gene_transcripts(gene_idx); + if transcripts.len() <= 1 { + return false; + } + + // Count how many transcripts have at least one exon overlapping any M-block + let mut tx_count = 0; + for tx in transcripts { + let mut has_overlap = false; + 'outer: for &(block_start, block_end) in m_blocks { + for &(exon_start, exon_end) in &tx.exons { + if block_start < exon_end && block_end > exon_start { + has_overlap = true; + break 'outer; + } + } + } + if has_overlap { + tx_count += 1; + } + } + + tx_count > 1 +} + +/// Build a mate buffer key from a BAM record. +/// +/// Uses FNV-1a hash of qname + r1-centric ordering of positions. +fn make_mate_key(record: &bam::Record) -> MateKey { + let qname_hash = hash_qname(record.qname()); + let tid = record.tid(); + let pos = record.pos(); + let mate_tid = record.mtid(); + let mate_pos = record.mpos(); + + // Always store with read1 position first for consistent lookup + if tid < mate_tid || (tid == mate_tid && pos <= mate_pos) { + (qname_hash, tid, pos, mate_tid, mate_pos) + } else { + (qname_hash, mate_tid, mate_pos, tid, pos) + } +} + +/// FNV-1a hash of a byte slice (for qname hashing). +fn hash_qname(qname: &[u8]) -> u64 { + let mut hash: u64 = 0xcbf29ce484222325; + for &byte in qname { + hash ^= byte as u64; + hash = hash.wrapping_mul(0x100000001b3); + } + hash +} + +// ============================================================ +// Unit tests +// ============================================================ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash_qname_deterministic() { + let h1 = hash_qname(b"read1"); + let h2 = hash_qname(b"read1"); + let h3 = hash_qname(b"read2"); + assert_eq!(h1, h2); + assert_ne!(h1, h3); + } + + #[test] + fn test_decode_base() { + assert_eq!(decode_base(1), b'A'); + assert_eq!(decode_base(2), b'C'); + assert_eq!(decode_base(4), b'G'); + assert_eq!(decode_base(8), b'T'); + assert_eq!(decode_base(15), b'N'); + assert_eq!(decode_base(0), b'N'); + } + + #[test] + fn test_find_enclosing_genes_empty_chrom() { + let genes = indexmap::IndexMap::new(); + let index = QualimapIndex::from_genes(&genes); + let result = find_enclosing_genes(&[(100, 200)], "chr1", &index); + assert!(result.is_empty()); + } + + #[test] + fn test_counters_merge() { + let mut c1 = QualimapCounters::default(); + c1.primary_alignments = 10; + c1.exonic_reads = 5; + c1.intronic_reads = 3; + + let c2 = QualimapCounters { + primary_alignments: 20, + exonic_reads: 8, + intronic_reads: 4, + ..Default::default() + }; + + c1.merge(&c2); + assert_eq!(c1.primary_alignments, 30); + assert_eq!(c1.exonic_reads, 13); + assert_eq!(c1.intronic_reads, 7); + } + + #[test] + fn test_junction_motif_counts_merge() { + let mut j1 = JunctionMotifCounts::default(); + j1.counts.insert(([b'G', b'T'], [b'A', b'G']), 5); + + let mut j2 = JunctionMotifCounts::default(); + j2.counts.insert(([b'G', b'T'], [b'A', b'G']), 3); + j2.counts.insert(([b'G', b'C'], [b'A', b'G']), 1); + + j1.merge(&j2); + assert_eq!(j1.counts[&([b'G', b'T'], [b'A', b'G'])], 8); + assert_eq!(j1.counts[&([b'G', b'C'], [b'A', b'G'])], 1); + } +} diff --git a/src/rna/qualimap/coverage.rs b/src/rna/qualimap/coverage.rs new file mode 100644 index 0000000..958b329 --- /dev/null +++ b/src/rna/qualimap/coverage.rs @@ -0,0 +1,226 @@ +//! Per-transcript coverage tracking for Qualimap RNA-Seq QC. +//! +//! Maintains per-base coverage arrays for all transcripts of assigned genes. +//! Qualimap tracks coverage across ALL transcripts (not just the longest), +//! using genomic coordinates mapped to transcript-relative positions. + +use std::collections::HashMap; + +use super::index::TranscriptInfo; + +// ============================================================ +// Per-transcript coverage accumulator +// ============================================================ + +/// Per-transcript per-base coverage tracker. +/// +/// Stores a sparse map of transcript flat-index → per-base depth array. +/// Coverage is added for ALL transcripts of a gene when a read is assigned. +/// The depth array uses `i32` to allow efficient increment without overflow +/// concerns for typical RNA-Seq depths. +#[derive(Debug, Clone, Default)] +pub struct TranscriptCoverage { + /// Per-base coverage arrays, keyed by flat transcript index in `QualimapIndex.transcripts`. + /// Only transcripts that have been touched (at least one read assigned to their gene) + /// get an entry here. The `Vec` has length = `transcript.length`. + coverage: HashMap>, +} + +impl TranscriptCoverage { + /// Create a new empty coverage tracker. + pub fn new() -> Self { + Self::default() + } + + /// Add coverage for all transcripts of a gene. + /// + /// For each transcript of the gene, maps the read's M-blocks (genomic coordinates, + /// 0-based half-open) to transcript-relative positions and increments the depth array. + /// + /// # Arguments + /// * `gene_idx` - Index of the assigned gene. + /// * `m_blocks` - Read's M-only aligned blocks in 0-based half-open genomic coords. + /// * `transcripts` - Slice of `TranscriptInfo` for this gene (from `QualimapIndex::gene_transcripts`). + /// * `tx_flat_offset` - Flat index offset: the index of the first transcript of this gene + /// in the global transcripts vec. + pub fn add_coverage( + &mut self, + m_blocks: &[(i32, i32)], + transcripts: &[TranscriptInfo], + tx_flat_offset: u32, + ) { + for (local_idx, tx_info) in transcripts.iter().enumerate() { + let flat_idx = tx_flat_offset + local_idx as u32; + let tx_len = tx_info.length as usize; + if tx_len == 0 { + continue; + } + + // Get or create the depth array for this transcript + let depth = self + .coverage + .entry(flat_idx) + .or_insert_with(|| vec![0i32; tx_len]); + + // Map each M-block to transcript-relative coordinates + let tx_start = tx_info.start; + let tx_end = tx_info.end; + for &(block_start, block_end) in m_blocks { + // Clip to transcript span + let clipped_start = block_start.max(tx_start); + let clipped_end = block_end.min(tx_end); + if clipped_start >= clipped_end { + continue; + } + + // Convert to transcript-relative position + let rel_start = (clipped_start - tx_start) as usize; + let rel_end = (clipped_end - tx_start) as usize; + let rel_end = rel_end.min(tx_len); + + for d in &mut depth[rel_start..rel_end] { + *d += 1; + } + } + } + } + + /// Merge another `TranscriptCoverage` into this one. + /// + /// For transcripts present in both, adds depth arrays element-wise. + /// For transcripts only in `other`, moves them into `self`. + pub fn merge(&mut self, other: TranscriptCoverage) { + for (tx_idx, other_depth) in other.coverage { + let entry = self.coverage.entry(tx_idx); + match entry { + std::collections::hash_map::Entry::Occupied(mut e) => { + let self_depth = e.get_mut(); + for (i, &val) in other_depth.iter().enumerate() { + if i < self_depth.len() { + self_depth[i] += val; + } + } + } + std::collections::hash_map::Entry::Vacant(e) => { + e.insert(other_depth); + } + } + } + } + + /// Get the depth array for a transcript (by flat index). + #[allow(dead_code)] + pub fn get(&self, tx_flat_idx: u32) -> Option<&[i32]> { + self.coverage.get(&tx_flat_idx).map(|v| v.as_slice()) + } + + /// Number of transcripts with coverage data. + #[allow(dead_code)] + pub fn num_transcripts_with_coverage(&self) -> usize { + self.coverage.len() + } + + /// Iterate over all transcripts with coverage data. + pub fn iter(&self) -> impl Iterator { + self.coverage.iter().map(|(&idx, v)| (idx, v.as_slice())) + } + + /// Compute mean coverage for a transcript. + #[allow(dead_code)] + pub fn mean_coverage(&self, tx_flat_idx: u32) -> f64 { + match self.coverage.get(&tx_flat_idx) { + Some(depth) if !depth.is_empty() => { + let sum: i64 = depth.iter().map(|&d| d as i64).sum(); + sum as f64 / depth.len() as f64 + } + _ => 0.0, + } + } +} + +// ============================================================ +// Unit tests +// ============================================================ + +#[cfg(test)] +mod tests { + use super::*; + + fn make_tx_info(start: i32, end: i32) -> TranscriptInfo { + TranscriptInfo { + gene_idx: 0, + transcript_idx: 0, + gene_id: "g1".to_string(), + transcript_id: "t1".to_string(), + chrom: "chr1".to_string(), + start, + end, + strand: '+', + length: (end - start) as u32, + exons: vec![(start, end)], + exonic_length: (end - start) as u32, + } + } + + #[test] + fn test_add_coverage_single_block() { + let mut cov = TranscriptCoverage::new(); + let tx = make_tx_info(100, 200); + // M-block: 120..150 (0-based half-open) + cov.add_coverage(&[(120, 150)], &[tx], 0); + + let depth = cov.get(0).unwrap(); + assert_eq!(depth.len(), 100); + // Positions 20..50 (relative) should have depth 1 + assert_eq!(depth[19], 0); + assert_eq!(depth[20], 1); + assert_eq!(depth[49], 1); + assert_eq!(depth[50], 0); + } + + #[test] + fn test_add_coverage_clipping() { + let mut cov = TranscriptCoverage::new(); + let tx = make_tx_info(100, 200); + // M-block extends beyond transcript: 180..220 + cov.add_coverage(&[(180, 220)], &[tx], 0); + + let depth = cov.get(0).unwrap(); + // Only 80..100 (relative) should be covered + assert_eq!(depth[79], 0); + assert_eq!(depth[80], 1); + assert_eq!(depth[99], 1); + } + + #[test] + fn test_merge() { + let mut cov1 = TranscriptCoverage::new(); + let tx = make_tx_info(100, 200); + cov1.add_coverage(&[(120, 150)], &[tx.clone()], 0); + + let mut cov2 = TranscriptCoverage::new(); + cov2.add_coverage(&[(130, 160)], &[tx], 0); + + cov1.merge(cov2); + let depth = cov1.get(0).unwrap(); + // 120..130 relative 20..30: depth 1 + assert_eq!(depth[25], 1); + // 130..150 relative 30..50: depth 2 + assert_eq!(depth[35], 2); + // 150..160 relative 50..60: depth 1 + assert_eq!(depth[55], 1); + } + + #[test] + fn test_mean_coverage() { + let mut cov = TranscriptCoverage::new(); + let tx = make_tx_info(0, 10); + // Cover all 10 bases with depth 1 + cov.add_coverage(&[(0, 10)], &[tx], 0); + let mean = cov.mean_coverage(0); + assert!((mean - 1.0).abs() < 1e-10); + + // Non-existent transcript + assert!((cov.mean_coverage(99) - 0.0).abs() < 1e-10); + } +} diff --git a/src/rna/qualimap/index.rs b/src/rna/qualimap/index.rs new file mode 100644 index 0000000..27e3275 --- /dev/null +++ b/src/rna/qualimap/index.rs @@ -0,0 +1,418 @@ +//! Qualimap-compatible annotation index for RNA-Seq QC. +//! +//! Builds per-chromosome COITrees for exon overlap queries and intron interval +//! trees. Each exon node stores its parent gene and transcript, enabling +//! Qualimap's enclosure-based gene assignment (every M-block must be fully +//! enclosed by exons of the same gene). + +use coitrees::{COITree, Interval, IntervalTree}; +use indexmap::IndexMap; +use log::debug; +use std::collections::HashMap; + +use crate::gtf::Gene; + +// ============================================================ +// Exon node metadata stored in the COITree +// ============================================================ + +/// Metadata for a single exon interval in the COITree. +/// +/// Each node represents one exon of one transcript of one gene. +/// Qualimap assigns reads by checking that all M-blocks are *enclosed* +/// (fully contained) within exons of the same gene. +#[derive(Debug, Clone, Copy)] +pub struct ExonMeta { + /// Index into the gene list (position in the IndexMap). + pub gene_idx: u32, + /// Index into the transcript list within the gene. + #[allow(dead_code)] + pub transcript_idx: u16, + /// Exon start in 0-based half-open coordinates. + pub exon_start: i32, + /// Exon end in 0-based half-open coordinates. + pub exon_end: i32, + /// Strand ('+', '-', or '.'). + #[allow(dead_code)] + pub strand: u8, +} + +impl Default for ExonMeta { + fn default() -> Self { + Self { + gene_idx: 0, + transcript_idx: 0, + exon_start: 0, + exon_end: 0, + strand: b'.', + } + } +} + +/// Per-chromosome exon COITree for overlap queries. +pub type ExonTree = COITree; + +// ============================================================ +// Intron node metadata +// ============================================================ + +/// Metadata for an intron interval (gap between consecutive exons of a transcript). +#[derive(Debug, Clone, Copy, Default)] +#[allow(dead_code)] +pub struct IntronMeta { + /// Index into the gene list. + pub gene_idx: u32, +} + +/// Per-chromosome intron COITree for intronic read classification. +pub type IntronTree = COITree; + +// ============================================================ +// Per-transcript metadata +// ============================================================ + +/// Pre-computed metadata for a single transcript, used for coverage tracking. +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct TranscriptInfo { + /// Gene index (position in the IndexMap). + pub gene_idx: u32, + /// Transcript index within the gene. + pub transcript_idx: u16, + /// Gene ID string (for output). + pub gene_id: String, + /// Transcript ID string. + pub transcript_id: String, + /// Chromosome name. + pub chrom: String, + /// Transcript start (0-based). + pub start: i32, + /// Transcript end (0-based half-open). + pub end: i32, + /// Strand. + pub strand: char, + /// Total transcript length (end - start). + pub length: u32, + /// Sorted exon intervals in 0-based half-open coordinates. + pub exons: Vec<(i32, i32)>, + /// Total exonic bases (sum of exon lengths). + pub exonic_length: u32, +} + +// ============================================================ +// QualimapIndex — the main index structure +// ============================================================ + +/// Complete Qualimap annotation index built from GTF genes. +/// +/// Contains per-chromosome exon and intron trees, plus transcript metadata. +/// This index is *separate* from the existing dupRadar/featureCounts index +/// because Qualimap uses fundamentally different assignment logic. +pub struct QualimapIndex { + /// Per-chromosome exon COITrees (key = chromosome name). + pub exon_trees: HashMap, + /// Per-chromosome intron COITrees (key = chromosome name). + pub intron_trees: HashMap, + /// All transcript metadata, indexed by a flat transcript ID. + /// The index is `(gene_idx, transcript_idx)` mapped to a flat position. + pub transcripts: Vec, + /// Lookup: gene_idx -> range of transcript indices in `transcripts` vec. + /// `gene_transcript_ranges[gene_idx] = (start, end)` half-open into `transcripts`. + pub gene_transcript_ranges: Vec<(u32, u32)>, + /// Total number of genes. + #[allow(dead_code)] + pub num_genes: u32, +} + +impl QualimapIndex { + /// Build the Qualimap index from GTF gene annotations. + /// + /// Creates per-chromosome exon COITrees (with enclosure metadata) and intron + /// interval trees. Also builds per-transcript metadata for coverage tracking. + /// + /// # Arguments + /// * `genes` - Gene annotations from GTF parsing (insertion-order preserved). + pub fn from_genes(genes: &IndexMap) -> Self { + // Collect exon intervals and intron intervals per chromosome + let mut exon_intervals: HashMap>> = HashMap::new(); + let mut intron_intervals: HashMap>> = HashMap::new(); + let mut transcripts = Vec::new(); + let mut gene_transcript_ranges = Vec::with_capacity(genes.len()); + + for (gene_idx, gene) in genes.values().enumerate() { + let gene_idx = gene_idx as u32; + let tx_start_idx = transcripts.len() as u32; + + for (tx_idx, tx) in gene.transcripts.iter().enumerate() { + let tx_idx = tx_idx as u16; + + // Convert transcript exons from 1-based inclusive (GTF) to 0-based half-open + let mut exons_0based: Vec<(i32, i32)> = tx + .exons + .iter() + .map(|(s, e)| ((*s as i32) - 1, *e as i32)) + .collect(); + exons_0based.sort_unstable_by_key(|(s, _)| *s); + + let exonic_length: u32 = exons_0based.iter().map(|(s, e)| (e - s) as u32).sum(); + + let tx_start = exons_0based.first().map(|(s, _)| *s).unwrap_or(0); + let tx_end = exons_0based.last().map(|(_, e)| *e).unwrap_or(0); + + // Add exon intervals to the per-chrom collection + let chrom_exons = exon_intervals.entry(tx.chrom.clone()).or_default(); + for &(start, end) in &exons_0based { + chrom_exons.push(Interval::new( + start, + end, + ExonMeta { + gene_idx, + transcript_idx: tx_idx, + exon_start: start, + exon_end: end, + strand: tx.strand as u8, + }, + )); + } + + // Add intron intervals (gaps between consecutive exons) + let chrom_introns = intron_intervals.entry(tx.chrom.clone()).or_default(); + for window in exons_0based.windows(2) { + let intron_start = window[0].1; + let intron_end = window[1].0; + if intron_end > intron_start { + chrom_introns.push(Interval::new( + intron_start, + intron_end, + IntronMeta { gene_idx }, + )); + } + } + + // Store transcript metadata + transcripts.push(TranscriptInfo { + gene_idx, + transcript_idx: tx_idx, + gene_id: gene.gene_id.clone(), + transcript_id: tx.transcript_id.clone(), + chrom: tx.chrom.clone(), + start: tx_start, + end: tx_end, + strand: tx.strand, + length: (tx_end - tx_start) as u32, + exons: exons_0based, + exonic_length, + }); + } + + let tx_end_idx = transcripts.len() as u32; + gene_transcript_ranges.push((tx_start_idx, tx_end_idx)); + } + + // Build COITrees from collected intervals + let exon_trees: HashMap = exon_intervals + .into_iter() + .map(|(chrom, intervals)| (chrom, COITree::new(&intervals))) + .collect(); + + let intron_trees: HashMap = intron_intervals + .into_iter() + .map(|(chrom, intervals)| (chrom, COITree::new(&intervals))) + .collect(); + + let num_genes = genes.len() as u32; + + debug!( + "Built Qualimap index: {} genes, {} transcripts, {} chromosomes with exons", + num_genes, + transcripts.len(), + exon_trees.len(), + ); + + Self { + exon_trees, + intron_trees, + transcripts, + gene_transcript_ranges, + num_genes, + } + } + + /// Get the exon tree for a chromosome. + pub fn exon_tree(&self, chrom: &str) -> Option<&ExonTree> { + self.exon_trees.get(chrom) + } + + /// Get the intron tree for a chromosome. + pub fn intron_tree(&self, chrom: &str) -> Option<&IntronTree> { + self.intron_trees.get(chrom) + } + + /// Get transcript info entries for a gene by gene index. + /// + /// Returns a slice of `TranscriptInfo` for all transcripts of the gene. + pub fn gene_transcripts(&self, gene_idx: u32) -> &[TranscriptInfo] { + let (start, end) = self.gene_transcript_ranges[gene_idx as usize]; + &self.transcripts[start as usize..end as usize] + } +} + +// ============================================================ +// Unit tests +// ============================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::gtf::{Exon, Gene, Transcript}; + + /// Helper to build a minimal gene for testing. + fn make_gene(gene_id: &str, chrom: &str, strand: char, transcripts: Vec) -> Gene { + let start = transcripts + .iter() + .flat_map(|t| t.exons.iter().map(|(s, _)| *s)) + .min() + .unwrap_or(1); + let end = transcripts + .iter() + .flat_map(|t| t.exons.iter().map(|(_, e)| *e)) + .max() + .unwrap_or(1); + let exons = transcripts + .iter() + .flat_map(|t| { + t.exons.iter().map(|(s, e)| Exon { + chrom: chrom.to_string(), + start: *s, + end: *e, + strand, + }) + }) + .collect(); + Gene { + gene_id: gene_id.to_string(), + chrom: chrom.to_string(), + start, + end, + strand, + exons, + effective_length: 0, + attributes: HashMap::new(), + transcripts, + } + } + + fn make_transcript( + tx_id: &str, + chrom: &str, + strand: char, + exons: Vec<(u64, u64)>, + ) -> Transcript { + let start = exons.iter().map(|(s, _)| *s).min().unwrap_or(1); + let end = exons.iter().map(|(_, e)| *e).max().unwrap_or(1); + Transcript { + transcript_id: tx_id.to_string(), + chrom: chrom.to_string(), + start, + end, + strand, + exons, + cds_start: None, + cds_end: None, + } + } + + #[test] + fn test_index_from_single_gene() { + let mut genes = IndexMap::new(); + let tx = make_transcript("tx1", "chr1", '+', vec![(101, 200), (301, 400)]); + let gene = make_gene("gene1", "chr1", '+', vec![tx]); + genes.insert("gene1".to_string(), gene); + + let index = QualimapIndex::from_genes(&genes); + + assert_eq!(index.num_genes, 1); + assert_eq!(index.transcripts.len(), 1); + assert!(index.exon_tree("chr1").is_some()); + assert!(index.intron_tree("chr1").is_some()); + assert!(index.exon_tree("chr2").is_none()); + + // Check transcript metadata + let ti = &index.transcripts[0]; + assert_eq!(ti.gene_id, "gene1"); + assert_eq!(ti.transcript_id, "tx1"); + assert_eq!(ti.exons.len(), 2); + // GTF 1-based inclusive (101, 200) -> 0-based half-open (100, 200) + assert_eq!(ti.exons[0], (100, 200)); + // GTF 1-based inclusive (301, 400) -> 0-based half-open (300, 400) + assert_eq!(ti.exons[1], (300, 400)); + assert_eq!(ti.exonic_length, 200); // 100 + 100 + + // Check gene transcript ranges + let txs = index.gene_transcripts(0); + assert_eq!(txs.len(), 1); + assert_eq!(txs[0].transcript_id, "tx1"); + + // Check that exon tree has entries + let tree = index.exon_tree("chr1").unwrap(); + let mut hits = Vec::new(); + tree.query(150, 160, |iv| hits.push(iv.metadata.clone())); + assert_eq!(hits.len(), 1); + assert_eq!(hits[0].gene_idx, 0); + + // Check intron tree: intron from 200..300 + let itree = index.intron_tree("chr1").unwrap(); + let mut ihits = Vec::new(); + itree.query(250, 260, |iv| ihits.push(iv.metadata.clone())); + assert_eq!(ihits.len(), 1); + assert_eq!(ihits[0].gene_idx, 0); + } + + #[test] + fn test_index_multiple_transcripts() { + let mut genes = IndexMap::new(); + let tx1 = make_transcript("tx1", "chr1", '+', vec![(101, 200), (301, 400)]); + let tx2 = make_transcript("tx2", "chr1", '+', vec![(101, 250), (351, 400)]); + let gene = make_gene("gene1", "chr1", '+', vec![tx1, tx2]); + genes.insert("gene1".to_string(), gene); + + let index = QualimapIndex::from_genes(&genes); + + assert_eq!(index.transcripts.len(), 2); + let txs = index.gene_transcripts(0); + assert_eq!(txs.len(), 2); + assert_eq!(txs[0].transcript_id, "tx1"); + assert_eq!(txs[1].transcript_id, "tx2"); + } + + #[test] + fn test_enclosure_check() { + // Simulate what the accumulator will do: check if an M-block + // is fully enclosed within any single exon of a gene. + let mut genes = IndexMap::new(); + let tx = make_transcript("tx1", "chr1", '+', vec![(101, 200), (301, 400)]); + let gene = make_gene("gene1", "chr1", '+', vec![tx]); + genes.insert("gene1".to_string(), gene); + + let index = QualimapIndex::from_genes(&genes); + let tree = index.exon_tree("chr1").unwrap(); + + // M-block 120..180 (0-based) should be enclosed by exon (100, 200) + let mut enclosed_genes = Vec::new(); + tree.query(120, 180, |iv| { + if 120 >= iv.metadata.exon_start && 180 <= iv.metadata.exon_end { + enclosed_genes.push(iv.metadata.gene_idx); + } + }); + assert_eq!(enclosed_genes, vec![0]); + + // M-block 190..310 (0-based) overlaps both exons but is NOT enclosed by either + let mut enclosed_genes2 = Vec::new(); + tree.query(190, 310, |iv| { + if 190 >= iv.metadata.exon_start && 310 <= iv.metadata.exon_end { + enclosed_genes2.push(iv.metadata.gene_idx); + } + }); + assert!(enclosed_genes2.is_empty()); + } +} diff --git a/src/rna/qualimap/mod.rs b/src/rna/qualimap/mod.rs new file mode 100644 index 0000000..6d48e69 --- /dev/null +++ b/src/rna/qualimap/mod.rs @@ -0,0 +1,69 @@ +//! Qualimap RNA-Seq QC module. +//! +//! Reimplements Qualimap's RNA-Seq quality control analysis with enclosure-based +//! gene assignment, per-transcript coverage tracking, and Qualimap-compatible output. + +pub mod accumulator; +pub mod coverage; +pub mod index; + +pub use accumulator::QualimapAccum; +pub use index::QualimapIndex; + +use std::collections::HashMap; + +// ============================================================================ +// Result types +// ============================================================================ + +/// Final Qualimap result, produced from a merged `QualimapAccum` + `CoverageTracker`. +/// +/// Holds all counters and coverage data needed to produce Qualimap-compatible +/// output files, plots, and the HTML report. +#[derive(Debug)] +#[allow(dead_code)] // Fields used in Phase 2 (output/plots/report) +pub struct QualimapResult { + // --- Read counters --- + /// Total primary alignments (excluding unmapped, secondary, supplementary, QC-fail). + pub primary_alignments: u64, + /// Total secondary alignments seen. + pub secondary_alignments: u64, + /// Reads skipped because they were not aligned. + pub not_aligned: u64, + /// Reads skipped because NH > 1 (multi-mappers). + pub alignment_not_unique: u64, + + // --- Gene assignment counters --- + /// Reads assigned to exactly one gene (exonic). + pub exonic_reads: u64, + /// Reads where >1 gene encloses all blocks (ambiguous). + pub ambiguous_reads: u64, + /// Reads with no gene enclosing all blocks. + pub no_feature: u64, + /// Reads classified as intronic (M-block overlaps intron tree). + pub intronic_reads: u64, + /// Reads classified as intergenic (no exon or intron overlap). + pub intergenic_reads: u64, + /// Reads overlapping exons from multiple genes. + pub overlapping_exon_reads: u64, + + // --- Fragment counters (PE) --- + /// Total reads counted (PE: each mate counts as 1 read). + pub read_count: u64, + /// Total fragments counted (PE: 1 per paired fragment). + pub fragment_count: u64, + /// Left-of-pair reads that are proper pairs. + pub left_proper_in_pair: u64, + /// Right-of-pair reads that are proper pairs. + pub right_proper_in_pair: u64, + + // --- Junction counters --- + /// Reads containing at least one splice junction (N-op in CIGAR). + pub reads_at_junctions: u64, + /// Junction motif counts: canonical_motif_string -> count. + pub junction_motifs: HashMap, + + // --- Per-transcript coverage --- + /// Per-transcript coverage arrays (transcript_key -> per-base coverage). + pub transcript_coverage: HashMap>, +} From b6533db79c7cb2b8af8b1d56af47328259518e32 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Mon, 16 Feb 2026 20:26:29 +0100 Subject: [PATCH 02/23] Add Qualimap rnaseq benchmark output for large dataset --- benchmark/qualimap/large/css/agogo.css | 471 ++++++++++ benchmark/qualimap/large/css/ajax-loader.gif | Bin 0 -> 673 bytes benchmark/qualimap/large/css/basic.css | 540 ++++++++++++ benchmark/qualimap/large/css/bgfooter.png | Bin 0 -> 434 bytes benchmark/qualimap/large/css/bgtop.png | Bin 0 -> 430 bytes .../qualimap/large/css/comment-bright.png | Bin 0 -> 3500 bytes .../qualimap/large/css/comment-close.png | Bin 0 -> 3578 bytes benchmark/qualimap/large/css/comment.png | Bin 0 -> 3445 bytes benchmark/qualimap/large/css/doctools.js | 247 ++++++ benchmark/qualimap/large/css/down-pressed.png | Bin 0 -> 368 bytes benchmark/qualimap/large/css/down.png | Bin 0 -> 363 bytes benchmark/qualimap/large/css/file.png | Bin 0 -> 392 bytes benchmark/qualimap/large/css/jquery.js | 154 ++++ benchmark/qualimap/large/css/minus.png | Bin 0 -> 199 bytes benchmark/qualimap/large/css/plus.png | Bin 0 -> 199 bytes benchmark/qualimap/large/css/pygments.css | 62 ++ .../large/css/qualimap_logo_small.png | Bin 0 -> 2922 bytes benchmark/qualimap/large/css/report.css | 50 ++ benchmark/qualimap/large/css/searchtools.js | 560 ++++++++++++ benchmark/qualimap/large/css/underscore.js | 23 + benchmark/qualimap/large/css/up-pressed.png | Bin 0 -> 372 bytes benchmark/qualimap/large/css/up.png | Bin 0 -> 363 bytes benchmark/qualimap/large/css/websupport.js | 808 ++++++++++++++++++ .../Coverage Profile Along Genes (High).png | Bin 0 -> 58604 bytes .../Coverage Profile Along Genes (Low).png | Bin 0 -> 73555 bytes .../Coverage Profile Along Genes (Total).png | Bin 0 -> 53485 bytes .../Junction Analysis.png | Bin 0 -> 38847 bytes .../Reads Genomic Origin.png | Bin 0 -> 38902 bytes .../Transcript coverage histogram.png | Bin 0 -> 64081 bytes benchmark/qualimap/large/qualimapReport.html | 324 +++++++ .../coverage_profile_along_genes_(high).txt | 101 +++ .../coverage_profile_along_genes_(low).txt | 101 +++ .../coverage_profile_along_genes_(total).txt | 101 +++ .../qualimap/large/rnaseq_qc_results.txt | 57 ++ 34 files changed, 3599 insertions(+) create mode 100644 benchmark/qualimap/large/css/agogo.css create mode 100644 benchmark/qualimap/large/css/ajax-loader.gif create mode 100644 benchmark/qualimap/large/css/basic.css create mode 100644 benchmark/qualimap/large/css/bgfooter.png create mode 100644 benchmark/qualimap/large/css/bgtop.png create mode 100644 benchmark/qualimap/large/css/comment-bright.png create mode 100644 benchmark/qualimap/large/css/comment-close.png create mode 100644 benchmark/qualimap/large/css/comment.png create mode 100644 benchmark/qualimap/large/css/doctools.js create mode 100644 benchmark/qualimap/large/css/down-pressed.png create mode 100644 benchmark/qualimap/large/css/down.png create mode 100644 benchmark/qualimap/large/css/file.png create mode 100644 benchmark/qualimap/large/css/jquery.js create mode 100644 benchmark/qualimap/large/css/minus.png create mode 100644 benchmark/qualimap/large/css/plus.png create mode 100644 benchmark/qualimap/large/css/pygments.css create mode 100644 benchmark/qualimap/large/css/qualimap_logo_small.png create mode 100644 benchmark/qualimap/large/css/report.css create mode 100644 benchmark/qualimap/large/css/searchtools.js create mode 100644 benchmark/qualimap/large/css/underscore.js create mode 100644 benchmark/qualimap/large/css/up-pressed.png create mode 100644 benchmark/qualimap/large/css/up.png create mode 100644 benchmark/qualimap/large/css/websupport.js create mode 100644 benchmark/qualimap/large/images_qualimapReport/Coverage Profile Along Genes (High).png create mode 100644 benchmark/qualimap/large/images_qualimapReport/Coverage Profile Along Genes (Low).png create mode 100644 benchmark/qualimap/large/images_qualimapReport/Coverage Profile Along Genes (Total).png create mode 100644 benchmark/qualimap/large/images_qualimapReport/Junction Analysis.png create mode 100644 benchmark/qualimap/large/images_qualimapReport/Reads Genomic Origin.png create mode 100644 benchmark/qualimap/large/images_qualimapReport/Transcript coverage histogram.png create mode 100644 benchmark/qualimap/large/qualimapReport.html create mode 100644 benchmark/qualimap/large/raw_data_qualimapReport/coverage_profile_along_genes_(high).txt create mode 100644 benchmark/qualimap/large/raw_data_qualimapReport/coverage_profile_along_genes_(low).txt create mode 100644 benchmark/qualimap/large/raw_data_qualimapReport/coverage_profile_along_genes_(total).txt create mode 100644 benchmark/qualimap/large/rnaseq_qc_results.txt diff --git a/benchmark/qualimap/large/css/agogo.css b/benchmark/qualimap/large/css/agogo.css new file mode 100644 index 0000000..3e96e34 --- /dev/null +++ b/benchmark/qualimap/large/css/agogo.css @@ -0,0 +1,471 @@ +/* + * agogo.css_t + * ~~~~~~~~~~~ + * + * Sphinx stylesheet -- agogo theme. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +* { + margin: 0px; + padding: 0px; +} + +body { + font-family: "Verdana", Arial, sans-serif; + line-height: 1.4em; + color: black; + background-color: #eeeeec; +} + + +/* Page layout */ + +div.header, div.content, div.footer { + width: 70em; + margin-left: auto; + margin-right: auto; +} + +div.header-wrapper { + background: url(bgtop.png) top left repeat-x; + border-bottom: 3px solid #2e3436; +} + + +/* Default body styles */ +a { + color: #ce5c00; +} + +div.bodywrapper a, div.footer a { + text-decoration: underline; +} + +.clearer { + clear: both; +} + +.left { + float: left; +} + +.right { + float: right; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +h1, h2, h3, h4 { + font-family: "Georgia", "Times New Roman", serif; + font-weight: normal; + color: #3465a4; + margin-bottom: .8em; +} + +h1 { + color: #204a87; +} + +h2 { + padding-bottom: .5em; + border-bottom: 1px solid #3465a4; +} + +a.headerlink { + visibility: hidden; + color: #dddddd; + padding-left: .3em; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +img { + border: 0; +} + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 2px 7px 1px 7px; + border-left: 0.2em solid black; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +/* Header */ + +div.header { + padding-top: 10px; + padding-bottom: 10px; +} + +div.header .headertitle { + font-family: "Georgia", "Times New Roman", serif; + font-weight: normal; + font-size: 180%; + letter-spacing: .08em; + margin-bottom: .8em; +} + +div.header .headertitle a { + color: white; +} + +div.header div.rel { + margin-top: 1em; +} + +div.header div.rel a { + color: #fcaf3e; + letter-spacing: .1em; + text-transform: uppercase; +} + +p.logo { + float: right; +} + +img.logo { + border: 0; +} + + +/* Content */ +div.content-wrapper { + background-color: white; + padding-top: 20px; + padding-bottom: 20px; +} + +div.document { + width: 50em; + float: left; +} + +div.body { + padding-right: 2em; + text-align: justify; +} + +div.document h1 { + line-height: 120%; +} + +div.document ul { + margin: 1.5em; + list-style-type: square; +} + +div.document dd { + margin-left: 1.2em; + margin-top: .4em; + margin-bottom: 1em; +} + +div.document .section { + margin-top: 1.7em; +} +div.document .section:first-child { + margin-top: 0px; +} + +div.document div.highlight { + padding: 3px; + background-color: #eeeeec; + border-top: 2px solid #dddddd; + border-bottom: 2px solid #dddddd; + margin-top: .8em; + margin-bottom: .8em; +} + +div.document h2 { + margin-top: .7em; +} + +div.document p { + margin-bottom: .5em; +} + +div.document li.toctree-l1 { + margin-bottom: 1em; +} + +div.document .descname { + font-weight: bold; +} + +div.document .docutils.literal { + background-color: #eeeeec; + padding: 1px; +} + +div.document .docutils.xref.literal { + background-color: transparent; + padding: 0px; +} + +div.document blockquote { + margin: 1em; +} + +div.document ol { + margin: 1.5em; +} + + +/* Sidebar */ + +div.sidebar { + width: 20em; + float: right; + font-size: .9em; +} + +div.sidebar a, div.header a { + text-decoration: none; +} + +div.sidebar a:hover, div.header a:hover { + text-decoration: underline; +} + +div.sidebar h3 { + color: #2e3436; + text-transform: uppercase; + font-size: 130%; + letter-spacing: .1em; +} + +div.sidebar ul { + list-style-type: none; +} + +div.sidebar li.toctree-l1 a { + display: block; + padding: 1px; + border: 1px solid #dddddd; + background-color: #eeeeec; + margin-bottom: .4em; + padding-left: 3px; + color: #2e3436; +} + +div.sidebar li.toctree-l2 a { + background-color: transparent; + border: none; + margin-left: 1em; + border-bottom: 1px solid #dddddd; +} + +div.sidebar li.toctree-l3 a { + background-color: transparent; + border: none; + margin-left: 2em; + border-bottom: 1px solid #dddddd; +} + +div.sidebar li.toctree-l2:last-child a { + border-bottom: none; +} + +div.sidebar li.toctree-l1.current a { + border-right: 5px solid #fcaf3e; +} + +div.sidebar li.toctree-l1.current li.toctree-l2 a { + border-right: none; +} + +div.sidebar input[type="text"] { + width: 170px; +} + +div.sidebar input[type="submit"] { + width: 30px; +} + + +/* Footer */ + +div.footer-wrapper { + background: url(bgfooter.png) top left repeat-x; + border-top: 4px solid #babdb6; + padding-top: 10px; + padding-bottom: 10px; + min-height: 80px; +} + +div.footer, div.footer a { + color: #888a85; +} + +div.footer .right { + text-align: right; +} + +div.footer .left { + text-transform: uppercase; +} + + +/* Styles copied from basic theme */ + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-center-qualimap { + display: block; + margin-left: auto; + margin-right: auto; + width: 100% +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +/* -- viewcode extension ---------------------------------------------------- */ + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family:: "Verdana", Arial, sans-serif; +} + +div.viewcode-block:target { + margin: -1px -3px; + padding: 0 3px; + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} diff --git a/benchmark/qualimap/large/css/ajax-loader.gif b/benchmark/qualimap/large/css/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..61faf8cab23993bd3e1560bff0668bd628642330 GIT binary patch literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nno%(3)e{?)x>&1u}A`t?OF7Z|1gRivOgXi&7IyQd1Pl zGfOfQ60;I3a`F>X^fL3(@);C=vM_KlFfb_o=k{|A33hf2a5d61U}gjg=>Rd%XaNQW zW@Cw{|b%Y*pl8F?4B9 zlo4Fz*0kZGJabY|>}Okf0}CCg{u4`zEPY^pV?j2@h+|igy0+Kz6p;@SpM4s6)XEMg z#3Y4GX>Hjlml5ftdH$4x0JGdn8~MX(U~_^d!Hi)=HU{V%g+mi8#UGbE-*ao8f#h+S z2a0-5+vc7MU$e-NhmBjLIC1v|)9+Im8x1yacJ7{^tLX(ZhYi^rpmXm0`@ku9b53aN zEXH@Y3JaztblgpxbJt{AtE1ad1Ca>{v$rwwvK(>{m~Gf_=-Ro7Fk{#;i~+{{>QtvI yb2P8Zac~?~=sRA>$6{!(^3;ZP0TPFR(G_-UDU(8Jl0?(IXu$~#4A!880|o%~Al1tN literal 0 HcmV?d00001 diff --git a/benchmark/qualimap/large/css/basic.css b/benchmark/qualimap/large/css/basic.css new file mode 100644 index 0000000..1e02c5d --- /dev/null +++ b/benchmark/qualimap/large/css/basic.css @@ -0,0 +1,540 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 0px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + width: 30px; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/benchmark/qualimap/large/css/bgfooter.png b/benchmark/qualimap/large/css/bgfooter.png new file mode 100644 index 0000000000000000000000000000000000000000..9ce5bdd902943fdf8b0c0ca6a545297e1e2cc665 GIT binary patch literal 434 zcmV;j0ZsmiP)Px#24YJ`L;%wO*8tD73qoQ5000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXD> z2Q(2CT#42I000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0003ENklR?sq9~H`=l5UI-{JW_f9!)=Hwush3JC}Y z1gFM&r>$lJNPt^*1k!w;l|obx>lr$2IOaI$n=(gBBaj^I0=y%@K5N&GIU&-%OE_~V zX=m=_j7d`hvubQRuF+xT63vIfWnC3%kKN*T3l7ob3nEC2R->wU1Y)4)(7_t^thiqb zj$CO7xBn9gg`*!MY$}SI|_*)!a*&V0w7h>cUb&$Grh37iJ=C%Yn c>}w1E0Z4f>1OEiDlmGw#07*qoM6N<$g4BwtIsgCw literal 0 HcmV?d00001 diff --git a/benchmark/qualimap/large/css/bgtop.png b/benchmark/qualimap/large/css/bgtop.png new file mode 100644 index 0000000000000000000000000000000000000000..a0d4709bac8f79943a817195c086461c8c4d5419 GIT binary patch literal 430 zcmV;f0a5;mP)Px#24YJ`L;zI)R{&FzA;Z4_000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXD> z2Q3AZhV-)l000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0003ANklMo8vqN`cM=KwSQV|n zk}naE+VzlN;kK@Ej${PSkI$-R6-Yfp`zA;^O$`)7`gRi{-0i?owGIbX{p>Nc##93U z;sA|ayOYkG%F9M0iEMUM*s3NDYSS=KN2ht8Rv|7nv77i{NTO47R)}V_+2H~mL-nTR z_8j}*%6Qm8?#7NU2kM$#gcP&kO?iw|n}ynz+r-~FA9nKcZnfixWvZ&d28Cc_6&_Pe zMpbjI>9r+<=}NIDz4mCd3U++H?rrHcYxH&eeB|)>mnv*N#44ILM2zL6yU!VVWSrgp Y0Yu&#qm)=by8r+H07*qoM6N<$f@HC)j{pDw literal 0 HcmV?d00001 diff --git a/benchmark/qualimap/large/css/comment-bright.png b/benchmark/qualimap/large/css/comment-bright.png new file mode 100644 index 0000000000000000000000000000000000000000..551517b8c83b76f734ff791f847829a760ad1903 GIT binary patch literal 3500 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 A2LJ#7 literal 0 HcmV?d00001 diff --git a/benchmark/qualimap/large/css/comment.png b/benchmark/qualimap/large/css/comment.png new file mode 100644 index 0000000000000000000000000000000000000000..92feb52b8824c6b0f59b658b1196c61de9162a95 GIT binary patch literal 3445 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 Xn!ZpXD%P(H00000NkvXXu0mjf= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/benchmark/qualimap/large/css/down-pressed.png b/benchmark/qualimap/large/css/down-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..6f7ad782782e4f8e39b0c6e15c7344700cdd2527 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}Z23@f-Ava~9&<9T!#}JFtXD=!G zGdl{fK6ro2OGiOl+hKvH6i=D3%%Y^j`yIkRn!8O>@bG)IQR0{Kf+mxNd=_WScA8u_ z3;8(7x2){m9`nt+U(Nab&1G)!{`SPVpDX$w8McLTzAJ39wprG3p4XLq$06M`%}2Yk zRPPsbES*dnYm1wkGL;iioAUB*Or2kz6(-M_r_#Me-`{mj$Z%( literal 0 HcmV?d00001 diff --git a/benchmark/qualimap/large/css/down.png b/benchmark/qualimap/large/css/down.png new file mode 100644 index 0000000000000000000000000000000000000000..3003a88770de3977d47a2ba69893436a2860f9e7 GIT binary patch literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}xaV3tUZ$qnrLa#kt978NlpS`ru z&)HFc^}^>{UOEce+71h5nn>6&w6A!ieNbu1wh)UGh{8~et^#oZ1# z>T7oM=FZ~xXWnTo{qnXm$ZLOlqGswI_m2{XwVK)IJmBjW{J3-B3x@C=M{ShWt#fYS9M?R;8K$~YwlIqwf>VA7q=YKcwf2DS4Zj5inDKXXB1zl=(YO3ST6~rDq)&z z*o>z)=hxrfG-cDBW0G$!?6{M<$@{_4{m1o%Ub!naEtn|@^frU1tDnm{r-UW|!^@B8 literal 0 HcmV?d00001 diff --git a/benchmark/qualimap/large/css/file.png b/benchmark/qualimap/large/css/file.png new file mode 100644 index 0000000000000000000000000000000000000000..d18082e397e7e54f20721af768c4c2983258f1b4 GIT binary patch literal 392 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP$HyOL$D9)yc9|lc|nKf<9@eUiWd>3GuTC!a5vdfWYEazjncPj5ZQX%+1 zt8B*4=d)!cdDz4wr^#OMYfqGz$1LDFF>|#>*O?AGil(WEs?wLLy{Gj2J_@opDm%`dlax3yA*@*N$G&*ukFv>P8+2CBWO(qz zD0k1@kN>hhb1_6`&wrCswzINE(evt-5C1B^STi2@PmdKI;Vst0PQB6!2kdN literal 0 HcmV?d00001 diff --git a/benchmark/qualimap/large/css/jquery.js b/benchmark/qualimap/large/css/jquery.js new file mode 100644 index 0000000..7c24308 --- /dev/null +++ b/benchmark/qualimap/large/css/jquery.js @@ -0,0 +1,154 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/benchmark/qualimap/large/css/minus.png b/benchmark/qualimap/large/css/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..da1c5620d10c047525a467a425abe9ff5269cfc2 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1SHkYJtzcHoCO|{#XvD(5N2eUHAey{$X?>< z>&kweokM_|(Po{+Q=kw>iEBiObAE1aYF-J$w=>iB1I2R$WLpMkF=>bh=@O1TaS?83{1OVknK< z>&kweokM`jkU7Va11Q8%;u=xnoS&PUnpeW`?aZ|OK(QcC7sn8Z%gHvy&v=;Q4jejg zV8NnAO`-4Z@2~&zopr02WF_WB>pF literal 0 HcmV?d00001 diff --git a/benchmark/qualimap/large/css/pygments.css b/benchmark/qualimap/large/css/pygments.css new file mode 100644 index 0000000..1a14f2a --- /dev/null +++ b/benchmark/qualimap/large/css/pygments.css @@ -0,0 +1,62 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #303030 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0040D0 } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/benchmark/qualimap/large/css/qualimap_logo_small.png b/benchmark/qualimap/large/css/qualimap_logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..7e65e76f8d0c7b562da731eeaf6d25cc65525b6b GIT binary patch literal 2922 zcmV-w3zhVVP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L09-}@09-}^6qG|j00007bV*G`2iyY; z4+xN`@9+0p>%y=M%diZ~jv}%pm5ecyRQ2N`G7i|J zsxJeJ%H{G)?mgDp_rw6T)0d&@ZxrUpmul|~V8w%GjF|>p0G!dJ-a1vCFCz1;wNLIq z;F$uXwf7cl?dh(}=YgA2%3Nlx{ZW15gL8pjHUFCC^QGFNxRNz=+OnxYw+byMCRc*CU0B%>+(ZJq` z-_LxWYpF+-N*H`P39P2oTDOf#PmumR3jSw>J=h# znODZIdmFH?E8DHAvqfZ6;(M#CwfB1<86zSyQ+$i%*4jsXFqO+?j4|VZ*~XYdfcF9M z2y6si2bKf(`E@&?8dcp#M2<@MU8w34&RqbJbi1P{n_meiCj)o8iJAb66KNPXZ5a2I z_C;YpzEpMK$rS~r*D00JUfb3RycmVSwPx!sI0b6vEWYK?s07-Jp-W?5@jcIbx{RrU8NAiM(j zK@0D5IdA|V2r6)x*TRDwhJ3|KfR8p)nJ-nT^yE$ukv{{{kKKMu`fU{vqA)l!U#kAL z(i2eV-3*AU1deU6j`amHCJKXlfg=*1KDQDE*9d$pU#h;)X;k>xCVjftqe~LkpG%Qvx)W6ZuWb+$=`eQB0WL~5<5P|+m`EAB;r za!k_AP-VIL@lJ})C=7loB6m4`Dg#I&HFo_?6b2W0paRv6er)CXs38AR)gw}^ud)~L zbQA_3$fDq`j8%SU#^AnEE|=G2`8UoAypd7&!=2{TT2YiS=I5$9p-I{GiIjl2a^0%> z)pEJK`8$IWd1dRl!v~70?jD7~RM)Um5@WCmc(K8=P3VlmAP1c9tvpHOIW~BGRzI8q zuCDat#y0AfwfQozdkEtBG0qd zinVqWuq&`GrH^xlE>R|b@qyy3%Xx0TlUCt65u`=x;tW;o7O(SC2g4GDG5Jz;mwc(( z1>B$w3sxNG%CshwFCHA%1J47i(+s+x$46l>r4uR~kzrG-Z46^Iqru&sCMesWr@X2j z44kN{mr&1)-)OC!>(TVO*ilZ%xMHz5q5~>yC%jD6Bd}KCp9$Oi#Yz~QkYpKPp2CO* zJp*;#0_?9)0M0?V23QCjStwQC^v3V{boA(yMvhEFjn)+24@_3oy+KPEB=faSqQbrb zkx5#2+!BJxe(-ziJ!uug!rCgY2feTUr`b22c>MQ<^1#$@^0HC}`sRn8V+ zW|Dye2l-6&^K`sT3LS$MRXd2rn=@v|6Y(utPGnfe3*ftefYnK{hmI4Q- zygt%kkpKhPB+GkwAOBPo8M=(NHt7l;O4jhORcqs zB+4Gb_952;mtLztg^sSJpN)w7&nSAH& z=NV%*0&f{(rUTz=;t%VfI`NWiA1XaL@}=sEs3%whj7@3CZJBf>zF}g0#y36^g~7Lg zhbv()LE!7ajIp zZW56Tfg&&knBaA+4|o-L%K2UU;@)Z>gSpic$xaKjEl -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('
').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; +} + + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, success: null, + dataType: "script", cache: true}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (var i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('

' + _('Searching') + '

').appendTo(this.out); + this.dots = $('').appendTo(this.title); + this.status = $('

').appendTo(this.out); + this.output = $('