You can search for identifiers within the package.
in-package search v0.2.0
Source file fastq.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351(** Parsing FASTQ files *)open!Base(** A record type for FASTQ files.
{1 Overview}
Very similar to the {!Fasta.Record.t}. See that module for more
details.contents
{2 Fields}
For the "header" line, the [id] is everything up to the first space, and the
[desc] is everything after. Note, that [desc] is optional. E.g.,
{[
@this_is_the_id this is the desc
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
id ----------| Some desc -----|
]}
The other possibly weird field is [extra]. It is simply everything on the
line following the [+] (i.e., the third line of the 4 line FASTQ file).
It is currently {i NOT} a parse error if the sequence length and quality
string length are different lengths. This may change in the future. *)moduleRecord:sig(** {1 API} *)typet[@@derivingsexp]valcreate:id:string->desc:stringoption->seq:string->qual:string->extra:stringoption->t(** [create ~id ~desc ~seq ~qual ~extra] creates a new [t]. Shouldn't raise as
literally any values of the correct type are accepted. *)valto_string:t->string(** [to_string t] returns a string representation of [t] ready to print to a
FASTQ output file. *)valto_string_nl:?nl:string->t->string(** [to_string_nl t ~nl] returns a string representation of [t] ready to print
to a FASTQ output file, including a trailing newline (nl) string. [nl]
defaults to ["\n"]. *)valequal:t->t->bool(** [equal this other] returns [true] if all fields of two [t]s are the same. *)val(=):t->t->bool(** [Fastq.Record.(this = other)] is like [equal this other]. *)valid:t->string(** [id t] returns the [id] of the [t]. *)valdesc:t->stringoption(** [desc t] returns the [desc] (description) of the [t]. *)valseq:t->string(** [seq t] returns the [seq] of the [t]. *)valqual:t->string(** [qual t] returns the [qual] of [t]. *)valextra:t->stringoption(** [extra t] returns the [extra] of the [t] if there is one. It is whatever
is after the [+] line. Technically it should be the same ID+desc found in
the header line, but this library just treats it as a blob of data. *)valseq_length:t->int(** [seq_length t] returns the length of the [seq] of [t].
If you construct a record by hand (e.g., with [create]), and there are
spaces or other weird characters in the sequences, they will be counted in
the length. E.g.,
{[
let r =
Fastq.Record.create ~id:"apple" ~desc:None ~seq:"a a" ~qual:". ."
~extra:None
in
assert (Int.(3 = Fastq.Record.seq_length r))
]} *)valwith_id:string->t->t(** [with_id new_id t] returns a [t] with [new_id] instead of the original
[id]. *)valwith_seq:string->t->t(** [with_seq new_seq t] returns a [t] with [new_seq] instead of the original
[seq]. *)valwith_desc:stringoption->t->t(** [with_desc new_desc t] returns a [t] with [new_desc] instead of the
original [desc]. *)valwith_qual:string->t->t(** [with_qual new_qual t] returns a [t] with [new_qual] instead of the
original [qual]. *)valwith_extra:stringoption->t->t(** [with_extra new_extra t] returns a [t] with [new_extra] instead of the
original [extra]. *)valrev:t->t(** [rev t] returns the reverse of [t]. I.e., the [seq] and the [qual] are
reversed. *)valcomp:t->t(** [comp t] returns the complement of [t]. I.e., the [seq] is complemented.
Uses IUPAC conventions. Any "base" (char) that isn't part of the IUPAC
passes through unchanged. Note that [comp] does not round-trip. *)valrev_comp:t->t(** [rev_comp t] returns the reverse complement of [t]. I.e., the [seq] is
reverse complemented, and the quality score is reversed to match it. Uses
IUPAC conventions. Any "base" (char) that isn't part of the IUPAC passes
through unchanged. Note that [rev_comp] does not round-trip. *)end=struct[@@@coverageoff]typet={id:string;desc:stringoption;seq:string;qual:string;extra:stringoption}[@@derivingsexp][@@@coverageon]letcreate~id~desc~seq~qual~extra={id;desc;seq;qual;extra}letto_stringr=letheader=matchr.descwithNone->r.id|Somedesc->r.id^" "^descinletextra=Option.value~default:""r.extrainPrintf.sprintf"@%s\n%s\n+%s\n%s"headerr.seqextrar.qualletto_string_nl?(nl="\n")r=to_stringr^nlletequalr1r2=String.(r1.id=r2.id)&&String.(r1.seq=r2.seq)&&String.(r1.qual=r2.qual)&&Option.equalString.equalr1.descr2.desc&&Option.equalString.equalr1.extrar2.extralet(=)=equalletidr=r.idletdescr=r.descletseqr=r.seqletqualr=r.qualletextrar=r.extraletseq_lengthr=String.lengthr.seqletwith_ididr={rwithid}letwith_seqseqr={rwithseq}letwith_descdescr={rwithdesc}letwith_qualqualr={rwithqual}letwith_extraextrar={rwithextra}letrevt=t|>with_seq(String.revt.seq)|>with_qual(String.revt.qual)letcompt=t|>with_seq(Utils.complementt.seq)letrev_compt=t|>with_seq(Utils.rev_complementt.seq)|>with_qual(String.revt.qual)end(** [In_channel] for FASTQ records.
For more general info, see the {!Record_in_channel} module mli file. For
more specific info, see the {!Fasta.In_channel} module.
Note: Empty fastq files parse OK, but are empty.
{1:examples Examples}
These examples basically match those in the {!Fasta.In_channel} module.
{2 Return all records in a list}
{[
let records = Fastq.In_channel.with_file_records fname
]}
{2 Iterating over records}
Use the [iter] functions when you need to go over each record and perform
some side-effects with them.
Print sequence IDs and sequence lengths
{[
let () =
Fastq.In_channel.with_file_iter_records "sequences.fastq"
~f:(fun record ->
let open Fastq.Record in
printf "%s => %d\n" (id record) (seq_length record) )
]}
Print sequence index, IDs, and sequence lengths.
This is like the last example except that we also want to print the index.
The first record is 0, the 2nd is 1, etc.
{[
let () =
Fastq.In_channel.with_file_iteri_records "sequences.fastq"
~f:(fun i record ->
let open Fastq.Record in
printf "%d: %s => %d\n" (i + 1) (id record) (seq_length record) )
]}
{2 Folding over records}
If you need to reduce all the records down to a single value, use the [fold]
functions.
Get total length of all sequences in the file.
{[
let total_length =
Fastq.In_channel.with_file_fold_records "sequences.fastq" ~init:0
~f:(fun length record -> length + Fastq.Record.seq_length record )
]}
{2:pipelines Pipelines with records}
Sometimes you have a "pipeline" of computations that you need to do one
after the other on records. In that case, you could the [sequence]
functions. Here's a silly example.
{[
let () =
Fastq.In_channel.with_file name ~f:(fun chan ->
Fastq.In_channel.record_sequence chan
(* Add sequence index to record description *)
|> Sequence.mapi ~f:(fun i record ->
let new_desc =
match Fastq.Record.desc record with
| None ->
Some (sprintf "sequence %d" i)
| Some old_desc ->
Some (sprintf "%s -- sequence %d" old_desc i)
in
Fastq.Record.with_desc new_desc record )
(* Convert all sequence chars to lowercase *)
|> Sequence.map ~f:(fun record ->
let new_seq = String.lowercase (Fastq.Record.seq record) in
Fastq.Record.with_seq new_seq record )
(* Print sequences *)
|> Sequence.iter ~f:(fun record ->
print_endline @@ Fastq.Record.to_string record ) )
]}
One thing to watch out for though...if you get an exception half way through
and you are running side-effecting code like we are here then part of your
side effects will have occured and part of them will {i not} have occured.
As you can see, if that fastq file has more than one sequence it will hit
the [assert false] and blow up. *)moduleIn_channel:sigincludeRecord_in_channel.Swithtyperecord:=Record.tend=structmoduleT=structincludePrivate.Peekable_in_channeltyperecord=Record.tletclean_sequences=String.filters~f:(func->Char.(c<>' '))(* Be sure not to call this on a quality line as it may start with [@]. *)letparse_header_lineline=ifnot(String.is_prefixline~prefix:"@")thenfailwith@@Printf.sprintf"I expected to see a header line starting with '@', but saw '%s' \
instead."@@String.prefixline0;matchString.split~on:' '@@String.chop_prefix_exn~prefix:"@"@@String.striplinewith|[id]->(id,None)|id::desc->(id,Some(String.concat~sep:" "desc))(* String.split should at least give [""]. Should never get here. *)|[]->assertfalseletparse_extra_lineline=matchString.chop_prefixline~prefix:"+"with|Somes->s|None->failwith@@Printf.sprintf"I expected to see an extra line starting with '+', but saw \
'%s' instead."@@String.prefixline0letinput_recordchan=letrecloop?header_line?seq_line?extra_linei=match(input_line~fix_win_eol:truechan,i)with|Someline,0->loop(i+1)~header_line:line|Someline,1->loop(i+1)?header_line~seq_line:line|Someline,2->loop(i+1)?header_line?seq_line~extra_line:line|Someline,3->letid,desc=parse_header_line@@Option.value_exnheader_lineinletseq=clean_sequence@@Option.value_exnseq_lineinletextra=Option.map~f:parse_extra_lineextra_lineinletqual=clean_sequencelineinSome(Record.create~id~desc~seq~extra~qual)|None,0->None|None,1|None,2|None,3->failwith"I was partway through a record, but I hit the end of the file."|Some_,_|None,_->assertfalseinloop0endincludeTincludeRecord_in_channel.Make(T)end