From d88de15cdca34d267e42b3c0dc98f77ba369b1d0 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Tue, 7 Mar 2023 11:12:13 +0700 Subject: [PATCH] Refactoring codes for removing buffer size options. An some other changes... Former-commit-id: 10b57cc1a27446ade3c444217341e9651e89cdce --- doc/book/annexes.qmd | 125 ++++++----- doc/book/comm_sampling.qmd | 28 ++- doc/book/expressions.qmd | 53 ++++- doc/build/_book/OBITools-V4.epub | Bin 231848 -> 233964 bytes doc/build/_book/annexes.html | 205 +++++++++++------ doc/build/_book/comm_sampling.html | 21 +- doc/build/_book/expressions.html | 71 +++++- doc/build/_man/man1/obigrep.man | 14 +- doc/lib/options/selection/_max-length.qmd | 3 + doc/lib/options/selection/_min-length.qmd | 3 + doc/lib/options/selection/_sequence.qmd | 7 + doc/man/obigrep.qmd | 6 +- pkg/goutils/goutils.go | 10 +- pkg/goutils/slices.go | 24 ++ pkg/obiapat/pcr.go | 18 -- pkg/obichunk/chunk_on_disk.go | 10 +- pkg/obichunk/chunks.go | 11 +- pkg/obichunk/options.go | 14 -- pkg/obichunk/subchunks.go | 15 +- pkg/obichunk/unique.go | 14 +- pkg/obiformats/csv_writer.go | 248 +++++++++++++++++++++ pkg/obiformats/ecopcr_read.go | 2 +- pkg/obiformats/embl_read.go | 4 +- pkg/obiformats/fastseq_header.go | 3 +- pkg/obiformats/fastseq_read.go | 4 +- pkg/obiformats/fastseq_write_fasta.go | 3 +- pkg/obiformats/fastseq_write_fastq.go | 3 +- pkg/obiformats/genbank_read.go | 4 +- pkg/obiformats/options.go | 147 ++++++++++-- pkg/obiiter/batchiterator.go | 67 +----- pkg/obiiter/distribute.go | 7 +- pkg/obiiter/merge.go | 6 +- pkg/obiiter/workers.go | 25 +-- pkg/obingslibrary/worker.go | 18 -- pkg/obioptions/options.go | 22 +- pkg/obiseq/attributes.go | 9 + pkg/obiseq/biosequence.go | 25 +++ pkg/obiseq/class.go | 1 + pkg/obiseq/eval.go | 21 +- pkg/{obieval => obiseq}/language.go | 19 +- pkg/obiseq/predicate.go | 3 +- pkg/obitools/obicleandb/obicleandb.go | 63 ++++++ pkg/obitools/obiconvert/options.go | 31 +-- pkg/obitools/obiconvert/sequence_reader.go | 7 +- pkg/obitools/obiconvert/sequence_writer.go | 1 - pkg/obitools/obicsv/obicsv.go | 61 +++++ pkg/obitools/obicsv/options.go | 126 +++++++++++ pkg/obitools/obidistribute/distribute.go | 1 - pkg/obitools/obigrep/grep.go | 1 - pkg/obitools/obimultiplex/demultiplex.go | 1 - pkg/obitools/obipairing/pairing.go | 6 +- pkg/obitools/obiuniq/unique.go | 2 - 52 files changed, 1172 insertions(+), 421 deletions(-) create mode 100644 doc/lib/options/selection/_max-length.qmd create mode 100644 doc/lib/options/selection/_min-length.qmd create mode 100644 doc/lib/options/selection/_sequence.qmd create mode 100644 pkg/goutils/slices.go create mode 100644 pkg/obiformats/csv_writer.go rename pkg/{obieval => obiseq}/language.go (84%) create mode 100644 pkg/obitools/obicleandb/obicleandb.go create mode 100644 pkg/obitools/obicsv/obicsv.go create mode 100644 pkg/obitools/obicsv/options.go diff --git a/doc/book/annexes.qmd b/doc/book/annexes.qmd index 80d7e27..7eb0467 100644 --- a/doc/book/annexes.qmd +++ b/doc/book/annexes.qmd @@ -1,82 +1,107 @@ # Annexes -### Sequence attributes +## Sequence attributes -#### Reserved sequence attributes +**ali_dir (`string`)** -##### `ali_dir` + - Set by the *obipairing* tool + - The attribute can contain 2 string values `left` or `right`. -###### Type : `string` + The alignment generated by *obipairing* is a 3'-end gap free algorithm. + Two cases can occur when aligning the forward and reverse reads. If the + barcode is long enough, both the reads overlap only on their 3' ends. In + such case, the alignment direction `ali_dir` is set to *left*. If the + barcode is shorter than the read length, the paired reads overlap by + their 5' ends, and the complete barcode is sequenced by both the reads. + In that later case, `ali_dir` is set to *right*. -The attribute can contain 2 string values `"left"` or `"right".` +**ali_length (`int`)** -###### Set by the *obipairing* tool + - Set by the *obipairing* tool -The alignment generated by *obipairing* is a 3'-end gap free algorithm. -Two cases can occur when aligning the forward and reverse reads. If the -barcode is long enough, both the reads overlap only on their 3' ends. In -such case, the alignment direction `ali_dir` is set to *left*. If the -barcode is shorter than the read length, the paired reads overlap by -their 5' ends, and the complete barcode is sequenced by both the reads. -In that later case, `ali_dir` is set to *right*. + Length of the aligned parts when merging forward and reverse reads -##### `ali_length` -###### Set by the *obipairing* tool +**count (`int`)** -Length of the aligned parts when merging forward and reverse reads + - Set by the *obiuniq* tool + - Getter : method `Count()` + - Setter : method `SetCount(int)` -##### `count` : the number of sequence occurrences + The `count` attribute indicates how-many strictly identical reads + have been merged in a single record. It contains an integer value. If it + is absent this means that the sequence record represents a single + occurrence of the sequence. -###### Set by the *obiuniq* tool + The `Count()` method allows to access to the count attribute as an + integer value. If the `count` attribute is not defined for the given + sequence, the value *1* is returned -The `count` attribute indicates how-many strictly identical sequences -have been merged in a single record. It contains an integer value. If it -is absent this means that the sequence record represents a single -occurrence of the sequence. +**merged_* (`map[string]int`)** -###### Getter : method `Count()` + - Set by the *obiuniq* tool -The `Count()` method allows to access to the count attribute as an -integer value. If the `count` attribute is not defined for the given -sequence, the value *1* is returned + The `-m` option of the *obiuniq* tools allows for keeping track of the + distribution of the values stored in given attribute of interest. Often + this option is used to summarise distribution of a sequence variant + accross samples when *obiuniq* is run after running *obimultiplex*. The + actual name of the attribute depends on the name of the monitored + attribute. If `-m` option is used with the attribute *sample*, then this + attribute names *merged_sample*. -##### `merged_*` +**mode (`string`)** -###### Type : `map[string]int` + - Set by the *obipairing* tool + - The attribute can contain 2 string values `join` or `alignment`. -###### Set by the *obiuniq* tool -The `-m` option of the *obiuniq* tools allows for keeping track of the -distribution of the values stored in given attribute of interest. Often -this option is used to summarise distribution of a sequence variant -accross samples when *obiuniq* is run after running *obimultiplex*. The -actual name of the attribute depends on the name of the monitored -attribute. If `-m` option is used with the attribute *sample*, then this -attribute names *merged_sample*. +**obitag_ref_index (`map[string]string`)** -##### `mode` + - Set by the *obirefidx* tool. -###### Set by the *obipairing* tool + It resumes to which taxonomic annotation a match to that sequence must + lead according to the number of differences existing between the query + sequence and the reference sequence having that tag. -**`obitag_ref_index`** +```json + {"0":"9606@Homo sapiens@species", + "2":"207598@Homininae@subfamily", + "3":"9604@Hominidae@family", + "8":"314295@Hominoidea@superfamily", + "10":"9526@Catarrhini@parvorder", + "12":"1437010@Boreoeutheria@clade", + "16":"9347@Eutheria@clade", + "17":"40674@Mammalia@class", + "22":"117571@Euteleostomi@clade", + "25":"7776@Gnathostomata@clade", + "29":"33213@Bilateria@clade", + "30":"6072@Eumetazoa@clade"} +``` -###### Set by the *obirefidx* tool. +**pairing_mismatches (`map[string]string`)** -It resumes to which taxonomic annotation a match to that sequence must -lead according to the number of differences existing between the query -sequence and the reference sequence having that tag. + - Set by the *obipairing* tool -###### Getter : method `Count()` +**seq_a_single (`int`)** -##### `pairing_mismatches` + - Set by the *obipairing* tool -###### Set by the *obipairing* tool +**seq_ab_match (`int`)** -##### `score` + - Set by the *obipairing* tool -###### Set by the *obipairing* tool +**seq_b_single (`int`)** -##### `score_norm` + - Set by the *obipairing* tool -###### Set by the *obipairing* tool +**score (`int`)** + + - Set by the *obipairing* tool + +**score_norm (`float`)** + + - Set by the *obipairing* tool + - The value ranges between 0 and 1. + + Score of the alignment between forward and reverse reads expressed as a fraction of identity. + diff --git a/doc/book/comm_sampling.qmd b/doc/book/comm_sampling.qmd index 0a3ba45..a8ca2a6 100644 --- a/doc/book/comm_sampling.qmd +++ b/doc/book/comm_sampling.qmd @@ -10,13 +10,39 @@ Sequences can be selected on several of their caracteristics, their length, their id, their sequence. Options allow for specifying the condition if selection. +**Selection based on the sequence** + + +Sequence records can be selected according if they match or not with a pattern. The simplest pattern is as short sequence (*e.g* `AACCTT`). But the usage of regular patterns allows for looking for more complex pattern. As example, `A[TG]C+G` matches a `A`, followed by a `T` or a `G`, then one or several `C` and endly a `G`. + +{{< include ../lib/options/selection/_sequence.qmd >}} + +*Examples:* + +: Selects only the sequence records that contain an *EcoRI* restriction site. + +```bash +obigrep -s 'GAATTC' seq1.fasta > seq2.fasta +``` + +: Selects only the sequence records that contain a stretch of at least 10 ``A``. + +```bash +obigrep -s 'A{10,}' seq1.fasta > seq2.fasta +``` + +: Selects only the sequence records that do not contain ambiguous nucleotides. + +```bash +obigrep -s '^[ACGT]+$' seq1.fasta > seq2.fasta +``` {{< include ../lib/options/selection/_min-count.qmd >}} {{< include ../lib/options/selection/_max-count.qmd >}} -Example +*Examples* : Selecting sequence records representing at least five reads in the dataset. diff --git a/doc/book/expressions.qmd b/doc/book/expressions.qmd index 71261c4..4a1a883 100644 --- a/doc/book/expressions.qmd +++ b/doc/book/expressions.qmd @@ -11,26 +11,64 @@ Several OBITools (*e.g.* obigrep, obiannotate) allow the user to specify some si ### Instrospection functions {.unnumbered} -- `len(x)`is a generic function allowing to retreive the size of a object. It returns +**`len(x)`** + +: It is a generic function allowing to retreive the size of a object. It returns the length of a sequences, the number of element in a map like `annotations`, the number of elements in an array. The reurned value is an `int`. ### Cast functions {.unnumbered} -- `int(x)` converts if possible the `x` value to an integer value. The function +**`int(x)`** + +: Converts if possible the `x` value to an integer value. The function returns an `int`. -- `numeric(x)` converts if possible the `x` value to a float value. The function + +**`numeric(x)`** + +: Converts if possible the `x` value to a float value. The function returns a `float`. -- `bool(x)` converts if possible the `x` value to a boolean value. The function + +**`bool(x)`** + +: Converts if possible the `x` value to a boolean value. The function returns a `bool`. ### String related functions {.unnumbered} -- `printf(format,...)` allows to combine several values to build a string. `format` follows the +**`printf(format,...)`** + +: Allows to combine several values to build a string. `format` follows the classical C `printf` syntax. The function returns a `string`. -- `subspc(x)` substitutes every space in the `x` string by the underscore (`_`) character. The function + +**`subspc(x)`** + +: substitutes every space in the `x` string by the underscore (`_`) character. The function returns a `string`. +### Condition function {.unnumbered} + +**`ifelse(condition,val1,val2)`** + +: The `condition` value has to be a `bool` value. If it is `true` the function returns `val1`, + otherwise, it is returning `val2`. + +### Sequence analysis related function + +**`composition(sequence)`** + +: The nucleotide composition of the sequence is returned as as map indexed by `a`, `c`, `g`, or `t` and + each value is the number of occurrences of that nucleotide. A fifth key `others` accounts for + all others symboles. + +**`gcskew(sequence)`** + +: Computes the excess of g compare to c of the sequence, known as the GC skew. + + $$ + Skew_{GC}=\frac{G-C}{G+C} + $$ + ## Accessing to the sequence annotations The `annotations` variable is a map object containing all the annotations associated to the currently processed sequence. Index of the map are the attribute names. It exists to possibillities to retreive @@ -53,4 +91,7 @@ Special attributes of the sequence are accessible only by dedicated methods of t - The sequence identifier : `Id()` - THe sequence definition : `Definition()` +```go +sequence.Id() +``` diff --git a/doc/build/_book/OBITools-V4.epub b/doc/build/_book/OBITools-V4.epub index 934b2b41320aa8a189fe883396427880c736651b..8bffd15ed72f4e56cf16500105c6c95213a3bb5e 100644 GIT binary patch delta 41767 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 Au>b%7 delta 39503 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 KKQV^ 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 code.sourceCode { white-space: pre; position: relative; } +pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } +pre > code.sourceCode > span:empty { height: 1.2em; } +.sourceCode { overflow: visible; } +code.sourceCode > span { color: inherit; text-decoration: inherit; } +div.sourceCode { margin: 1em 0; } +pre.sourceCode { margin: 0; } +@media screen { +div.sourceCode { overflow: auto; } +} +@media print { +pre > code.sourceCode { white-space: pre-wrap; } +pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; } +} +pre.numberSource code + { counter-reset: source-line 0; } +pre.numberSource code > span + { position: relative; left: -4em; counter-increment: source-line; } +pre.numberSource code > span > a:first-child::before + { content: counter(source-line); + position: relative; left: -1em; text-align: right; vertical-align: baseline; + border: none; display: inline-block; + -webkit-touch-callout: none; -webkit-user-select: none; + -khtml-user-select: none; -moz-user-select: none; + -ms-user-select: none; user-select: none; + padding: 0 4px; width: 4em; + color: #aaaaaa; + } +pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; } +div.sourceCode + { } +@media screen { +pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; } +} +code span.al { color: #ff0000; font-weight: bold; } /* Alert */ +code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */ +code span.at { color: #7d9029; } /* Attribute */ +code span.bn { color: #40a070; } /* BaseN */ +code span.bu { color: #008000; } /* BuiltIn */ +code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */ +code span.ch { color: #4070a0; } /* Char */ +code span.cn { color: #880000; } /* Constant */ +code span.co { color: #60a0b0; font-style: italic; } /* Comment */ +code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */ +code span.do { color: #ba2121; font-style: italic; } /* Documentation */ +code span.dt { color: #902000; } /* DataType */ +code span.dv { color: #40a070; } /* DecVal */ +code span.er { color: #ff0000; font-weight: bold; } /* Error */ +code span.ex { } /* Extension */ +code span.fl { color: #40a070; } /* Float */ +code span.fu { color: #06287e; } /* Function */ +code span.im { color: #008000; font-weight: bold; } /* Import */ +code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */ +code span.kw { color: #007020; font-weight: bold; } /* Keyword */ +code span.op { color: #666666; } /* Operator */ +code span.ot { color: #007020; } /* Other */ +code span.pp { color: #bc7a00; } /* Preprocessor */ +code span.sc { color: #4070a0; } /* SpecialChar */ +code span.ss { color: #bb6688; } /* SpecialString */ +code span.st { color: #4070a0; } /* String */ +code span.va { color: #19177c; } /* Variable */ +code span.vs { color: #4070a0; } /* VerbatimString */ +code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */ @@ -215,7 +278,7 @@ ul.task-list li input[type="checkbox"] {

