source: branches/properties/GDE/SINA/builddir/src/rw_fasta.cpp

Last change on this file was 19170, checked in by westram, 3 years ago
  • sina source
    • unpack + remove tarball
    • no longer ignore sina builddir.
File size: 16.0 KB
Line 
1/*
2Copyright (c) 2006-2018 Elmar Pruesse <elmar.pruesse@ucdenver.edu>
3
4This file is part of SINA.
5SINA is free software: you can redistribute it and/or modify it under
6the terms of the GNU General Public License as published by the Free
7Software Foundation, either version 3 of the License, or (at your
8option) any later version.
9
10SINA is distributed in the hope that it will be useful, but WITHOUT ANY
11WARRANTY; without even the implied warranty of MERCHANTABILITY or
12FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13for more details.
14
15You should have received a copy of the GNU General Public License
16along with SINA.  If not, see <http://www.gnu.org/licenses/>.
17
18Additional permission under GNU GPL version 3 section 7
19
20If you modify SINA, or any covered work, by linking or combining it
21with components of ARB (or a modified version of that software),
22containing parts covered by the terms of the
23ARB-public-library-license, the licensors of SINA grant you additional
24permission to convey the resulting work. Corresponding Source for a
25non-source form of such a combination shall include the source code
26for the parts of ARB used as well as that of the covered work.
27*/
28
29#include "rw_fasta.h"
30
31#include <fstream>
32#include <utility>
33#include <vector>
34#include <iostream>
35#include <unordered_set>
36#include <string>
37#include <sstream>
38
39#include <functional>
40
41#include <boost/algorithm/string.hpp>
42#include <boost/iostreams/filtering_stream.hpp>
43#include <boost/iostreams/filter/gzip.hpp>
44#include <boost/iostreams/filter/counter.hpp>
45#include <boost/iostreams/device/file_descriptor.hpp>
46
47#include "query_arb.h"
48#include "log.h"
49#include "progress.h"
50
51using std::stringstream;
52using std::vector;
53using std::string;
54using boost::algorithm::iequals;
55
56namespace bi = boost::iostreams;
57namespace po = boost::program_options;
58namespace fs = boost::filesystem;
59
60namespace sina {
61
62static const char* module_name = "FASTA I/O";
63static auto logger = Log::create_logger(module_name);
64
65// define extra datatype for metadata output format
66std::ostream& operator<<(std::ostream& out, const sina::FASTA_META_TYPE& m) {
67    switch(m) {
68    case FASTA_META_NONE:
69        out << "none";
70    break;
71    case FASTA_META_HEADER:
72        out << "header";
73    break;
74    case FASTA_META_COMMENT:
75        out << "comment";
76    break;
77    case FASTA_META_CSV:
78        out << "csv";
79    break;
80    default:
81        out << "[UNKNOWN!] (value=" << (int)m << ")";
82    }
83    return out;
84}
85void validate(boost::any& v,
86              const std::vector<std::string>& values,
87              sina::FASTA_META_TYPE* /*m*/, int /*unused*/) {
88    po::validators::check_first_occurrence(v);
89    const std::string &s = po::validators::get_single_string(values);
90    if (iequals(s, "none")) {
91        v = FASTA_META_NONE;
92    } else if (iequals(s, "header")) {
93        v = FASTA_META_HEADER;
94    } else if (iequals(s, "comment")) {
95        v = FASTA_META_COMMENT;
96    } else if (iequals(s, "csv")) {
97        v = FASTA_META_CSV;
98    } else {
99        throw po::invalid_option_value("must be one of 'none', 'header', 'comment' or 'cvs'");
100    }
101}
102
103// struct holding configuration options
104struct rw_fasta::options {
105    FASTA_META_TYPE fastameta;
106    int line_length;
107    float min_idty;
108    long fasta_block;
109    long fasta_idx;
110    bool out_dots;
111    bool out_dna;
112};
113struct rw_fasta::options *rw_fasta::opts;
114
115void
116rw_fasta::get_options_description(po::options_description& main,
117                                  po::options_description& adv) {
118    opts = new options;
119
120    main.add_options()
121        ("meta-fmt",
122         po::value<FASTA_META_TYPE>(&opts->fastameta)->default_value(FASTA_META_NONE,""),
123         "meta data in (*none*|header|comment|csv)")
124        ;
125
126    po::options_description od(module_name);
127    od.add_options()
128        // write
129        ("line-length",
130         po::value<int>(&opts->line_length)->default_value(0, ""),
131         "wrap output sequence (unlimited)")
132
133        ("min-idty",
134         po::value<float>(&opts->min_idty)->default_value(0.f, ""),
135         "only write sequences with align_idty_slv > X, implies calc-idty")
136        ("fasta-write-dna",
137         po::bool_switch(&opts->out_dna),
138         "Write DNA sequences (default: RNA)")
139        ("fasta-write-dots",
140         po::bool_switch(&opts->out_dots),
141         "Use dots instead of dashes to distinguish unknown sequence data from indels")
142
143        // read
144        ("fasta-idx",
145         po::value<long>(&opts->fasta_idx)->default_value(0, ""),
146         "process only sequences beginning in block <arg> (0 is first)")
147        ("fasta-block",
148         po::value<long>(&opts->fasta_block)->default_value(0, ""),
149         "block length in bytes")
150        ;
151    adv.add(od);
152}
153
154
155void
156rw_fasta::validate_vm(po::variables_map& /*vm*/,
157                      po::options_description& /*desc*/) {
158}
159
160
161////////// reader
162
163struct rw_fasta::reader::priv_data {
164    bi::file_descriptor_source file;
165    bi::filtering_istream in;
166    bi::counter *counter{0};
167    fs::path filename;
168    size_t file_size{0};
169    int lineno{0};
170    int seqno{0};
171
172    vector<string>& v_fields;
173    logger_progress* p{nullptr};
174
175    priv_data(fs::path filename_, vector<string>& fields)
176        : filename(std::move(filename_)),
177          v_fields(fields)
178    {}
179    ~priv_data() {
180        logger->info("read {} sequences from {} lines", seqno-1, lineno-1);
181    }
182};
183
184rw_fasta::reader::reader(const fs::path& infile, vector<string>& fields)
185    : data(new priv_data(infile, fields))
186{
187    if (infile == "-") {
188        data->file.open(STDIN_FILENO, bi::never_close_handle);
189    } else {
190        data->file.open(infile.c_str(), std::ios_base::binary);
191        if (fs::is_regular_file(infile)) {
192            data->file_size = fs::file_size(infile);
193        }
194    }
195    if (!data->file.is_open()) {
196        stringstream msg; 
197        msg << "Unable to open file \"" << infile << "\" for reading.";
198        throw std::runtime_error(msg.str());
199    }
200    if (infile.extension() == ".gz") {
201        data->in.push(bi::gzip_decompressor());
202    }
203    if (data->file_size > 0) {
204        data->in.push(bi::counter());
205        data->counter = data->in.component<bi::counter>(data->in.size()-1);
206    }
207    data->in.push(data->file);
208
209    // if fasta blocking enabled, seek to selected block
210    if (opts->fasta_block > 0) {
211        if (infile == "-") {
212            throw std::logic_error("Cannot use --fasta-idx when input is piped");
213        }
214
215        data->in.seekg(opts->fasta_block * opts->fasta_idx);
216    }
217}
218
219rw_fasta::reader::reader(const reader& r) = default;
220rw_fasta::reader& rw_fasta::reader::operator=(const reader& r) = default;
221rw_fasta::reader::~reader() = default;
222
223void
224rw_fasta::reader::set_progress(logger_progress& p) {
225    data->p = &p;
226}
227
228bool
229rw_fasta::reader::operator()(tray& t) {
230    t.seqno = ++data->seqno;
231    t.input_sequence = new cseq();
232    cseq &c = *t.input_sequence;
233    if (data->in.fail()) {
234        return false;
235    }
236   
237    // if fasta blocking enabled, check if we've passed block
238    // boundary in last sequence
239    if (opts->fasta_block > 0
240        && data->in.tellg() > opts->fasta_block * (opts->fasta_idx + 1)) {
241        return false;
242    }
243
244    string line;
245    // skip lines not beginning with '>'
246    while (data->in.peek() != '>' && getline(data->in, line).good()) {
247        data->lineno++;
248    }
249
250    // parse title
251    data->lineno++;
252    if (getline(data->in, line).good()) {
253        if (line[line.size()-1] == '\r') {  // "\r" at end
254            line.resize(line.size()-1);
255        }
256       
257        // set name to text between first '>' and first ' '
258        unsigned int blank = line.find_first_of(" \t");
259        if (blank == 0) {
260            blank = line.size();
261        }
262        c.setName(line.substr(1, blank-1));
263        if (blank < line.size()) {
264            c.set_attr<string>(query_arb::fn_fullname, line.substr(blank+1));
265        }
266    } else { // didn't get a title
267        return false;
268    }
269
270    // handle comments
271    while (data->in.peek() == ';' && getline(data->in, line).good()) {
272        data->lineno++;
273
274        // if the comment contains an attribute: add it.
275        // Otherwise ignore the comment
276
277        size_t equalsign = line.find_first_of('=');
278        if (equalsign != string::npos) {
279            string key = line.substr(1, equalsign-1);
280            boost::trim(key);
281            string value = line.substr(equalsign+1);
282            boost::trim(value);
283            c.set_attr(key,value);
284        }
285    }
286
287    try {
288        // all lines until eof or next /^>/ are data
289        while (data->in.peek() != '>' && data->in.good()) {
290            getline(data->in, line);
291            data->lineno++;
292            c.append(line);
293        }
294    } catch (base_iupac::bad_character_exception& e) {
295        logger->error("Skipping sequence {} (>{}) at {}:{} (contains character '{}')",
296                      data->seqno, c.getName(),
297                      data->filename, data->lineno,
298                      e.character);
299        while (data->in.peek() != '>' && getline(data->in, line).good()) {
300            data->lineno++;
301        }
302        delete t.input_sequence;
303        return (*this)(t); // FIXME: stack size?
304    }
305
306    if (data->p && data->counter) {
307        auto bytes_read = data->counter->characters();
308        if (bytes_read) {
309            data->p->set_total(data->seqno * data->file_size / bytes_read);
310        }
311    }
312
313    logger->debug("loaded sequence {}", t.input_sequence->getName());
314    return true;
315}
316
317
318/////////////// writer
319
320struct rw_fasta::writer::priv_data {
321    bi::file_descriptor_sink file;
322    bi::filtering_ostream out;
323    std::ofstream out_csv;
324    int count;
325    int excluded;
326    std::unordered_set<string> relatives_written;
327    unsigned long copy_relatives;
328    vector<string>& v_fields;
329    priv_data(unsigned int copy_relatives_,
330              vector<string>& fields)
331        : count(0),
332          excluded(0),
333          copy_relatives(copy_relatives_),
334          v_fields(fields)
335    {}
336    ~priv_data() {
337        logger->info("wrote {} sequences ({} excluded, {} relatives)",
338                     count, excluded, relatives_written.size());
339    }
340    void write(const cseq& c);
341};
342
343rw_fasta::writer::writer(const fs::path& outfile,
344                         unsigned int copy_relatives,
345                         vector<string>& fields)
346    : data(new priv_data(copy_relatives, fields))
347{
348    if (outfile == "-") {
349        data->file.open(STDOUT_FILENO, bi::never_close_handle);
350    } else {
351        data->file.open(outfile.c_str(), std::ios_base::binary);
352    }
353    if (!data->file.is_open()) {
354        stringstream msg; 
355        msg << "Unable to open file \"" << outfile << "\" for writing.";
356        throw std::runtime_error(msg.str());
357    }
358    if (outfile.extension() == ".gz") {
359        data->out.push(bi::gzip_compressor());
360    }
361    data->out.push(data->file);
362
363    if (opts->fastameta == FASTA_META_CSV) {
364        fs::path outcsv(outfile);
365        outcsv.replace_extension(".csv");
366        data->out_csv.open(outcsv.native());
367        if (data->out_csv.fail()) {
368            stringstream msg; 
369            msg << "Unable to open file \"" << outfile << ".csv\" for writing.";
370            throw std::runtime_error(msg.str());
371        }
372    }
373}
374
375rw_fasta::writer::writer(const writer& o) = default;
376rw_fasta::writer& rw_fasta::writer::operator=(const writer& o) = default;
377rw_fasta::writer::~writer() = default;
378
379string escape_string(const string& in) {
380    if (in.find_first_of("\",\r\n") == string::npos) {
381        return in;
382    }
383    stringstream tmp;
384    tmp << "\"";
385    size_t j = 0;
386    for (size_t i = in.find('"'); i != string::npos; 
387         j=i+1, i = in.find('"',i+1)) {
388        tmp << in.substr(j, i-j) << "\"\"";
389    }
390    tmp << in.substr(j) << "\"";
391    return tmp.str();
392}
393
394tray
395rw_fasta::writer::operator()(tray t) {
396    if (t.input_sequence == nullptr) {
397        throw std::runtime_error("Received broken tray in " __FILE__);
398    }
399    if (t.aligned_sequence == nullptr) {
400        logger->info("Not writing sequence {} (>{}): not aligned",
401                     t.seqno, t.input_sequence->getName());
402        ++data->excluded;
403        return t;
404    }
405    if (opts->min_idty > 0) {
406        auto idty = t.aligned_sequence->get_attr<float>(query_arb::fn_idty, 0);
407        if (opts->min_idty > idty) {
408            logger->info("Not writing sequence {} (>{}): below identity threshold ({}<={})",
409                         t.seqno, t.input_sequence->getName(),
410                         idty, opts->min_idty);
411            ++data->excluded;
412            return t;
413        }
414    }
415    cseq &c = *t.aligned_sequence;
416
417    data->write(c);
418
419    if (data->copy_relatives != 0u) {
420        auto *relatives = t.search_result != nullptr ? t.search_result : t.alignment_reference;
421        if (relatives != nullptr) {
422            int i = data->copy_relatives;
423            for (auto& item : *relatives) {
424                if (data->relatives_written.insert( item.sequence->getName() ).second) {
425                    data->write(*item.sequence);
426                }
427                if (--i == 0) {
428                    break;
429                }
430            }
431        }
432    }
433
434    return t;
435}
436
437void
438rw_fasta::writer::priv_data::write(const cseq& c) {
439    const auto& attrs = c.get_attrs();
440
441    out << ">" << c.getName();
442    string fname = c.get_attr<string>(query_arb::fn_fullname, "");
443    if (!fname.empty()) {
444        out << " " << fname;
445    }
446
447    switch (opts->fastameta) {
448    case FASTA_META_NONE:
449        out << std::endl;
450        break;
451    case FASTA_META_HEADER:
452        for (auto& ap: attrs) {
453            if (ap.first == query_arb::fn_family) {
454                continue; // alignment family is too much
455            }
456            if (ap.first == query_arb::fn_fullname) {
457                continue; // already written as description in header
458            }
459            string val = boost::apply_visitor(lexical_cast_visitor<string>(),
460                                              ap.second);
461            if (!val.empty()) {
462                out << " [" << ap.first << "="
463                    << val
464                    << "]";
465            }
466        }
467        out << std::endl;
468        break;
469    case FASTA_META_COMMENT:
470        out << std::endl;
471
472        for (auto& ap: attrs) {
473            if (ap.first == query_arb::fn_family) {
474                continue; // alignment family is too much
475            }
476            if (ap.first == query_arb::fn_fullname) {
477                continue; // already written as description in header
478            }
479            out << "; " << ap.first << "="
480                << boost::apply_visitor(lexical_cast_visitor<string>(),
481                                        ap.second)
482                << std::endl;
483        }
484        break;
485    case FASTA_META_CSV:
486        out << std::endl;
487
488        // print header
489        if (count == 0) {
490            out_csv << "name";
491            for (auto& ap: attrs) {
492              if (ap.first == query_arb::fn_family) {
493                  continue; // alignment family is too much
494              }
495              out_csv << "," << escape_string(ap.first);
496            }
497            out_csv << "\r\n";
498        }
499
500        out_csv << c.getName();
501        for (auto& ap: attrs) {
502            if (ap.first == query_arb::fn_family) {
503                continue;
504            }
505            out_csv << ","
506                    << escape_string(
507                        boost::apply_visitor(
508                            lexical_cast_visitor<string>(),
509                            ap.second
510                            )
511                        );
512        }
513
514        out_csv << "\r\n";
515        break;
516    default:
517        throw std::runtime_error("Unknown meta-fmt output option");
518    }
519
520    string seq  = c.getAligned(!opts->out_dots, opts->out_dna);
521    int len = seq.size();
522    if (opts->line_length > 0) {
523        for (int i=0; i<len; i+=opts->line_length) {
524            out << seq.substr(i, opts->line_length) << std::endl;
525        }
526    } else {
527        out << seq << std::endl;
528    }
529    count++;
530}
531
532} // namespace sina
533
534/*
535  Local Variables:
536  mode:c++
537  c-file-style:"stroustrup"
538  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . 0))
539  indent-tabs-mode:nil
540  fill-column:99
541  End:
542*/
543// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
Note: See TracBrowser for help on using the repository browser.