1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
|
package Demeter::Plugins::X23A2MED;
use Moose;
extends 'Demeter::Plugins::FileType';
#use Demeter::IniReader;
#use Config::IniFiles;
use File::Basename;
use File::Copy;
use File::CountLines qw(count_lines);
use Const::Fast;
const my $INIFILE => 'x23a2med.demeter_conf';
use List::MoreUtils qw(any);
has '+is_binary' => (default => 0);
has '+description' => (default => "the NIST 4-element Vortex");
has '+version' => (default => 0.2);
has '+metadata_ini' => (default => File::Spec->catfile(File::Basename::dirname($INC{'Demeter.pm'}), 'Demeter', 'share', 'xdi', 'xdac.x23a2.ini'));
has 'nelements' => (is => 'rw', isa => 'Int', default => 4);
has 'dts' => (is => 'rw', isa => 'Str', default => q{});
has 'maxints' => (is => 'rw', isa => 'Str', default => q{});
has '+conffile' => (default => File::Spec->catfile(dirname($INC{'Demeter.pm'}), 'Demeter', 'Plugins', $INIFILE));
Demeter -> co -> read_config(File::Spec->catfile(dirname($INC{'Demeter.pm'}), 'Demeter', 'Plugins', $INIFILE));
sub is {
my ($self) = @_;
## the header, line of dashes, and column labels constitute 18
## lines, multiedge gives 4 more
return 0 if (count_lines($self->file) < 30);
open(my $D, $self->file) or $self->Croak("could not open " . $self->file . " as a BMM/X23A2MED file\n");
my $line = <$D>;
my $is_bmm = ($line =~ m{BMM});
$line = <$D>;
my $is_x23a2 = ($line =~ m{X-23A2});
my $is_nist = ($is_x23a2 or $is_bmm);
while (<$D>) {
last if ($_ =~ m{------+});
};
$line = <$D> || q{};
my @headers = split(" ", $line);
#my $cfg = new Config::IniFiles( -file => $self->inifile );
my $enr = Demeter->co->default("x23a2med", "energy");
my $ch1 = Demeter->co->default("x23a2med", "roi1");
# my $mere = Demeter->co->default("x23a2med", "multiedge_regex");
my $sl1 = Demeter->co->default("x23a2med", "slow1");
my $fa1 = Demeter->co->default("x23a2med", "fast1");
#Demeter->pjoin($enr, $ch1, $sl1, $fa1);
my $seems_escan = ($line =~ m{$enr\b}i);
my $seems_med = (($line =~ m{\b$ch1\b}i) or ($line =~ m{\broi\d_\d\b}i)); # hard wired multiedge labels
my $is_med = (($line =~ m{\b$sl1\b}i) and ($line =~ m{\b$fa1\b}i));
#Demeter->pjoin($is_nist, $seems_escan, $seems_med, $is_med);
close $D;
return ($is_nist and $seems_escan and $seems_med and $is_med);
};
sub fix {
my ($self) = @_;
my $file = $self->file;
my $new = File::Spec->catfile($self->stash_folder, $self->filename);
($new = File::Spec->catfile($self->stash_folder, "toss")) if (length($new) > 127);
## read the raw data file
Demeter->dispense('process', 'read_group', {file=>$file, group=>Demeter->mo->throwaway_group, type=>'data'});
my @labels = split(" ", $self->fetch_string('$column_label'));
## is this the four-element or one-element vortex?
my @represented = ();
my @edges = ();
##my $mere = Demeter->co->default("x23a2med", "multiedge_regex");
my $multiedge = any { $_ =~ m{roi\d_\d} } @labels; # hard wired column labels
foreach my $i (1 .. 4) {
my $is_ok = 1;
if (not $multiedge) {
$is_ok &&= any { $_ eq lc(Demeter->co->default("x23a2med", "roi$i") ) } @labels;
};
$is_ok &&= any { $_ eq lc(Demeter->co->default("x23a2med", "slow$i")) } @labels;
$is_ok &&= any { $_ eq lc(Demeter->co->default("x23a2med", "fast$i")) } @labels;
push @represented, $i if $is_ok;
};
return 0 if ($#represented == -1);
$self->nelements($#represented+1);
my @options = ();
foreach my $l (@labels) {
my $val = Demeter->mo->throwaway_group.".$l";
push @options, [$l, $val];
};
my $elab = Demeter->co->default('x23a2med', 'energy');
$elab = 'energy' if (($elab eq 'nergy') and Demeter->is_larch); # ifeffit's frickin' nergy problem!
my @labs = ($elab, lc(Demeter->co->default('x23a2med', 'i0')));
my (@edge1, @edge2);
my $maxints = q{};
my $dts = q{};
my $time = Demeter->co->default("x23a2med", "time");
my $inttime = Demeter->co->default("x23a2med", "inttime");
my @intcol = $self->fetch_array(Demeter->mo->throwaway_group.'.'.lc(Demeter->co->default("x23a2med", "intcol")));
my $nosignal = 0;
foreach my $ch (@represented) {
my $deadtime = Demeter->co->default("x23a2med", "dt$ch");
my @slow = $self->fetch_array(Demeter->mo->throwaway_group.'.'.lc(Demeter->co->default("x23a2med", "slow$ch")));
my @fast = $self->fetch_array(Demeter->mo->throwaway_group.'.'.lc(Demeter->co->default("x23a2med", "fast$ch")));
if (any {$_ == 0} @slow) {
++$nosignal;
next;
};
my ($max, @corr);
if (not $multiedge) { # normal MED file
my @roi = $self->fetch_array(Demeter->mo->throwaway_group.'.'.lc(Demeter->co->default("x23a2med", "roi$ch" )));
($max, @corr) = _correct($inttime, $time, $deadtime, \@intcol, \@roi, \@fast, \@slow);
$self->place_array(Demeter->mo->throwaway_group.".corr$ch", \@corr);
push @labs, "corr$ch";
} else { # multiedge MED file
my @roi = $self->fetch_array(Demeter->mo->throwaway_group.'.roi1_'.$ch);
($max, @corr) = _correct($inttime, $time, $deadtime, \@intcol, \@roi, \@fast, \@slow);
$self->place_array(Demeter->mo->throwaway_group.".c1_$ch", \@corr);
push @edge1, "c1_$ch";
@roi = $self->fetch_array(Demeter->mo->throwaway_group.'.roi2_'.$ch);
($max, @corr) = _correct($inttime, $time, $deadtime, \@intcol, \@roi, \@fast, \@slow);
$self->place_array(Demeter->mo->throwaway_group.".c2_$ch", \@corr);
push @edge2, "c2_$ch";
};
$maxints .= "$max ";
$dts .= "$deadtime ";
};
if ($multiedge) {
push @labs, @edge1, @edge2;
};
return 0 if ($nosignal == $#represented+1);
$self->nelements($#represented+1-$nosignal);
push @labs, 'diamond' if any {lc($_) eq 'diamond'} @labels;
push @labs, 'it' if any {lc($_) eq 'it'} @labels;
push @labs, 'ir' if any {lc($_) =~ m{\Air\z}} @labels;
push @labs, 'iref' if any {lc($_) =~ m{\Airef\z}} @labels;
my $text = ($self->nelements == 1) ? "1 channel" : $self->nelements." channels";
my $columns = join(", ".Demeter->mo->throwaway_group.".", @labs);
$dts =~ s{\s+\z}{};
$maxints =~ s{\s+\z}{};
$self->dts($dts);
$self->maxints($maxints);
my $command = Demeter->template('plugin', 'x23a2med', {file=>$new, columns=>$columns, text=>$text,
dts=>$dts, maxints=>$maxints});
unlink $new if (-e $new);
Demeter->dispose($command);
$self->fixed($new);
return $new;
};
sub suggest {
my ($self, $which) = @_;
$which ||= 'fluorescence';
if ($which eq 'transmission') {
return (energy => '$1',
numerator => '$2',
denominator => ($self->nelements == 1) ? '$4' : '$7',
ln => 1,);
} elsif ($self->nelements == 1) {
return (energy => '$1',
numerator => '$3',
denominator => '$2',
ln => 0,);
} elsif ($self->nelements == 2) {
return (energy => '$1',
numerator => '$3+$4',
denominator => '$2',
ln => 0,);
} elsif ($self->nelements == 3) {
return (energy => '$1',
numerator => '$3+$4+$5',
denominator => '$2',
ln => 0,);
} else {
return (energy => '$1',
numerator => '$3+$4+$5+$6',
denominator => '$2',
ln => 0,);
};
};
sub _correct {
my ($int, $time, $deadtime, $rintcol, $rroi, $rfast, $rslow) = @_;
$deadtime *= 1e-9; # nanoseconds!
my @corrected = ();
my ($toto, $totn)=(0,0);
my ($maxcount, $maxiter) = (20,0);
LOOP: foreach my $i (0 .. $#{$rroi}) {
if ($deadtime <= 1e-9) { # 1 nanosecond is an unreasonable
# deadtime, so this accounts for
# numerical issues in recognizing 0
push @corrected, $rroi->[$i] * $rfast->[$i] / $rslow->[$i];
next LOOP;
};
my $test = 1;
my $count = 0;
($int = $rintcol->[$i]) if ($time eq 'column');
$toto = $rfast->[$i]/$int;
(($totn, $test) = ($rslow->[$i],0)) if ($rfast->[$i] <= 1);
while ($test > $deadtime) {
$totn = ($rfast->[$i]/$int) * exp($toto*$deadtime);
$test = ($totn-$toto) / $toto;
$toto = $totn;
++$count;
$test = 0 if ($count >= $maxcount);
$maxiter = $count if ($count > $maxiter);
};
push @corrected, $rroi->[$i] * ($totn*$int/$rslow->[$i]);
};
#print "Maximum number of iterations: $maxiter\n";
return ($maxiter, @corrected);
};
after 'add_metadata' => sub {
my ($self, $data) = @_;
return if not Demeter->xdi_exists;
Demeter::Plugins::Beamlines::XDAC->is($data, $self->file);
$data->xdi->set_item('Detector', 'if', $self->nelements.' element Vortex silicon drift');
$data->xdi->set_item('Detector', 'med_nchannels', $self->nelements);
$data->xdi->set_item('Detector', 'med_deadtime', $self->dts);
$data->xdi->set_item('Detector', 'med_maxiterations', $self->maxints);
};
__PACKAGE__->meta->make_immutable;
1;
=head1 NAME
Demeter::Plugin::X23A2MED - filetype plugin for X23A2 Vortex data
=head1 VERSION
This documentation refers to Demeter version 0.9.26.
=head1 SYNOPSIS
This plugin performs a deadtime correction on data recorded using the
X23A2 Vortex silicon drift detector. Both the single-element and
four-element detectors are supported.
This plugin requires configuration to provide all the information
needed to correct each channel, including the known deadtime (in
nanoseconds) for each channel and the columns in the file containing
the ROI, fast, and slow channels.
=head1 METHODS
=over 4
=item C<is>
An X23A2 file is recognized by the second comment line identifying the
beamline at which the data were collected. An MED file is recognized
by having a large (>7) number of columns. Care is taken to be sure
that the file is an energy scan and has many lines of data. Care is
not, however, taken to be sure that the I0 column does not have any
zero-valued entries.
=item C<fix>
An ini file is used to specify the deadtime for each channel and is
prompted for the column labels for the ROI, fast, and slow channels
for each element of the detector. The default deadtime (280
nanoseconds) is very good for channels 1 and 2 and quite close for
channels 3 and 4.
Once all of this information is supplied, the deadtime correction is
done point-by-point for each channel and written out to a temporary
file in the stash directory. The columns in the stash directory are
energy, I0, the four corrected channels, It, and Ir. It and Ir are
written out if present in the original file. Ir can also be called
Iref.
=item C<suggest>
This method returns a list which can be used in a Demeter script
define a Data object which will correctly process the output file as
fluorescence XAS data.
=back
=head1 ADDITIONAL ATTRIBUTE
This plugin also provides the C<inifile> attribute which points at an
ini file containing the various paremeters of the deadtime correction.
Demeter ships with a demeter_conf file for configuring this plugin.
=head2 Deadtime correction parameters
To apply the correction algorithm, this plugin needs to know which
columns contain which data channels. For each channel N, we need:
=over 4
=item C<dtN>
The fast channel deadtime (which Joe measured to be approximately 280
nsec for each channel) for detector N.
=item C<roiN>
The column label for the region of interest (i.e. the discriminator
window channel) for detector N.
=item C<fastN>
The column label for the fast channel (i.e. the input count
rate) for detector N.
=item C<fastN>
The column label for the slow channel (i.e. the output count
rate) for detector N.
=back
=head2 Other column labels
Additionally, the implementation of the algorithm needs to know the
column labels for:
=over
=item *
The energy column. Oddly, Ifeffit removes the leading character from
the line in the data file containing the column labels. So "energy"
becomes "nergy". Go figure.
=item *
The I0 column label
=back
=head2 Integration time
The implementation of the algorithm also needs to know how integration
times were done in the measurement.
I strongly recommend that the integration time be recorded in the
file. (that can be set in XDAC). in that case, you set the C<intcol>
parameter to the name of the column label containing the per-point
integration time (which is called inttime) in XDAC. If that column is
missing, then the integration time must be specified by the C<inttime>
parameters.
The C<time> parameter is set to either C<column> or C<constant> to
choose between the C<intcol> and C<inttime> options.
=head1 THE CORRECTION ALGORITHM
If the deadtime is known for a channel, that will be used to correct
the fast channel (aka input count rate, or ICR) by iteratively solving
the transcendental equation which describes the attenuation of the
fast channel due to deadtime:
y = x*exp(-x*tau)
where tau is the deadtime, x is the actual input rate on the fast
channel, and y is the measured rate on the fast channel. The
iterative solution encoded in the C<_correct> subroutine thus solves
point-by-point for x using a value for tau and the column containing
y.
If the deadtime is set to 0 (or negative) for a channel, then the
standard correction of the ratio of the fast and slow channels (or
ICR/OCR) will be applied to that channel.
For high count rates, the proper deadtime correction is considerably
more accurate than the standard correction. For details see Woicik,
Ravel, Fischer, and Newburgh, J. Synchrotron Rad. (2010). 17, 409-413
https://doi:org/10.1107/S0909049510009064
=head1 MULTIEDGE FILES
At X23A2, we have four quad single channel analyzers, one for each
detector element of our four-element Vortex. Each quad SCA is
modified to have the input channels tied together. We use the fourth
output channel as the slow channel, or OCR, signal. This is done by
setting the discriminator window over the entire energy range. In
principle, we could record three regions of interest for each detector
element. However, we only have enough scalar input channels to our
crate to record two ROIs, along with the slow and fast (OCR and ICR)
signals.
Recording even two channels is useful for switching between edges as
part of a measurement macro. To this end, this plugin will recognize
when the system is configured that why by recognizing channel labels
that are different from the configured C<roiN> column labels, as
discussed above and typically C<Ifch1> through C<Ifch4>. Currently,
this is I<hard-wired> to be
roi1_1 roi1_2 roi1_3 roi1_4
for the ROI corresponding to one edge, and
roi2_1 roi2_2 roi2_3 roi2_4
for the ROI corresponding to the second edge. For example, in PbGeO3,
the C<roi1_N> might be configured to measure the Ge K alpha line while
the C<roi2_N> would then be configured for the Pb L alpha line.
When processed and presented to the user in the column selection
dialog, the corrected columns are then labeled
c1_1 c1_2 c1_3 c1_4
and
c2_1 c2_2 c2_3 c2_4
It is then up to the user to select the correct group of four when
importing the data.
=head1 BUGS AND LIMITATIONS
Please report problems to the Ifeffit Mailing List
(L<http://cars9.uchicago.edu/mailman/listinfo/ifeffit/>)
Patches are welcome.
=head1 AUTHORS
Joe Woicik, NIST (algorithm)
Bruce Ravel, L<http://bruceravel.github.io/home> (implementation)
http://bruceravel.github.io/demeter
=cut
|