Table of contents

@@ -239,84 +302,82 @@ ul.task-list li input[type="checkbox"] { -
-

A.0.1 Sequence attributes

-
-

A.0.1.1 Reserved sequence attributes

-
-
A.0.1.1.1 ali_dir
-
-
A.0.1.1.1.1 Type : string
-

The attribute can contain 2 string values "left" or "right".

-
-
-
A.0.1.1.1.2 Set by the obipairing tool
+
+

A.1 Sequence attributes

+

ali_dir (string)

+
    +
  • Set by the obipairing tool
  • +
  • The attribute can contain 2 string values left or right.
  • +

The alignment generated by obipairing is a 3’-end gap free algorithm. Two cases can occur when aligning the forward and reverse reads. If the barcode is long enough, both the reads overlap only on their 3’ ends. In such case, the alignment direction ali_dir is set to left. If the barcode is shorter than the read length, the paired reads overlap by their 5’ ends, and the complete barcode is sequenced by both the reads. In that later case, ali_dir is set to right.

-
-
-
-
A.0.1.1.2 ali_length
-
-
A.0.1.1.2.1 Set by the obipairing tool
+

ali_length (int)

+
    +
  • Set by the obipairing tool
  • +

Length of the aligned parts when merging forward and reverse reads

-
-
-
-
A.0.1.1.3 count : the number of sequence occurrences
-
-
A.0.1.1.3.1 Set by the obiuniq tool
-

The count attribute indicates how-many strictly identical sequences have been merged in a single record. It contains an integer value. If it is absent this means that the sequence record represents a single occurrence of the sequence.

-
-
-
A.0.1.1.3.2 Getter : method Count()
+

count (int)

+
    +
  • Set by the obiuniq tool
  • +
  • Getter : method Count()
  • +
  • Setter : method SetCount(int)
  • +
+

The count attribute indicates how-many strictly identical reads have been merged in a single record. It contains an integer value. If it is absent this means that the sequence record represents a single occurrence of the sequence.

The Count() method allows to access to the count attribute as an integer value. If the count attribute is not defined for the given sequence, the value 1 is returned

-
-
-
-
A.0.1.1.4 merged_*
-
-
A.0.1.1.4.1 Type : map[string]int
-
-
-
A.0.1.1.4.2 Set by the obiuniq tool
+

merged_* (map[string]int)

+
    +
  • Set by the obiuniq tool
  • +

The -m option of the obiuniq tools allows for keeping track of the distribution of the values stored in given attribute of interest. Often this option is used to summarise distribution of a sequence variant accross samples when obiuniq is run after running obimultiplex. The actual name of the attribute depends on the name of the monitored attribute. If -m option is used with the attribute sample, then this attribute names merged_sample.

-
-
-
-
A.0.1.1.5 mode
-
-
A.0.1.1.5.1 Set by the obipairing tool
-

obitag_ref_index

-
-
-
A.0.1.1.5.2 Set by the obirefidx tool.
+

mode (string)

+
    +
  • Set by the obipairing tool
  • +
  • The attribute can contain 2 string values join or alignment.
  • +
+

obitag_ref_index (map[string]string)

+
    +
  • Set by the obirefidx tool.
  • +

It resumes to which taxonomic annotation a match to that sequence must lead according to the number of differences existing between the query sequence and the reference sequence having that tag.

-
-
-
A.0.1.1.5.3 Getter : method Count()
-
-
-
-
A.0.1.1.6 pairing_mismatches
-
-
A.0.1.1.6.1 Set by the obipairing tool
-
-
-
-
A.0.1.1.7 score
-
-
A.0.1.1.7.1 Set by the obipairing tool
-
-
-
-
A.0.1.1.8 score_norm
-
-
A.0.1.1.8.1 Set by the obipairing tool
+
   {"0":"9606@Homo sapiens@species",
+    "2":"207598@Homininae@subfamily",
+    "3":"9604@Hominidae@family",
+    "8":"314295@Hominoidea@superfamily",
+    "10":"9526@Catarrhini@parvorder",
+    "12":"1437010@Boreoeutheria@clade",
+    "16":"9347@Eutheria@clade",
+    "17":"40674@Mammalia@class",
+    "22":"117571@Euteleostomi@clade",
+    "25":"7776@Gnathostomata@clade",
+    "29":"33213@Bilateria@clade",
+    "30":"6072@Eumetazoa@clade"}
+

pairing_mismatches (map[string]string)

+
    +
  • Set by the obipairing tool
  • +
+

seq_a_single (int)

+
    +
  • Set by the obipairing tool
  • +
+

seq_ab_match (int)

+
    +
  • Set by the obipairing tool
  • +
+

seq_b_single (int)

+
    +
  • Set by the obipairing tool
  • +
+

score (int)

+
    +
  • Set by the obipairing tool
  • +
+

score_norm (float)

+
    +
  • Set by the obipairing tool
  • +
  • The value ranges between 0 and 1.
  • +
+

Score of the alignment between forward and reverse reads expressed as a fraction of identity.

-
-
-
diff --git a/doc/build/_book/comm_sampling.html b/doc/build/_book/comm_sampling.html index a7e2001..5eaf17b 100644 --- a/doc/build/_book/comm_sampling.html +++ b/doc/build/_book/comm_sampling.html @@ -314,6 +314,23 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni

12.1.1.1 Selecting sequences based on their caracteristics

Sequences can be selected on several of their caracteristics, their length, their id, their sequence. Options allow for specifying the condition if selection.

+

Selection based on the sequence

+

Sequence records can be selected according if they match or not with a pattern. The simplest pattern is as short sequence (e.g AACCTT). But the usage of regular patterns allows for looking for more complex pattern. As example, A[TG]C+G matches a A, followed by a T or a G, then one or several C and endly a G.

+
+
--sequence|-s PATTERN
+
+

Regular expression pattern to be tested against the sequence itself. The pattern is case insensitive. A complete description of the regular pattern grammar is available here.

+
+
Examples:
+
+

Selects only the sequence records that contain an EcoRI restriction site.

+
+
+
obigrep -s 'GAATTC' seq1.fasta > seq2.fasta
+

: Selects only the sequence records that contain a stretch of at least 10 A.

+
obigrep -s 'A{10,}' seq1.fasta > seq2.fasta
+

: Selects only the sequence records that do not contain ambiguous nucleotides.

+
obigrep -s '^[ACGT]+$' seq1.fasta > seq2.fasta
--min-count | -c COUNT
@@ -323,12 +340,12 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni

only sequences reprensenting no more than COUNT reads will be selected. That option rely on the count attribute. If the count attribute is not defined for a sequence record, it is assumed equal to \(1\).

-
Example
+
Examples

Selecting sequence records representing at least five reads in the dataset.

-
obigrep -c 5 data_SPER01.fasta > data_norare_SPER01.fasta
+
obigrep -c 5 data_SPER01.fasta > data_norare_SPER01.fasta
diff --git a/doc/build/_book/expressions.html b/doc/build/_book/expressions.html index 7ccf909..8bc2ad5 100644 --- a/doc/build/_book/expressions.html +++ b/doc/build/_book/expressions.html @@ -124,6 +124,7 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni } } + @@ -284,6 +285,8 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni
  • Instrospection functions
  • Cast functions
  • String related functions
  • +
  • Condition function
  • +
  • 7.2.1 Sequence analysis related function
  • 7.3 Accessing to the sequence annotations
  • @@ -321,24 +324,67 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni

    7.2 Function defined in the language

    Instrospection functions

    -
      -
    • len(x)is a generic function allowing to retreive the size of a object. It returns the length of a sequences, the number of element in a map like annotations, the number of elements in an array. The reurned value is an int.
    • -
    +
    +
    len(x)
    +
    +

    It is a generic function allowing to retreive the size of a object. It returns the length of a sequences, the number of element in a map like annotations, the number of elements in an array. The reurned value is an int.

    +
    +

    Cast functions

    -
      -
    • int(x) converts if possible the x value to an integer value. The function returns an int.
    • -
    • numeric(x) converts if possible the x value to a float value. The function returns a float.
    • -
    • bool(x) converts if possible the x value to a boolean value. The function returns a bool.
    • -
    +
    +
    int(x)
    +
    +

    Converts if possible the x value to an integer value. The function returns an int.

    +
    +
    numeric(x)
    +
    +

    Converts if possible the x value to a float value. The function returns a float.

    +
    +
    bool(x)
    +
    +

    Converts if possible the x value to a boolean value. The function returns a bool.

    +
    +
    +
    +

    Condition function

    +
    +
    ifelse(condition,val1,val2)
    +
    +

    The condition value has to be a bool value. If it is true the function returns val1, otherwise, it is returning val2.

    +
    +
    +
    +
    @@ -352,6 +398,7 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni
  • The sequence identifier : Id()
  • THe sequence definition : Definition()
  • +
    sequence.Id()
    diff --git a/doc/build/_man/man1/obigrep.man b/doc/build/_man/man1/obigrep.man index 4c8e9a1..a27dc67 100644 --- a/doc/build/_man/man1/obigrep.man +++ b/doc/build/_man/man1/obigrep.man @@ -174,14 +174,22 @@ selected. That option rely on the \f[V]count\f[R] attribute. If the \f[V]count\f[R] attribute is not defined for a sequence record, it is assumed equal to 1. -.PP +.TP \f[B]--max-length\f[R] | \f[B]-L\f[R] \f[I]LENGTH\f[R] -.PP +Keeps sequence records whose sequence length is equal or shorter than +\f[I]LENGTH\f[R]. +.TP \f[B]--min-length\f[R] | \f[B]-l\f[R] \f[I]LENGTH\f[R] +Keeps sequence records whose sequence length is equal or longer than +\f[I]LENGTH\f[R]. .PP \f[B]--predicate\f[R]|\f[B]-p\f[R] \f[I]EXPRESSION\f[R] -.PP +.TP \f[B]--sequence\f[R]|\f[B]-s\f[R] \f[I]PATTERN\f[R] +Regular expression pattern to be tested against the sequence itself. +The pattern is case insensitive. +A complete description of the regular pattern grammar is available +here (https://yourbasic.org/golang/regexp-cheat-sheet/#cheat-sheet). .PP \f[B]--inverse-match\f[R] | \f[B]-v\f[R] .PP diff --git a/doc/lib/options/selection/_max-length.qmd b/doc/lib/options/selection/_max-length.qmd new file mode 100644 index 0000000..8264c5b --- /dev/null +++ b/doc/lib/options/selection/_max-length.qmd @@ -0,0 +1,3 @@ +**\--max-length** | **-L** _LENGTH_ + +: Keeps sequence records whose sequence length is equal or shorter than _LENGTH_. diff --git a/doc/lib/options/selection/_min-length.qmd b/doc/lib/options/selection/_min-length.qmd new file mode 100644 index 0000000..e64defc --- /dev/null +++ b/doc/lib/options/selection/_min-length.qmd @@ -0,0 +1,3 @@ +**\--min-length** | **-l** _LENGTH_ + +: Keeps sequence records whose sequence length is equal or longer than _LENGTH_. diff --git a/doc/lib/options/selection/_sequence.qmd b/doc/lib/options/selection/_sequence.qmd new file mode 100644 index 0000000..fa5c5ca --- /dev/null +++ b/doc/lib/options/selection/_sequence.qmd @@ -0,0 +1,7 @@ +**\--sequence**|**-s** _PATTERN_ + +: Regular expression pattern to be tested against the + sequence itself. The pattern is case insensitive. A + complete description of the regular pattern grammar + is available [here](https://yourbasic.org/golang/regexp-cheat-sheet/#cheat-sheet). + \ No newline at end of file diff --git a/doc/man/obigrep.qmd b/doc/man/obigrep.qmd index 05b4e6d..14af0f5 100644 --- a/doc/man/obigrep.qmd +++ b/doc/man/obigrep.qmd @@ -99,13 +99,13 @@ The OBITools are centered around the [FASTA] (https://en.wikipedia.org/wiki/FAST {{< include ../lib/options/selection/_min-count.qmd >}} -**\--max-length** | **-L** _LENGTH_ +{{< include ../lib/options/selection/_max-length.qmd >}} -**\--min-length** | **-l** _LENGTH_ +{{< include ../lib/options/selection/_min-length.qmd >}} **\--predicate**|**-p** _EXPRESSION_ -**\--sequence**|**-s** _PATTERN_ +{{< include ../lib/options/selection/_sequence.qmd >}} **\--inverse-match** | **-v** diff --git a/pkg/goutils/goutils.go b/pkg/goutils/goutils.go index 4015f76..136668d 100644 --- a/pkg/goutils/goutils.go +++ b/pkg/goutils/goutils.go @@ -13,6 +13,7 @@ import ( "github.com/barkimedes/go-deepcopy" ) + // InterfaceToInt converts a interface{} to an integer value if possible. // If not a "NotAnInteger" error is returned via the err // return value and val is set to 0. @@ -302,15 +303,6 @@ func ReadLines(path string) (lines []string, err error) { return } -func Contains[T comparable](arr []T, x T) bool { - for _, v := range arr { - if v == x { - return true - } - } - return false -} - func AtomicCounter(initial ...int) func() int { counterMutex := sync.Mutex{} counter := 0 diff --git a/pkg/goutils/slices.go b/pkg/goutils/slices.go new file mode 100644 index 0000000..738abe5 --- /dev/null +++ b/pkg/goutils/slices.go @@ -0,0 +1,24 @@ +package goutils + + +func Contains[T comparable](arr []T, x T) bool { + for _, v := range arr { + if v == x { + return true + } + } + return false +} + +func LookFor[T comparable](arr []T, x T) int { + for i, v := range arr { + if v == x { + return i + } + } + return -1 +} + +func RemoveIndex[T comparable](s []T, index int) []T { + return append(s[:index], s[index+1:]...) +} diff --git a/pkg/obiapat/pcr.go b/pkg/obiapat/pcr.go index e446997..c87f879 100644 --- a/pkg/obiapat/pcr.go +++ b/pkg/obiapat/pcr.go @@ -13,7 +13,6 @@ type _Options struct { circular bool forwardError int reverseError int - bufferSize int batchSize int parallelWorkers int forward ApatPattern @@ -66,12 +65,6 @@ func (options Options) Circular() bool { return options.pointer.circular } -// BufferSize returns the size of the channel -// buffer specified by the options -func (options Options) BufferSize() int { - return options.pointer.bufferSize -} - // BatchSize returns the size of the // sequence batch used by the PCR algorithm func (options Options) BatchSize() int { @@ -95,7 +88,6 @@ func MakeOptions(setters []WithOption) Options { circular: false, parallelWorkers: 4, batchSize: 100, - bufferSize: 100, forward: NilApatPattern, cfwd: NilApatPattern, reverse: NilApatPattern, @@ -188,16 +180,6 @@ func OptionCircular(circular bool) WithOption { return f } -// OptionBufferSize sets the requested channel -// buffer size. -func OptionBufferSize(size int) WithOption { - f := WithOption(func(opt Options) { - opt.pointer.bufferSize = size - }) - - return f -} - // OptionParallelWorkers sets how many search // jobs will be run in parallel. func OptionParallelWorkers(nworkers int) WithOption { diff --git a/pkg/obichunk/chunk_on_disk.go b/pkg/obichunk/chunk_on_disk.go index 772eba7..fdfef49 100644 --- a/pkg/obichunk/chunk_on_disk.go +++ b/pkg/obichunk/chunk_on_disk.go @@ -36,20 +36,14 @@ func find(root, ext string) []string { } func ISequenceChunkOnDisk(iterator obiiter.IBioSequence, - classifier *obiseq.BioSequenceClassifier, - sizes ...int) (obiiter.IBioSequence, error) { + classifier *obiseq.BioSequenceClassifier) (obiiter.IBioSequence, error) { dir, err := tempDir() if err != nil { return obiiter.NilIBioSequence, err } - bufferSize := iterator.BufferSize() - if len(sizes) > 0 { - bufferSize = sizes[0] - } - - newIter := obiiter.MakeIBioSequence(bufferSize) + newIter := obiiter.MakeIBioSequence() newIter.Add(1) diff --git a/pkg/obichunk/chunks.go b/pkg/obichunk/chunks.go index 13b3404..f88e779 100644 --- a/pkg/obichunk/chunks.go +++ b/pkg/obichunk/chunks.go @@ -10,16 +10,9 @@ import ( ) func ISequenceChunk(iterator obiiter.IBioSequence, - classifier *obiseq.BioSequenceClassifier, - sizes ...int) (obiiter.IBioSequence, error) { + classifier *obiseq.BioSequenceClassifier) (obiiter.IBioSequence, error) { - bufferSize := iterator.BufferSize() - - if len(sizes) > 0 { - bufferSize = sizes[0] - } - - newIter := obiiter.MakeIBioSequence(bufferSize) + newIter := obiiter.MakeIBioSequence() newIter.Add(1) diff --git a/pkg/obichunk/options.go b/pkg/obichunk/options.go index e49a614..d6ffa03 100644 --- a/pkg/obichunk/options.go +++ b/pkg/obichunk/options.go @@ -6,7 +6,6 @@ type __options__ struct { navalue string cacheOnDisk bool batchCount int - bufferSize int batchSize int parallelWorkers int noSingleton bool @@ -25,7 +24,6 @@ func MakeOptions(setters []WithOption) Options { navalue: "NA", cacheOnDisk: false, batchCount: 100, - bufferSize: 2, batchSize: 5000, parallelWorkers: 4, noSingleton: false, @@ -65,10 +63,6 @@ func (opt Options) BatchCount() int { return opt.pointer.batchCount } -func (opt Options) BufferSize() int { - return opt.pointer.bufferSize -} - func (opt Options) BatchSize() int { return opt.pointer.batchSize } @@ -148,14 +142,6 @@ func OptionsBatchSize(size int) WithOption { return f } -func OptionsBufferSize(size int) WithOption { - f := WithOption(func(opt Options) { - opt.pointer.bufferSize = size - }) - - return f -} - func OptionsNoSingleton() WithOption { f := WithOption(func(opt Options) { opt.pointer.noSingleton = true diff --git a/pkg/obichunk/subchunks.go b/pkg/obichunk/subchunks.go index b55942c..7250946 100644 --- a/pkg/obichunk/subchunks.go +++ b/pkg/obichunk/subchunks.go @@ -58,20 +58,13 @@ func (by _By) Sort(seqs []sSS) { func ISequenceSubChunk(iterator obiiter.IBioSequence, classifier *obiseq.BioSequenceClassifier, - sizes ...int) (obiiter.IBioSequence, error) { + nworkers int) (obiiter.IBioSequence, error) { - bufferSize := iterator.BufferSize() - nworkers := 4 - - if len(sizes) > 0 { - nworkers = sizes[0] + if nworkers <=0 { + nworkers = 4 } - if len(sizes) > 1 { - bufferSize = sizes[1] - } - - newIter := obiiter.MakeIBioSequence(bufferSize) + newIter := obiiter.MakeIBioSequence() newIter.Add(nworkers) diff --git a/pkg/obichunk/unique.go b/pkg/obichunk/unique.go index ec2d202..aa3631b 100644 --- a/pkg/obichunk/unique.go +++ b/pkg/obichunk/unique.go @@ -19,7 +19,7 @@ func IUniqueSequence(iterator obiiter.IBioSequence, opts := MakeOptions(options) nworkers := opts.ParallelWorkers() - iUnique := obiiter.MakeIBioSequence(opts.BufferSize()) + iUnique := obiiter.MakeIBioSequence() iterator = iterator.Speed("Splitting data set") @@ -28,8 +28,7 @@ func IUniqueSequence(iterator obiiter.IBioSequence, if opts.SortOnDisk() { nworkers = 1 iterator, err = ISequenceChunkOnDisk(iterator, - obiseq.HashClassifier(opts.BatchCount()), - 0) + obiseq.HashClassifier(opts.BatchCount())) if err != nil { return obiiter.NilIBioSequence, err @@ -37,8 +36,7 @@ func IUniqueSequence(iterator obiiter.IBioSequence, } else { iterator, err = ISequenceChunk(iterator, - obiseq.HashClassifier(opts.BatchCount()), - opts.BufferSize()) + obiseq.HashClassifier(opts.BatchCount())) if err != nil { return obiiter.NilIBioSequence, err @@ -78,12 +76,11 @@ func IUniqueSequence(iterator obiiter.IBioSequence, icat-- input, err = ISequenceSubChunk(input, classifier, - 1, - opts.BufferSize()) + 1) var next obiiter.IBioSequence if icat >= 0 { - next = obiiter.MakeIBioSequence(opts.BufferSize()) + next = obiiter.MakeIBioSequence() iUnique.Add(1) @@ -130,7 +127,6 @@ func IUniqueSequence(iterator obiiter.IBioSequence, iMerged := iUnique.IMergeSequenceBatch(opts.NAValue(), opts.StatsOn(), - opts.BufferSize(), ) return iMerged, nil diff --git a/pkg/obiformats/csv_writer.go b/pkg/obiformats/csv_writer.go new file mode 100644 index 0000000..b4b590a --- /dev/null +++ b/pkg/obiformats/csv_writer.go @@ -0,0 +1,248 @@ +package obiformats + +import ( + "bytes" + "encoding/csv" + "fmt" + "io" + "os" + "sync" + "time" + + "git.metabarcoding.org/lecasofts/go/obitools/pkg/goutils" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obiiter" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obiseq" + log "github.com/sirupsen/logrus" +) + +func CSVRecord(sequence *obiseq.BioSequence, opt Options) []string { + keys := opt.CSVKeys() + record := make([]string, 0, len(keys)+4) + + if opt.CSVId() { + record = append(record, sequence.Id()) + } + + if opt.CSVCount() { + record = append(record, fmt.Sprint(sequence.Count())) + } + + if opt.CSVTaxon() { + taxid := sequence.Taxid() + sn, ok := sequence.GetAttribute("scientific_name") + + if !ok { + if taxid == 1 { + sn = "root" + } else { + sn = opt.CSVNAValue() + } + } + + record = append(record, fmt.Sprint(taxid), fmt.Sprint(sn)) + } + + if opt.CSVDefinition() { + record = append(record, sequence.Definition()) + } + + for _, key := range opt.CSVKeys() { + value, ok := sequence.GetAttribute(key) + if !ok { + value = opt.CSVNAValue() + } + + svalue, _ := goutils.InterfaceToString(value) + record = append(record, svalue) + } + + if opt.CSVSequence() { + record = append(record, string(sequence.Sequence())) + } + + if opt.CSVQuality() { + if sequence.HasQualities() { + l := sequence.Len() + q := sequence.Qualities() + ascii := make([]byte, l) + quality_shift := opt.QualityShift() + for j := 0; j < l; j++ { + ascii[j] = uint8(q[j]) + uint8(quality_shift) + } + record = append(record, string(ascii)) + } else { + record = append(record, opt.CSVNAValue()) + } + } + + return record +} + +func CSVHeader(opt Options) []string { + keys := opt.CSVKeys() + record := make([]string, 0, len(keys)+4) + + if opt.CSVId() { + record = append(record, "id") + } + + if opt.CSVCount() { + record = append(record, "count") + } + + if opt.CSVTaxon() { + record = append(record, "taxid", "scientific_name") + } + + if opt.CSVDefinition() { + record = append(record, "definition") + } + + record = append(record, opt.CSVKeys()...) + + if opt.CSVSequence() { + record = append(record, "sequence") + } + + if opt.CSVQuality() { + record = append(record, "quality") + } + + return record +} + +func FormatCVSBatch(batch obiiter.BioSequenceBatch, opt Options) []byte { + buff := new(bytes.Buffer) + csv := csv.NewWriter(buff) + + if batch.Order() == 0 { + csv.Write(CSVHeader(opt)) + } + for _, s := range batch.Slice() { + csv.Write(CSVRecord(s, opt)) + } + + csv.Flush() + + return buff.Bytes() +} + +func WriteCSV(iterator obiiter.IBioSequence, + file io.WriteCloser, + options ...WithOption) (obiiter.IBioSequence, error) { + opt := MakeOptions(options) + + file, _ = goutils.CompressStream(file, opt.CompressedFile(), opt.CloseFile()) + + newIter := obiiter.MakeIBioSequence() + + nwriters := opt.ParallelWorkers() + + obiiter.RegisterAPipe() + chunkchan := make(chan FileChunck) + + newIter.Add(nwriters) + var waitWriter sync.WaitGroup + + go func() { + newIter.WaitAndClose() + for len(chunkchan) > 0 { + time.Sleep(time.Millisecond) + } + close(chunkchan) + waitWriter.Wait() + }() + + ff := func(iterator obiiter.IBioSequence) { + for iterator.Next() { + + batch := iterator.Get() + + chunkchan <- FileChunck{ + FormatCVSBatch(batch, opt), + batch.Order(), + } + newIter.Push(batch) + } + newIter.Done() + } + + log.Debugln("Start of the CSV file writing") + go ff(iterator) + for i := 0; i < nwriters-1; i++ { + go ff(iterator.Split()) + } + + next_to_send := 0 + received := make(map[int]FileChunck, 100) + + waitWriter.Add(1) + go func() { + for chunk := range chunkchan { + if chunk.order == next_to_send { + file.Write(chunk.text) + next_to_send++ + chunk, ok := received[next_to_send] + for ok { + file.Write(chunk.text) + delete(received, next_to_send) + next_to_send++ + chunk, ok = received[next_to_send] + } + } else { + received[chunk.order] = chunk + } + + } + + file.Close() + + log.Debugln("End of the CSV file writing") + obiiter.UnregisterPipe() + waitWriter.Done() + + }() + + return newIter, nil +} + +func WriteCSVToStdout(iterator obiiter.IBioSequence, + options ...WithOption) (obiiter.IBioSequence, error) { + options = append(options, OptionDontCloseFile()) + return WriteCSV(iterator, os.Stdout, options...) +} + +func WriteCSVToFile(iterator obiiter.IBioSequence, + filename string, + options ...WithOption) (obiiter.IBioSequence, error) { + + opt := MakeOptions(options) + flags := os.O_WRONLY | os.O_CREATE + + if opt.AppendFile() { + flags |= os.O_APPEND + } + file, err := os.OpenFile(filename, flags, 0660) + + if err != nil { + log.Fatalf("open file error: %v", err) + return obiiter.NilIBioSequence, err + } + + options = append(options, OptionCloseFile()) + + iterator, err = WriteCSV(iterator, file, options...) + + if opt.HaveToSavePaired() { + var revfile *os.File + + revfile, err = os.OpenFile(opt.PairedFileName(), flags, 0660) + if err != nil { + log.Fatalf("open file error: %v", err) + return obiiter.NilIBioSequence, err + } + iterator, err = WriteCSV(iterator.PairedWith(), revfile, options...) + } + + return iterator, err +} diff --git a/pkg/obiformats/ecopcr_read.go b/pkg/obiformats/ecopcr_read.go index 20d7fb3..c3da13e 100644 --- a/pkg/obiformats/ecopcr_read.go +++ b/pkg/obiformats/ecopcr_read.go @@ -166,7 +166,7 @@ func ReadEcoPCR(reader io.Reader, options ...WithOption) obiiter.IBioSequence { opt := MakeOptions(options) - newIter := obiiter.MakeIBioSequence(opt.BufferSize()) + newIter := obiiter.MakeIBioSequence() newIter.Add(1) go func() { diff --git a/pkg/obiformats/embl_read.go b/pkg/obiformats/embl_read.go index b72ff1b..13fc176 100644 --- a/pkg/obiformats/embl_read.go +++ b/pkg/obiformats/embl_read.go @@ -244,9 +244,9 @@ func _ReadFlatFileChunk(reader io.Reader, readers chan _FileChunk) { // ?//? func ReadEMBL(reader io.Reader, options ...WithOption) obiiter.IBioSequence { opt := MakeOptions(options) - entry_channel := make(chan _FileChunk, opt.BufferSize()) + entry_channel := make(chan _FileChunk) - newIter := obiiter.MakeIBioSequence(opt.BufferSize()) + newIter := obiiter.MakeIBioSequence() nworkers := opt.ParallelWorkers() newIter.Add(nworkers) diff --git a/pkg/obiformats/fastseq_header.go b/pkg/obiformats/fastseq_header.go index fca2896..29da3c3 100644 --- a/pkg/obiformats/fastseq_header.go +++ b/pkg/obiformats/fastseq_header.go @@ -19,6 +19,5 @@ func IParseFastSeqHeaderBatch(iterator obiiter.IBioSequence, options ...WithOption) obiiter.IBioSequence { opt := MakeOptions(options) return iterator.MakeIWorker(obiseq.AnnotatorToSeqWorker(opt.ParseFastSeqHeader()), - opt.ParallelWorkers(), - opt.BufferSize()) + opt.ParallelWorkers()) } diff --git a/pkg/obiformats/fastseq_read.go b/pkg/obiformats/fastseq_read.go index 33decc7..79873b8 100644 --- a/pkg/obiformats/fastseq_read.go +++ b/pkg/obiformats/fastseq_read.go @@ -105,7 +105,7 @@ func ReadFastSeqFromFile(filename string, options ...WithOption) (obiiter.IBioSe size = -1 } - newIter := obiiter.MakeIBioSequence(opt.BufferSize()) + newIter := obiiter.MakeIBioSequence() newIter.Add(1) go func() { @@ -127,7 +127,7 @@ func ReadFastSeqFromFile(filename string, options ...WithOption) (obiiter.IBioSe func ReadFastSeqFromStdin(options ...WithOption) obiiter.IBioSequence { opt := MakeOptions(options) - newIter := obiiter.MakeIBioSequence(opt.BufferSize()) + newIter := obiiter.MakeIBioSequence() newIter.Add(1) diff --git a/pkg/obiformats/fastseq_write_fasta.go b/pkg/obiformats/fastseq_write_fasta.go index 0071cd9..0c36d3d 100644 --- a/pkg/obiformats/fastseq_write_fasta.go +++ b/pkg/obiformats/fastseq_write_fasta.go @@ -71,8 +71,7 @@ func WriteFasta(iterator obiiter.IBioSequence, file, _ = goutils.CompressStream(file, opt.CompressedFile(), opt.CloseFile()) - buffsize := iterator.BufferSize() - newIter := obiiter.MakeIBioSequence(buffsize) + newIter := obiiter.MakeIBioSequence() nwriters := opt.ParallelWorkers() diff --git a/pkg/obiformats/fastseq_write_fastq.go b/pkg/obiformats/fastseq_write_fastq.go index 5fb63fb..f8e3a39 100644 --- a/pkg/obiformats/fastseq_write_fastq.go +++ b/pkg/obiformats/fastseq_write_fastq.go @@ -60,8 +60,7 @@ func WriteFastq(iterator obiiter.IBioSequence, file, _ = goutils.CompressStream(file, opt.CompressedFile(), opt.CloseFile()) - buffsize := iterator.BufferSize() - newIter := obiiter.MakeIBioSequence(buffsize) + newIter := obiiter.MakeIBioSequence() nwriters := opt.ParallelWorkers() diff --git a/pkg/obiformats/genbank_read.go b/pkg/obiformats/genbank_read.go index 53cb271..9d98b40 100644 --- a/pkg/obiformats/genbank_read.go +++ b/pkg/obiformats/genbank_read.go @@ -113,9 +113,9 @@ func _ParseGenbankFile(input <-chan _FileChunk, out obiiter.IBioSequence) { func ReadGenbank(reader io.Reader, options ...WithOption) obiiter.IBioSequence { opt := MakeOptions(options) - entry_channel := make(chan _FileChunk, opt.BufferSize()) + entry_channel := make(chan _FileChunk) - newIter := obiiter.MakeIBioSequence(opt.BufferSize()) + newIter := obiiter.MakeIBioSequence() nworkers := opt.ParallelWorkers() newIter.Add(nworkers) diff --git a/pkg/obiformats/options.go b/pkg/obiformats/options.go index 10da43c..87780ac 100644 --- a/pkg/obiformats/options.go +++ b/pkg/obiformats/options.go @@ -15,10 +15,15 @@ type __options__ struct { closefile bool appendfile bool compressed bool - csv_ids bool - cvs_sequence bool + csv_id bool + csv_sequence bool + csv_quality bool csv_definition bool + csv_count bool + csv_taxon bool + csv_keys []string csv_separator string + csv_navalue string paired_filename string } @@ -40,11 +45,16 @@ func MakeOptions(setters []WithOption) Options { closefile: false, appendfile: false, compressed: false, - csv_ids: true, + csv_id: true, csv_definition: false, - cvs_sequence: true, + csv_count: false, + csv_taxon: false, + csv_sequence: true, + csv_quality: false, csv_separator: ",", - paired_filename: "", + csv_navalue: "NA", + csv_keys: make([]string, 0), + paired_filename: "", } opt := Options{&o} @@ -60,10 +70,6 @@ func (opt Options) QualityShift() int { return opt.pointer.quality_shift } -func (opt Options) BufferSize() int { - return opt.pointer.buffer_size -} - func (opt Options) BatchSize() int { return opt.pointer.batch_size } @@ -96,8 +102,40 @@ func (opt Options) CompressedFile() bool { return opt.pointer.compressed } -func (opt Options) CSVIds() bool { - return opt.pointer.csv_ids +func (opt Options) CSVId() bool { + return opt.pointer.csv_id +} + +func (opt Options) CSVDefinition() bool { + return opt.pointer.csv_definition +} + +func (opt Options) CSVCount() bool { + return opt.pointer.csv_count +} + +func (opt Options) CSVTaxon() bool { + return opt.pointer.csv_taxon +} + +func (opt Options) CSVSequence() bool { + return opt.pointer.csv_sequence +} + +func (opt Options) CSVQuality() bool { + return opt.pointer.csv_quality +} + +func (opt Options) CSVKeys() []string { + return opt.pointer.csv_keys +} + +func (opt Options) CSVSeparator() string { + return opt.pointer.csv_separator +} + +func (opt Options) CSVNAValue() string { + return opt.pointer.csv_navalue } func (opt Options) HaveToSavePaired() bool { @@ -108,14 +146,6 @@ func (opt Options) PairedFileName() string { return opt.pointer.paired_filename } -func OptionsBufferSize(size int) WithOption { - f := WithOption(func(opt Options) { - opt.pointer.buffer_size = size - }) - - return f -} - func OptionCloseFile() WithOption { f := WithOption(func(opt Options) { opt.pointer.closefile = true @@ -247,3 +277,82 @@ func WritePairedReadsTo(filename string) WithOption { return f } +func CSVId(include bool) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_id = include + }) + + return f +} + +func CSVSequence(include bool) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_sequence = include + }) + + return f +} + +func CSVQuality(include bool) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_quality = include + }) + + return f +} + +func CSVDefinition(include bool) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_definition = include + }) + + return f +} + +func CSVCount(include bool) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_count = include + }) + + return f +} + +func CSVTaxon(include bool) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_taxon = include + }) + + return f +} + +func CSVKey(key string) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_keys = append(opt.pointer.csv_keys, key) + }) + + return f +} + +func CSVKeys(keys []string) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_keys = append(opt.pointer.csv_keys, keys...) + }) + + return f +} + +func CSVSeparator(separator string) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_separator = separator + }) + + return f +} + +func CSVNAValue(navalue string) WithOption { + f := WithOption(func(opt Options) { + opt.pointer.csv_navalue = navalue + }) + + return f +} diff --git a/pkg/obiiter/batchiterator.go b/pkg/obiiter/batchiterator.go index f2d5263..8782b87 100644 --- a/pkg/obiiter/batchiterator.go +++ b/pkg/obiiter/batchiterator.go @@ -60,17 +60,11 @@ type IBioSequence struct { var NilIBioSequence = IBioSequence{pointer: nil} func MakeIBioSequence(sizes ...int) IBioSequence { - buffsize := int32(0) - - if len(sizes) > 0 { - buffsize = int32(sizes[0]) - } i := _IBioSequence{ - channel: make(chan BioSequenceBatch, buffsize), + channel: make(chan BioSequenceBatch), current: NilBioSequenceBatch, pushBack: abool.New(), - buffer_size: buffsize, batch_size: -1, sequence_format: "", finished: abool.New(), @@ -160,14 +154,6 @@ func (iterator IBioSequence) IsNil() bool { return iterator.pointer == nil } -func (iterator IBioSequence) BufferSize() int { - if iterator.pointer == nil { - log.Panic("call of IBioSequenceBatch.BufferSize method on NilIBioSequenceBatch") - } - - return int(atomic.LoadInt32(&iterator.pointer.buffer_size)) -} - func (iterator IBioSequence) BatchSize() int { if iterator.pointer == nil { log.Panic("call of IBioSequenceBatch.BatchSize method on NilIBioSequenceBatch") @@ -279,13 +265,8 @@ func (iterator IBioSequence) Finished() bool { // Sorting the batches of sequences. func (iterator IBioSequence) SortBatches(sizes ...int) IBioSequence { - buffsize := iterator.BufferSize() - if len(sizes) > 0 { - buffsize = sizes[0] - } - - newIter := MakeIBioSequence(buffsize) + newIter := MakeIBioSequence() newIter.Add(1) @@ -338,8 +319,7 @@ func (iterator IBioSequence) Concat(iterators ...IBioSequence) IBioSequence { allPaired = allPaired && i.IsPaired() } - buffsize := iterator.BufferSize() - newIter := MakeIBioSequence(buffsize) + newIter := MakeIBioSequence() newIter.Add(1) @@ -396,8 +376,7 @@ func (iterator IBioSequence) Pool(iterators ...IBioSequence) IBioSequence { } nextCounter := goutils.AtomicCounter() - buffsize := iterator.BufferSize() - newIter := MakeIBioSequence(buffsize) + newIter := MakeIBioSequence() newIter.Add(niterator) @@ -431,13 +410,8 @@ func (iterator IBioSequence) Pool(iterators ...IBioSequence) IBioSequence { // indicated in parameter. Rebatching implies to sort the // source IBioSequenceBatch. func (iterator IBioSequence) Rebatch(size int, sizes ...int) IBioSequence { - buffsize := iterator.BufferSize() - if len(sizes) > 0 { - buffsize = sizes[0] - } - - newIter := MakeIBioSequence(buffsize) + newIter := MakeIBioSequence() newIter.Add(1) @@ -532,14 +506,9 @@ func (iterator IBioSequence) Count(recycle bool) (int, int, int) { // iterator following the predicate value. func (iterator IBioSequence) DivideOn(predicate obiseq.SequencePredicate, size int, sizes ...int) (IBioSequence, IBioSequence) { - buffsize := iterator.BufferSize() - if len(sizes) > 0 { - buffsize = sizes[0] - } - - trueIter := MakeIBioSequence(buffsize) - falseIter := MakeIBioSequence(buffsize) + trueIter := MakeIBioSequence() + falseIter := MakeIBioSequence() trueIter.Add(1) falseIter.Add(1) @@ -604,18 +573,13 @@ func (iterator IBioSequence) DivideOn(predicate obiseq.SequencePredicate, // A function that takes a predicate and a batch of sequences and returns a filtered batch of sequences. func (iterator IBioSequence) FilterOn(predicate obiseq.SequencePredicate, size int, sizes ...int) IBioSequence { - buffsize := iterator.BufferSize() nworkers := 4 if len(sizes) > 0 { nworkers = sizes[0] } - if len(sizes) > 1 { - buffsize = sizes[1] - } - - trueIter := MakeIBioSequence(buffsize) + trueIter := MakeIBioSequence() trueIter.Add(nworkers) @@ -661,18 +625,13 @@ func (iterator IBioSequence) FilterOn(predicate obiseq.SequencePredicate, func (iterator IBioSequence) FilterAnd(predicate obiseq.SequencePredicate, size int, sizes ...int) IBioSequence { - buffsize := iterator.BufferSize() nworkers := 4 if len(sizes) > 0 { nworkers = sizes[0] } - if len(sizes) > 1 { - buffsize = sizes[1] - } - - trueIter := MakeIBioSequence(buffsize) + trueIter := MakeIBioSequence() trueIter.Add(nworkers) @@ -740,13 +699,7 @@ func (iterator IBioSequence) Load() obiseq.BioSequenceSlice { func IBatchOver(data obiseq.BioSequenceSlice, size int, sizes ...int) IBioSequence { - buffsize := 0 - - if len(sizes) > 0 { - buffsize = sizes[0] - } - - newIter := MakeIBioSequence(buffsize) + newIter := MakeIBioSequence() newIter.Add(1) diff --git a/pkg/obiiter/distribute.go b/pkg/obiiter/distribute.go index c1b453f..45e755a 100644 --- a/pkg/obiiter/distribute.go +++ b/pkg/obiiter/distribute.go @@ -36,7 +36,6 @@ func (dist *IDistribute) Classifier() *obiseq.BioSequenceClassifier { func (iterator IBioSequence) Distribute(class *obiseq.BioSequenceClassifier, sizes ...int) IDistribute { batchsize := 5000 - buffsize := 2 outputs := make(map[int]IBioSequence, 100) slices := make(map[int]*obiseq.BioSequenceSlice, 100) @@ -47,9 +46,7 @@ func (iterator IBioSequence) Distribute(class *obiseq.BioSequenceClassifier, siz batchsize = sizes[0] } - if len(sizes) > 1 { - buffsize = sizes[1] - } + jobDone := sync.WaitGroup{} lock := sync.Mutex{} @@ -80,7 +77,7 @@ func (iterator IBioSequence) Distribute(class *obiseq.BioSequenceClassifier, siz orders[key] = 0 lock.Lock() - outputs[key] = MakeIBioSequence(buffsize) + outputs[key] = MakeIBioSequence() lock.Unlock() news <- key diff --git a/pkg/obiiter/merge.go b/pkg/obiiter/merge.go index f6bed9b..8a24d7a 100644 --- a/pkg/obiiter/merge.go +++ b/pkg/obiiter/merge.go @@ -4,16 +4,12 @@ import "git.metabarcoding.org/lecasofts/go/obitools/pkg/obiseq" func (iterator IBioSequence) IMergeSequenceBatch(na string, statsOn []string, sizes ...int) IBioSequence { batchsize := 100 - buffsize := iterator.BufferSize() if len(sizes) > 0 { batchsize = sizes[0] } - if len(sizes) > 1 { - buffsize = sizes[1] - } - newIter := MakeIBioSequence(buffsize) + newIter := MakeIBioSequence() newIter.Add(1) diff --git a/pkg/obiiter/workers.go b/pkg/obiiter/workers.go index 1e2c503..8003851 100644 --- a/pkg/obiiter/workers.go +++ b/pkg/obiiter/workers.go @@ -6,7 +6,6 @@ import ( "git.metabarcoding.org/lecasofts/go/obitools/pkg/obiseq" ) - // That method allows for applying a SeqWorker function on every sequences. // // Sequences are provided by the iterator and modified sequences are pushed @@ -17,17 +16,12 @@ import ( // - The second the size of the chanel buffer. By default set to the same value than the input buffer. func (iterator IBioSequence) MakeIWorker(worker obiseq.SeqWorker, sizes ...int) IBioSequence { nworkers := 4 - buffsize := iterator.BufferSize() if len(sizes) > 0 { nworkers = sizes[0] } - if len(sizes) > 1 { - buffsize = sizes[1] - } - - newIter := MakeIBioSequence(buffsize) + newIter := MakeIBioSequence() newIter.Add(nworkers) @@ -64,17 +58,12 @@ func (iterator IBioSequence) MakeIWorker(worker obiseq.SeqWorker, sizes ...int) func (iterator IBioSequence) MakeIConditionalWorker(predicate obiseq.SequencePredicate, worker obiseq.SeqWorker, sizes ...int) IBioSequence { nworkers := 4 - buffsize := iterator.BufferSize() if len(sizes) > 0 { nworkers = sizes[0] } - if len(sizes) > 1 { - buffsize = sizes[1] - } - - newIter := MakeIBioSequence(buffsize) + newIter := MakeIBioSequence() newIter.Add(nworkers) @@ -112,17 +101,12 @@ func (iterator IBioSequence) MakeIConditionalWorker(predicate obiseq.SequencePre func (iterator IBioSequence) MakeISliceWorker(worker obiseq.SeqSliceWorker, sizes ...int) IBioSequence { nworkers := 4 - buffsize := iterator.BufferSize() if len(sizes) > 0 { nworkers = sizes[0] } - if len(sizes) > 1 { - buffsize = sizes[1] - } - - newIter := MakeIBioSequence(buffsize) + newIter := MakeIBioSequence() newIter.Add(nworkers) @@ -140,7 +124,7 @@ func (iterator IBioSequence) MakeISliceWorker(worker obiseq.SeqSliceWorker, size newIter.Done() } - log.Printf("Start of the batch slice workers on %d workers (buffer : %d)\n", nworkers, buffsize) + log.Printf("Start of the batch slice workers on %d workers\n", nworkers) for i := 0; i < nworkers-1; i++ { go f(iterator.Split()) } @@ -168,4 +152,3 @@ func SliceWorkerPipe(worker obiseq.SeqSliceWorker, sizes ...int) Pipeable { return f } - diff --git a/pkg/obingslibrary/worker.go b/pkg/obingslibrary/worker.go index fb57f0f..df9ad26 100644 --- a/pkg/obingslibrary/worker.go +++ b/pkg/obingslibrary/worker.go @@ -11,7 +11,6 @@ type _Options struct { withProgressBar bool parallelWorkers int batchSize int - bufferSize int } // Options stores a set of option usable by the @@ -56,16 +55,6 @@ func OptionAllowedMismatches(count int) WithOption { return f } -// OptionBufferSize sets the requested channel -// buffer size. -func OptionBufferSize(size int) WithOption { - f := WithOption(func(opt Options) { - opt.pointer.bufferSize = size - }) - - return f -} - // OptionParallelWorkers sets how many search // jobs will be run in parallel. func OptionParallelWorkers(nworkers int) WithOption { @@ -102,12 +91,6 @@ func (options Options) WithProgressBar() bool { return options.pointer.withProgressBar } -// BufferSize returns the size of the channel -// buffer specified by the options -func (options Options) BufferSize() int { - return options.pointer.bufferSize -} - // BatchSize returns the size of the // sequence batch used by the PCR algorithm func (options Options) BatchSize() int { @@ -130,7 +113,6 @@ func MakeOptions(setters []WithOption) Options { withProgressBar: false, parallelWorkers: 4, batchSize: 1000, - bufferSize: 100, } opt := Options{&o} diff --git a/pkg/obioptions/options.go b/pkg/obioptions/options.go index 1366aae..8212bb7 100644 --- a/pkg/obioptions/options.go +++ b/pkg/obioptions/options.go @@ -11,12 +11,11 @@ import ( ) var _Debug = false -var _ParallelWorkers = runtime.NumCPU() * 2 - 1 +var _ParallelWorkers = runtime.NumCPU()*2 - 1 var _MaxAllowedCPU = runtime.NumCPU() -var _BufferSize = 1 var _BatchSize = 5000 -type ArgumentParser func([]string) (*getoptions.GetOpt, []string, error) +type ArgumentParser func([]string) (*getoptions.GetOpt, []string) func GenerateOptionParser(optionset ...func(*getoptions.GetOpt)) ArgumentParser { @@ -38,16 +37,20 @@ func GenerateOptionParser(optionset ...func(*getoptions.GetOpt)) ArgumentParser o(options) } - return func(args []string) (*getoptions.GetOpt, []string, error) { + return func(args []string) (*getoptions.GetOpt, []string) { remaining, err := options.Parse(args[1:]) + if err != nil { + log.Fatalf("Error on the commande line : %v",err) + } + // Setup the maximum number of CPU usable by the program runtime.GOMAXPROCS(_MaxAllowedCPU) if options.Called("max-cpu") { log.Printf("CPU number limited to %d", _MaxAllowedCPU) - if ! options.Called("workers") { - _ParallelWorkers=_MaxAllowedCPU * 2 - 1 + if !options.Called("workers") { + _ParallelWorkers = _MaxAllowedCPU*2 - 1 log.Printf("Number of workers set %d", _ParallelWorkers) } } @@ -67,7 +70,7 @@ func GenerateOptionParser(optionset ...func(*getoptions.GetOpt)) ArgumentParser log.Debugln("Switch to debug level logging") } - return options, remaining, err + return options, remaining } } @@ -88,11 +91,6 @@ func CLIMaxCPU() int { return _MaxAllowedCPU } -// CLIBufferSize returns the expeted channel buffer size for obitools -func CLIBufferSize() int { - return _BufferSize -} - // CLIBatchSize returns the expeted size of the sequence batches func CLIBatchSize() int { return _BatchSize diff --git a/pkg/obiseq/attributes.go b/pkg/obiseq/attributes.go index cbdc82b..f78ea6a 100644 --- a/pkg/obiseq/attributes.go +++ b/pkg/obiseq/attributes.go @@ -8,6 +8,15 @@ import ( log "github.com/sirupsen/logrus" ) +func (s *BioSequence) HasAttribute(key string) bool { + ok := s.annotations != nil + + if ok { + _, ok = s.annotations[key] + } + + return ok +} // A method that returns the value of the key in the annotation map. func (s *BioSequence) GetAttribute(key string) (interface{}, bool) { var val interface{} diff --git a/pkg/obiseq/biosequence.go b/pkg/obiseq/biosequence.go index a1d4b10..f399cd1 100644 --- a/pkg/obiseq/biosequence.go +++ b/pkg/obiseq/biosequence.go @@ -278,3 +278,28 @@ func (s *BioSequence) Clear() { s.sequence = s.sequence[0:0] } +func (s *BioSequence) Composition() map[byte]int { + + a := 0 + c := 0 + g := 0 + t := 0 + other := 0 + for _, char := range s.sequence { + switch char { + case 'a': + a++ + case 'c': + c++ + case 'g': + g++ + case 't': + t++ + default: + other++ + + } + } + + return map[byte]int{'a': a, 'c': c, 'g': g, 't': t, 'o': other} +} diff --git a/pkg/obiseq/class.go b/pkg/obiseq/class.go index 3608d2d..ba711c1 100644 --- a/pkg/obiseq/class.go +++ b/pkg/obiseq/class.go @@ -316,3 +316,4 @@ func RotateClassifier(size int) *BioSequenceClassifier { c := BioSequenceClassifier{code, value, reset, clone,"RotateClassifier"} return &c } + diff --git a/pkg/obiseq/eval.go b/pkg/obiseq/eval.go index 8e007f6..a919142 100644 --- a/pkg/obiseq/eval.go +++ b/pkg/obiseq/eval.go @@ -4,22 +4,21 @@ import ( "context" "fmt" - "git.metabarcoding.org/lecasofts/go/obitools/pkg/obieval" log "github.com/sirupsen/logrus" ) -func Expression(expression string) func(*BioSequence) (interface{},error) { +func Expression(expression string) func(*BioSequence) (interface{}, error) { - exp, err := obieval.OBILang.NewEvaluable(expression) + exp, err := OBILang.NewEvaluable(expression) if err != nil { log.Fatalf("Error in the expression : %s", expression) } - f := func(sequence *BioSequence) (interface{},error) { + f := func(sequence *BioSequence) (interface{}, error) { return exp(context.Background(), map[string]interface{}{ - "annotations": sequence.Annotations(), - "sequence": sequence, + "annotations": sequence.Annotations(), + "sequence": sequence, }, ) } @@ -30,14 +29,14 @@ func Expression(expression string) func(*BioSequence) (interface{},error) { func EditIdWorker(expression string) SeqWorker { e := Expression(expression) f := func(sequence *BioSequence) *BioSequence { - v,err := e(sequence) + v, err := e(sequence) if err != nil { log.Fatalf("Expression '%s' cannot be evaluated on sequence %s", expression, sequence.Id()) } - sequence.SetId(fmt.Sprintf("%v",v)) + sequence.SetId(fmt.Sprintf("%v", v)) return sequence } @@ -47,16 +46,16 @@ func EditIdWorker(expression string) SeqWorker { func EditAttributeWorker(key string, expression string) SeqWorker { e := Expression(expression) f := func(sequence *BioSequence) *BioSequence { - v,err := e(sequence) + v, err := e(sequence) if err != nil { log.Fatalf("Expression '%s' cannot be evaluated on sequence %s", expression, sequence.Id()) } - sequence.SetAttribute(key,v) + sequence.SetAttribute(key, v) return sequence } return f -} \ No newline at end of file +} diff --git a/pkg/obieval/language.go b/pkg/obiseq/language.go similarity index 84% rename from pkg/obieval/language.go rename to pkg/obiseq/language.go index 74b4784..473a52a 100644 --- a/pkg/obieval/language.go +++ b/pkg/obiseq/language.go @@ -1,4 +1,4 @@ -package obieval +package obiseq import ( "fmt" @@ -174,8 +174,19 @@ var OBILang = gval.NewLanguage( log.Fatalf("%v cannot be converted to a boolan value", args[0]) } return val, nil + }), + gval.Function("ifelse", func(args ...interface{}) (interface{}, error) { + if args[0].(bool) { + return args[1], nil + } else { + return args[2], nil + } + }), + gval.Function("gcskew", func(args ...interface{}) (interface{}, error) { + composition := (args[0].(*BioSequence)).Composition() + return float64(composition['g']-composition['c']) / float64(composition['g']+composition['c']), nil + }), + gval.Function("composition", func(args ...interface{}) (interface{}, error) { + return (args[0].(*BioSequence)).Composition(), nil })) -func Expression(expression string) (gval.Evaluable, error) { - return OBILang.NewEvaluable(expression) -} diff --git a/pkg/obiseq/predicate.go b/pkg/obiseq/predicate.go index 31ddf48..0f00080 100644 --- a/pkg/obiseq/predicate.go +++ b/pkg/obiseq/predicate.go @@ -5,7 +5,6 @@ import ( "fmt" "regexp" - "git.metabarcoding.org/lecasofts/go/obitools/pkg/obieval" log "github.com/sirupsen/logrus" ) @@ -256,7 +255,7 @@ func IsIdIn(ids ...string) SequencePredicate { func ExpressionPredicat(expression string) SequencePredicate { - exp, err := obieval.OBILang.NewEvaluable(expression) + exp, err := OBILang.NewEvaluable(expression) if err != nil { log.Fatalf("Error in the expression : %s", expression) } diff --git a/pkg/obitools/obicleandb/obicleandb.go b/pkg/obitools/obicleandb/obicleandb.go new file mode 100644 index 0000000..64e3752 --- /dev/null +++ b/pkg/obitools/obicleandb/obicleandb.go @@ -0,0 +1,63 @@ +package obicleandb + +import ( + log "github.com/sirupsen/logrus" + + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obichunk" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obiiter" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obioptions" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obiseq" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obitools/obigrep" +) + +func ICleanDB(itertator obiiter.IBioSequence) obiiter.IBioSequence { + var rankPredicate obiseq.SequencePredicate + + options := make([]obichunk.WithOption, 0, 30) + + // Make sequence dereplication with a constraint on the taxid. + // To be merged, both sequences must have the same taxid. + + options = append(options, + obichunk.OptionBatchCount(100), + obichunk.OptionSortOnMemory(), + obichunk.OptionSubCategory("taxid"), + obichunk.OptionsParallelWorkers( + obioptions.CLIParallelWorkers()), + obichunk.OptionsBatchSize( + obioptions.CLIBatchSize()), + obichunk.OptionNAValue("NA"), + ) + + unique, err := obichunk.IUniqueSequence(itertator, options...) + + if err != nil { + log.Fatal(err) + } + + taxonomy := obigrep.CLILoadSelectedTaxonomy() + + if len(obigrep.CLIRequiredRanks()) > 0 { + rankPredicate = obigrep.CLIHasRankDefinedPredicate() + } else { + rankPredicate = taxonomy.HasRequiredRank("species").And(taxonomy.HasRequiredRank("genus")).And(taxonomy.HasRequiredRank("family")) + } + + goodTaxa := taxonomy.IsAValidTaxon(CLIUpdateTaxids()).And(rankPredicate) + + usable := unique.FilterOn(goodTaxa, + obioptions.CLIBatchSize(), + obioptions.CLIParallelWorkers()) + + annotated := usable.MakeIWorker(taxonomy.MakeSetSpeciesWorker(), + obioptions.CLIParallelWorkers(), + ).MakeIWorker(taxonomy.MakeSetGenusWorker(), + obioptions.CLIParallelWorkers(), + ).MakeIWorker(taxonomy.MakeSetFamilyWorker(), + obioptions.CLIParallelWorkers(), + ) + + // annotated.MakeIConditionalWorker(obiseq.IsMoreAbundantOrEqualTo(3),1000) + + return annotated +} diff --git a/pkg/obitools/obiconvert/options.go b/pkg/obitools/obiconvert/options.go index 7a4792d..006a390 100644 --- a/pkg/obitools/obiconvert/options.go +++ b/pkg/obitools/obiconvert/options.go @@ -60,6 +60,21 @@ func InputOptionSet(options *getoptions.GetOpt) { } +func OutputModeOptionSet(options *getoptions.GetOpt) { + options.BoolVar(&__no_progress_bar__, "no-progressbar", false, + options.Description("Disable the progress bar printing")) + + options.BoolVar(&__compressed__, "compress", false, + options.Alias("Z"), + options.Description("Output is compressed")) + + options.StringVar(&__output_file_name__, "out", __output_file_name__, + options.Alias("o"), + options.ArgName("FILENAME"), + options.Description("Filename used for saving the output"), + ) +} + func OutputOptionSet(options *getoptions.GetOpt) { options.BoolVar(&__output_in_fasta__, "fasta-output", false, options.Description("Read data following the ecoPCR output format.")) @@ -73,19 +88,7 @@ func OutputOptionSet(options *getoptions.GetOpt) { options.Alias("O"), options.Description("output FASTA/FASTQ title line annotations follow OBI format.")) - options.BoolVar(&__no_progress_bar__, "no-progressbar", false, - options.Description("Disable the progress bar printing")) - - options.BoolVar(&__compressed__, "compress", false, - options.Alias("Z"), - options.Description("Output is compressed")) - - options.StringVar(&__output_file_name__, "out", __output_file_name__, - options.Alias("o"), - options.ArgName("FILENAME"), - options.Description("Filename used for saving the output"), - ) - + OutputModeOptionSet(options) } func PairedFilesOptionSet(options *getoptions.GetOpt) { @@ -197,4 +200,4 @@ func CLIHasPairedFile() bool { } func CLIPairedFileName() string { return __paired_file_name__ -} \ No newline at end of file +} diff --git a/pkg/obitools/obiconvert/sequence_reader.go b/pkg/obitools/obiconvert/sequence_reader.go index 1d20962..c17f3b5 100644 --- a/pkg/obitools/obiconvert/sequence_reader.go +++ b/pkg/obitools/obiconvert/sequence_reader.go @@ -48,6 +48,10 @@ func _ExpandListOfFiles(check_ext bool, filenames ...string) ([]string, error) { strings.HasSuffix(path, "fasta.gz") || strings.HasSuffix(path, "fastq") || strings.HasSuffix(path, "fastq.gz") || + strings.HasSuffix(path, "seq") || + strings.HasSuffix(path, "seq.gz") || + strings.HasSuffix(path, "gb") || + strings.HasSuffix(path, "gb.gz") || strings.HasSuffix(path, "dat") || strings.HasSuffix(path, "dat.gz") || strings.HasSuffix(path, "ecopcr") || @@ -82,13 +86,12 @@ func CLIReadBioSequences(filenames ...string) (obiiter.IBioSequence, error) { opts = append(opts, obiformats.OptionsFastSeqHeaderParser(obiformats.ParseGuessedFastSeqHeader)) } - nworkers := obioptions.CLIParallelWorkers() // / 4 + nworkers := obioptions.CLIParallelWorkers() if nworkers < 2 { nworkers = 2 } opts = append(opts, obiformats.OptionsParallelWorkers(nworkers)) - opts = append(opts, obiformats.OptionsBufferSize(obioptions.CLIBufferSize())) opts = append(opts, obiformats.OptionsBatchSize(obioptions.CLIBatchSize())) opts = append(opts, obiformats.OptionsQualityShift(CLIInputQualityShift())) diff --git a/pkg/obitools/obiconvert/sequence_writer.go b/pkg/obitools/obiconvert/sequence_writer.go index 01b1882..d5ef634 100644 --- a/pkg/obitools/obiconvert/sequence_writer.go +++ b/pkg/obitools/obiconvert/sequence_writer.go @@ -60,7 +60,6 @@ func CLIWriteBioSequences(iterator obiiter.IBioSequence, } opts = append(opts, obiformats.OptionsParallelWorkers(nworkers)) - opts = append(opts, obiformats.OptionsBufferSize(obioptions.CLIBufferSize())) opts = append(opts, obiformats.OptionsBatchSize(obioptions.CLIBatchSize())) opts = append(opts, obiformats.OptionsQualityShift(CLIOutputQualityShift())) diff --git a/pkg/obitools/obicsv/obicsv.go b/pkg/obitools/obicsv/obicsv.go new file mode 100644 index 0000000..0e66565 --- /dev/null +++ b/pkg/obitools/obicsv/obicsv.go @@ -0,0 +1,61 @@ +package obicsv + +import ( + "log" + + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obiformats" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obiiter" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obioptions" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obitools/obiconvert" +) + +func CLIWriteCSV(iterator obiiter.IBioSequence, + terminalAction bool, filenames ...string) (obiiter.IBioSequence, error) { + + if obiconvert.CLIProgressBar() { + iterator = iterator.Speed() + } + + var newIter obiiter.IBioSequence + + opts := make([]obiformats.WithOption, 0, 10) + + nworkers := obioptions.CLIParallelWorkers() / 4 + if nworkers < 2 { + nworkers = 2 + } + + opts = append(opts, obiformats.OptionsParallelWorkers(nworkers)) + opts = append(opts, obiformats.OptionsBatchSize(obioptions.CLIBatchSize())) + + opts = append(opts, obiformats.OptionsQualityShift(obiconvert.CLIOutputQualityShift())) + opts = append(opts, obiformats.OptionsCompressed(obiconvert.CLICompressed())) + + opts = append(opts, obiformats.CSVId(CLIPrintId()), + obiformats.CSVCount(CLIPrintCount()), + obiformats.CSVTaxon(CLIPrintTaxon()), + obiformats.CSVDefinition(CLIPrintDefinition()), + obiformats.CSVKeys(CLIToBeKeptAttributes()), + ) + + var err error + + if len(filenames) == 0 { + newIter, err = obiformats.WriteCSVToStdout(iterator, opts...) + } else { + newIter, err = obiformats.WriteCSVToFile(iterator, filenames[0], opts...) + } + + if err != nil { + log.Fatalf("Write file error: %v", err) + return obiiter.NilIBioSequence, err + } + + if terminalAction { + newIter.Recycle() + return obiiter.NilIBioSequence, nil + } + + return newIter, nil + +} diff --git a/pkg/obitools/obicsv/options.go b/pkg/obitools/obicsv/options.go new file mode 100644 index 0000000..c4119d6 --- /dev/null +++ b/pkg/obitools/obicsv/options.go @@ -0,0 +1,126 @@ +package obicsv + +import ( + "git.metabarcoding.org/lecasofts/go/obitools/pkg/goutils" + "git.metabarcoding.org/lecasofts/go/obitools/pkg/obitools/obiconvert" + "github.com/DavidGamba/go-getoptions" +) + +var _outputIds = true +var _outputCount = false +var _outputTaxon = false +var _outputSequence = true +var _outputQuality = true +var _outputDefinition = false +var _obipairing = false +var _autoColumns = false +var _keepOnly = make([]string, 0) +var _naValue = "NA" + +var _softAttributes = map[string][]string{ + "obipairing": {"mode", "seq_a_single", "seq_b_single", + "ali_dir", "score", "score_norm", + "seq_ab_match", "pairing_mismatches", + }, +} + +func CSVOptionSet(options *getoptions.GetOpt) { + options.BoolVar(&_outputIds, "ids", _outputIds, + options.Alias("i"), + options.Description("Prints sequence ids in the ouput.")) + + options.BoolVar(&_outputSequence, "sequence", _outputSequence, + options.Alias("s"), + options.Description("Prints sequence itself in the output.")) + + options.BoolVar(&_outputQuality, "quality", _outputQuality, + options.Alias("q"), + options.Description("Prints sequence quality in the output.")) + + options.BoolVar(&_outputDefinition, "definition", _outputDefinition, + options.Alias("d"), + options.Description("Prints sequence definition in the output.")) + + options.BoolVar(&_autoColumns, "auto", _autoColumns, + options.Description("Based on the first sequences, propose a list of attibutes to print")) + + options.BoolVar(&_outputCount, "count", _outputCount, + options.Description("Prints the count attribute in the output")) + + options.BoolVar(&_outputTaxon, "taxon", _outputTaxon, + options.Description("Prints the NCBI taxid and its related scientific name")) + + options.BoolVar(&_obipairing, "obipairing", _obipairing, + options.Description("Prints the attributes added by obipairing")) + + options.StringSliceVar(&_keepOnly, "keep", 1, 1, + options.Alias("k"), + options.ArgName("KEY"), + options.Description("Keeps only attribute with key . Several -k options can be combined.")) + + options.StringVar(&_naValue, "na-value", _naValue, + options.ArgName("NAVALUE"), + options.Description("A string representing non available values in the CSV file.")) +} + +func OptionSet(options *getoptions.GetOpt) { + obiconvert.OutputModeOptionSet(options) + CSVOptionSet(options) +} + +func CLIPrintId() bool { + return _outputIds +} + +func CLIPrintSequence() bool { + return _outputSequence +} + +func CLIPrintCount() bool { + return _outputCount +} +func CLIPrintTaxon() bool { + return _outputTaxon +} +func CLIPrintQuality() bool { + return _outputQuality +} + +func CLIPrintDefinition() bool { + return _outputDefinition +} + +func CLIAutoColumns() bool { + return _autoColumns +} + +func CLIHasToBeKeptAttributes() bool { + return len(_keepOnly) > 0 +} + +func CLIToBeKeptAttributes() []string { + if _obipairing { + _keepOnly = append(_keepOnly, _softAttributes["obipairing"]...) + } + + if i := goutils.LookFor(_keepOnly, "count"); i >= 0 { + _keepOnly = goutils.RemoveIndex(_keepOnly, i) + _outputCount = true + } + + if i := goutils.LookFor(_keepOnly, "taxid"); i >= 0 { + _keepOnly = goutils.RemoveIndex(_keepOnly, i) + _outputTaxon = true + } + + if i := goutils.LookFor(_keepOnly, "scientific_name"); i >= 0 { + _keepOnly = goutils.RemoveIndex(_keepOnly, i) + _outputTaxon = true + } + + return _keepOnly +} + +func CLINAValue() string { + return _naValue +} diff --git a/pkg/obitools/obidistribute/distribute.go b/pkg/obitools/obidistribute/distribute.go index 88a77b9..beb5a9c 100644 --- a/pkg/obitools/obidistribute/distribute.go +++ b/pkg/obitools/obidistribute/distribute.go @@ -31,7 +31,6 @@ func DistributeSequence(sequences obiiter.IBioSequence) { } opts = append(opts, obiformats.OptionsParallelWorkers(nworkers), - obiformats.OptionsBufferSize(obioptions.CLIBufferSize()), obiformats.OptionsBatchSize(obioptions.CLIBatchSize()), obiformats.OptionsQualityShift(obiconvert.CLIOutputQualityShift()), obiformats.OptionsAppendFile(CLIAppendSequences()), diff --git a/pkg/obitools/obigrep/grep.go b/pkg/obitools/obigrep/grep.go index d3e474c..a358061 100644 --- a/pkg/obitools/obigrep/grep.go +++ b/pkg/obitools/obigrep/grep.go @@ -39,7 +39,6 @@ func CLIFilterSequence(iterator obiiter.IBioSequence) obiiter.IBioSequence { newIter = iterator.FilterOn(predicate, obioptions.CLIBatchSize(), obioptions.CLIParallelWorkers(), - obioptions.CLIBufferSize(), ) } } else { diff --git a/pkg/obitools/obimultiplex/demultiplex.go b/pkg/obitools/obimultiplex/demultiplex.go index 5ba527c..69b8a9c 100644 --- a/pkg/obitools/obimultiplex/demultiplex.go +++ b/pkg/obitools/obimultiplex/demultiplex.go @@ -20,7 +20,6 @@ func IExtractBarcode(iterator obiiter.IBioSequence) (obiiter.IBioSequence, error obingslibrary.OptionDiscardErrors(!CLIConservedErrors()), obingslibrary.OptionParallelWorkers(obioptions.CLIParallelWorkers()), obingslibrary.OptionBatchSize(obioptions.CLIBatchSize()), - obingslibrary.OptionBufferSize(obioptions.CLIBufferSize()), ) ngsfilter, err := CLINGSFIlter() diff --git a/pkg/obitools/obipairing/pairing.go b/pkg/obitools/obipairing/pairing.go index 36cf231..f1ecf93 100644 --- a/pkg/obitools/obipairing/pairing.go +++ b/pkg/obitools/obipairing/pairing.go @@ -211,17 +211,13 @@ func IAssemblePESequencesBatch(iterator obiiter.IBioSequence, } nworkers := obioptions.CLIMaxCPU() * 3 / 2 - buffsize := iterator.BufferSize() if len(sizes) > 0 { nworkers = sizes[0] } - if len(sizes) > 1 { - buffsize = sizes[1] - } - newIter := obiiter.MakeIBioSequence(buffsize) + newIter := obiiter.MakeIBioSequence() newIter.Add(nworkers) diff --git a/pkg/obitools/obiuniq/unique.go b/pkg/obitools/obiuniq/unique.go index fc1ac26..14d8e26 100644 --- a/pkg/obitools/obiuniq/unique.go +++ b/pkg/obitools/obiuniq/unique.go @@ -51,8 +51,6 @@ func Unique(sequences obiiter.IBioSequence) obiiter.IBioSequence { options = append(options, obichunk.OptionsParallelWorkers( obioptions.CLIParallelWorkers()), - obichunk.OptionsBufferSize( - obioptions.CLIBufferSize()), obichunk.OptionsBatchSize( obioptions.CLIBatchSize()), obichunk.OptionNAValue(CLINAValue()),