#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/state say/;
use 5.010;
use Getopt::Declare;
use Finnigan;
my $args = new Getopt::Declare q{
[strict]
[mutex: -h -w]
[mutex: -a -r]
[mutex: -header -n]
-header extract the log header (record structure)
-d[ump] dump the requested feature with file seek addresses
-a[ll] detailed dump of all field descriptors [requires: -d]
-s[ize] print record size [requires: -d]
-n[unmber] <n:0+n> extract the log entry number <n>
-h[tml] format as html
-w[iki] format as a wiki table
-r[elative] show relative addersess in the dump [requires: -d]
<file> input file [required]
}
or exit(-1);
my $file = $args->{"<file>"};
-e $file or die "file '$file' does not exist";
-f $file or die "'$file' is not a plain file";
-s $file or die "'$file' has zero size";
# -----------------------------------------------------------------------------
open INPUT, "<$file" or die "can't open '$file': $!";
binmode INPUT;
my $file_header = Finnigan::FileHeader->decode(\*INPUT);
my $seq_row = Finnigan::SeqRow->decode(\*INPUT, $file_header->version);
my $cas_info = Finnigan::CASInfo->decode(\*INPUT);
my $rfi = Finnigan::RawFileInfo->decode(\*INPUT, $file_header->version);
my $run_header_addr = $rfi->preamble->run_header_addr;
# fast-forward to RunHeader
seek INPUT, $run_header_addr, 0;
my $run_header = Finnigan::RunHeader->decode(\*INPUT, $file_header->version);
my $first_scan = $run_header->sample_info->first_scan;
my $last_scan = $run_header->sample_info->last_scan;
my $params_addr = $run_header->params_addr;
# The following two structures must be skipped in order to reach the
# scan parameters header.
# Skip to the error log and read through
seek INPUT, $run_header->error_log_addr, 0;
my $error_log_length = Finnigan::Decoder->read(\*INPUT, ['length' => ['V', 'UInt32']])->{data}->{length}->{value};
foreach my $i ( 0 .. $error_log_length - 1) {
Finnigan::Error->decode(\*INPUT);
}
# Read through the scan event hierarchy
my $nsegs = Finnigan::Decoder->read(\*INPUT, ['nsegs' => ['V', 'UInt32']])->{data}->{nsegs}->{value};
foreach my $i ( 0 .. $nsegs - 1) {
my $n = Finnigan::Decoder->read(\*INPUT, ['n' => ['V', 'UInt32']])->{data}->{n}->{value};
foreach my $j ( 0 .. $n - 1) {
Finnigan::ScanEventTemplate->decode(\*INPUT, $file_header->version);
}
}
# now at the start of the ScanParameters header (GenericDataHeader)
my $header = Finnigan::GenericDataHeader->decode(\*INPUT);
if ( $args->{'-header'} ) {
if ( exists $args->{-d} ) {
if ( exists $args->{-s} ) {
my $size = $header->size;
say "size: $size";
}
if ( exists $args->{-a}) {
if ( exists $args->{-h} ) {
$header->dump(style => 'html', filter => ['n']);
foreach my $i (0 .. $header->n - 1) {
$header->field($i)->dump(style => 'html', header => undef);
}
}
elsif ( exists $args->{-w} ) {
$header->dump(style => 'wiki', filter => ['n']);
foreach my $i (0 .. $header->n - 1) {
$header->field($i)->dump(style => 'wiki', header => undef);
}
}
else {
$header->dump(relative => exists $args->{-r}, filter => ['n']);
foreach my $i (0 .. $header->n - 1) {
$header->field($i)->dump(relative => exists $args->{-r});
}
}
}
else {
if ( exists $args->{-h} ) {
$header->dump(style => 'html', relative => exists $args->{-r});
}
elsif ( exists $args->{-w} ) {
$header->dump(style => 'wiki', relative => exists $args->{-r});
}
else {
$header->dump(relative => exists $args->{-r});
}
}
}
else {
foreach my $i (0 .. $header->n - 1) {
say $header->field($i)->type
. "\t" . $header->field($i)->length
. "\t" . $header->field($i)->label;
}
}
}
else {
# Do the records. With the header on hand, skip to the ScanParameters stream
seek INPUT, $params_addr, 0;
my $record;
my @range = ($first_scan - 1, $last_scan - 1);
my @selected = @range;
if ( exists $args->{-n}) {
my $n = $args->{-n}{"<n>"};
if ($n - 1 < $range[0] or $n - 1 > $range[-1]) {
say STDERR "Record number $n is out of range [$first_scan .. $last_scan]";
exit -1;
}
@selected = ($n - 1) x 2;
}
foreach my $i ( $range[0] .. $range[1] ) {
exit if $i > $selected[1];
$record = Finnigan::ScanParameters->decode(\*INPUT, $header->field_templates);
if ( $i >= $selected[0] and $i <= $selected[1] ) {
my $j = 0;
if ( exists $args->{-d} ) {
if ( exists $args->{-s} ) {
my $size = $record->size;
say "size: $size";
}
if ( exists $args->{-h} ) {
$record->dump(style => 'html', relative => exists $args->{-r});
}
elsif ( exists $args->{-w} ) {
$record->dump(style => 'wiki', relative => exists $args->{-r});
}
else {
$record->dump(relative => exists $args->{-r});
}
}
else {
if ( exists $args->{-w} ) {
say "|| seq || label || value ||";
foreach my $key (@{$header->labels}) {
say "|| " . ($i + 1)
. " || " . $key
. " || " . $record->{data}->{$key}->{value}
. " ||";
$j++;
}
}
else {
foreach my $key (@{$header->labels}) {
say $i + 1
. "\t" . $key
. "\t" . $record->{data}->{$key}->{value};
$j++;
}
}
} # if / if not -d
} # if in range
} # for each record
}
__END__
=head1 NAME
uf-params - list or dump the ScanParameters records in a Finnigan raw file
=head1 SYNOPSIS
uf-params [options] file
Options:
-header extract the log header (record structure)
-d[ump] dump the requested feature showing file seek addresses
-a[ll] detailed dump of all field descriptors [requires: -d]
-s[ize] print record size [requires: -d]
-n[unmber] <n:0+n> extract the log entry number <n>
-h[tml] format as html
-w[iki] format as a wiki table
-r[elative] show relative addersess in the dump [requires: -d]
<file> input file [required]
=head1 OPTIONS
=over 4
=item B<-help>
Print a brief help message and exit.
=item B<-d[ump]>
Prints a table listing all fields in the requested object (a log entry
or the file header), with their seek addresses, sizes, names and
values. Individual entries can be selected with the B<-n[umber]>
option.
=item B<-n[umber]>
Gives the number of a single InstrumentLogRecord to extract
=item B<-h[tml]>
Format the dump output as an html table. When multiple entries are
specified, each will be rendered in its own table
=item B<-w[iki]>
Format the dump output as a wiki table.
=item B<-s[ize]>
Show structure size in bytes (works with the -d[ump] option).
=item B<-r[elative]>
Show relative addresses of all itmes in the dump. The default is to
show the absolute seek address. (works with the -d[ump] option)
=item B<-a[ll]>
Dump all GenericDataDescriptor entries in the file header (requires B<-header>)
=back
=head1 DESCRIPTION
B<uf-params> can be used to examine the ScanParameters? records in a
Finnigan raw file. These records contain a miscellany of data
pertaining to a single scan: ion injection time, retention time,
charge state and M/z of the precursor ion, M/z conversion
coefficients, and other data.
=head1 SEE ALSO
Finnigan::ScanParameters
=head1 EXAMPLES
=over 4
=item List all ScanParameters? records in the tabular form: <record
number, label, value>
uf-params sample.raw
=item Print the parameters record for the fifth scan:
uf-params -n 5 sample.raw
=item Dump the fifth ScanParameters? record in wiki format with total size and relative addresses:
uf-params -dswr -n 5 sample.raw
=item Print the contents of the stream header in the tabular form: <type, length, label>
uf-params -header sample.raw
=item Dump the header in the compact wiki format, with a stringified GenericDataDescriptor list:
uf-params -header -dw sample.raw
=item Dump the header in the extended wiki format, showing the
location of echa GenericDataDescriptor's element:
uf-params -header -daw sample.raw
=back
=cut