forked from ome/bioformats
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PrairieMetadata.java
1099 lines (930 loc) · 34.1 KB
/
PrairieMetadata.java
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
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* #%L
* OME Bio-Formats package for reading and converting biological file formats.
* %%
* Copyright (C) 2005 - 2012 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-2.0.html>.
* #L%
*/
package loci.formats.in;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Metadata structure for Prairie Technologies' TIFF-based format.
*
* @author Curtis Rueden
*/
public class PrairieMetadata {
/** {@code <Sequence>} elements, keyed on each sequence's {@code cycle}. */
private final HashMap<Integer, Sequence> sequences =
new HashMap<Integer, Sequence>();
/** Table of key/value pairs at the top level. */
private final ValueTable scanValues = new ValueTable();
/** The first actual {@code <Sequence>} element. */
private Sequence firstSequence;
/** Minimum cycle value. */
private int cycleMin = Integer.MAX_VALUE;
/** Maximum cycle value. */
private int cycleMax = Integer.MIN_VALUE;
/** The date of the acquisition. */
private String date;
/** The wait time of the acquisition. */
private Double waitTime;
/** Set of active channel indices. */
private final HashSet<Integer> activeChannels = new HashSet<Integer>();
/** Key/value pairs from CFG and/or ENV files. */
private final ValueTable config = new ValueTable();
/**
* Creates a new Prairie metadata by parsing the given XML, CFG and/or ENV
* documents.
*
* @param xml The XML document to parse, or null if none available.
* @param cfg The CFG document to parse, or null if none available.
* @param env The ENV document to parse, or null if none available.
*/
public PrairieMetadata(final Document xml, final Document cfg,
final Document env)
{
if (xml != null) parseXML(xml);
if (cfg != null) parseCFG(cfg);
if (env != null) parseENV(env);
parseChannels();
}
// -- PrairieMetadata methods --
/** Gets the {@code waitTime} recorded in the configuration. */
public Double getWaitTime() {
return waitTime;
}
/**
* Gets the list of active channel indices, in sorted order.
* <p>
* These indices correspond to the configuration's {@code channel} keys
* flagged as {@code True}.
* </p>
*/
public int[] getActiveChannels() {
final int[] result = new int[activeChannels.size()];
int i = 0;
for (int channelIndex : activeChannels) {
result[i++] = channelIndex;
}
Arrays.sort(result);
return result;
}
/**
* Gets whether the stage position X coordinates are inverted (i.e.,
* left-to-right).
*/
public boolean isInvertX() {
//return b(getConfig("xYStageXPositionIncreasesLeftToRight"));
// HACK: It appears that this flag is not actually respected
// in the tile layout, so we behave as though it is never set!
return false;
}
/**
* Gets whether the stage position Y coordinates are inverted (i.e.,
* bottom-to-top).
*/
public boolean isInvertY() {
return b(value(getConfig("xYStageYPositionIncreasesBottomToTop")));
}
/** Gets the {@code bitDepth} recorded in the configuration. */
public Integer getBitDepth() {
return i(value(getConfig("bitDepth")));
}
/** Gets the first {@code laserPower} recorded in the configuration. */
public Double getLaserPower() {
return d(value(getConfig("laserPower"), 0));
}
/** Gets the {@code value} of the given configuration {@code key}. */
public Value getConfig(final String key) {
return config.get(key);
}
/** Gets the map of configuration key/value pairs. */
public ValueTable getConfig() {
return config;
}
/** Gets the date of the acquisition. */
public String getDate() {
return date;
}
/**
* Gets the minimum cycle value. Matches the smallest {@code cycle} attribute
* found, and hence will not necessarily equal {@code 1} (though in practice
* it usually does).
*/
public int getCycleMin() {
return cycleMin;
}
/**
* Gets the maximum cycle value. Matches the largest {@code cycle} attribute
* found, and hence will not necessarily equal {@code sequences#size()}
* (though in practice it usually does).
*/
public int getCycleMax() {
return cycleMax;
}
/**
* Gets the number of recorded cycles. This value is equal to
* {@link #getCycleMax()} - {@link #getCycleMin()} + 1.
*/
public int getCycleCount() {
return cycleMax - cycleMin + 1;
}
/** Gets the first {@code Sequence}. */
public Sequence getFirstSequence() {
return firstSequence;
}
/** Gets the {@code Sequence} at the given {@code cycle}. */
public Sequence getSequence(final int cycle) {
return sequences.get(cycle);
}
/** Gets all {@code Sequences}, ordered by {@code cycle}. */
public ArrayList<Sequence> getSequences() {
return valuesByKey(sequences);
}
/** Gets the {@code Frame} at the given ({@code cycle} and {@code index}). */
public Frame getFrame(final int cycle, final int index) {
final Sequence sequence = getSequence(cycle);
if (sequence == null) return null;
return sequence.getFrame(index);
}
/**
* Gets the {@code Frame} at the given ({@code cycle}, {@code index},
* {@code channel}).
*/
public PFile getFile(final int cycle, final int index, final int channel) {
final Frame frame = getFrame(cycle, index);
if (frame == null) return null;
return frame.getFile(channel);
}
/**
* Gets the {@code value} of the given {@code key}, at the top-level
* {@code <PVScan>} element.
*/
public Value getValue(final String key) {
return scanValues.get(key);
}
/** Gets the table of {@code PVScan} key/value pairs. */
public ValueTable getValues() {
return scanValues;
}
// -- Helper methods --
/** Parses metadata from Prairie XML file. */
private void parseXML(final Document doc) {
final Element pvScan = doc.getDocumentElement();
checkElement(pvScan, "PVScan");
// parse <PVStateShard> key/value block
parsePVStateShard(pvScan, scanValues);
// parse acquisition date
date = attr(pvScan, "date");
// iterate over all Sequence elements
final NodeList sequenceNodes = doc.getElementsByTagName("Sequence");
for (int s = 0; s < sequenceNodes.getLength(); s++) {
final Element sequenceElement = el(sequenceNodes, s);
if (sequenceElement == null) continue;
final Sequence sequence = new Sequence(sequenceElement);
if (firstSequence == null) firstSequence = sequence;
final int cycle = sequence.getCycle();
if (cycle < cycleMin) cycleMin = cycle;
if (cycle > cycleMax) cycleMax = cycle;
sequences.put(cycle, sequence);
}
}
/**
* Parses metadata from Prairie CFG file. This file is only present for
* Prairie datasets recorded prior to version 5.2.
*/
private void parseCFG(final Document doc) {
checkElement(doc.getDocumentElement(), "PVConfig");
final NodeList waitNodes = doc.getElementsByTagName("PVTSeriesElementWait");
if (waitNodes.getLength() > 0) {
final Element waitElement = el(waitNodes, 0);
waitTime = d(attr(waitElement, "waitTime"));
}
parseKeys(doc.getDocumentElement(), config);
}
/**
* Parses metadata from Prairie ENV file. This file is only present for
* Prairie datasets recorded with version 5.2 or later.
*/
private void parseENV(final Document doc) {
checkElement(doc.getDocumentElement(), "Environment");
parsePVStateShard(doc.getDocumentElement(), config);
}
/**
* Parses {@code <Key>} elements beneath the given element, into the specified
* table. These {@code <Key>} elements are only present in data from
* PrairieView versions prior to 5.2.
*/
private void parseKeys(final Element el, final ValueTable table) {
final NodeList keyNodes = el.getElementsByTagName("Key");
for (int k = 0; k < keyNodes.getLength(); k++) {
final Element keyElement = el(keyNodes, k);
if (keyElement == null) continue;
final String key = attr(keyElement, "key");
final String value = attr(keyElement, "value");
final int underscore = key.indexOf("_");
if (underscore < 0) {
// single key/value pair
table.put(key, new ValueItem(value, null));
}
else {
// table of key/value pairs
final String prefix = key.substring(0, underscore);
final String index = key.substring(underscore + 1);
if (!table.containsKey(prefix)) {
table.put(prefix, new ValueTable());
}
final ValueTable subTable = (ValueTable) table.get(prefix);
final String[] tokens = value.split(",");
if (tokens.length == 1) {
// single value
subTable.put(index, new ValueItem(value, null));
}
else {
// sub-table of values
final ValueTable subSubTable = new ValueTable();
for (int i=0; i<tokens.length; i++) {
subSubTable.put("" + i, new ValueItem(tokens[i], null));
}
subTable.put(index, subSubTable);
}
}
}
}
/**
* Parses the {@code <PVStateShard>} element beneath the given element, into
* the specified table. These {@code <PVStateShard>} elements are only present
* in data from PrairieView versions 5.2 and later.
*/
private void parsePVStateShard(final Element el, final ValueTable table) {
final Element pvStateShard = getFirstChild(el, "PVStateShard");
if (pvStateShard == null) return;
final NodeList svNodes = el.getElementsByTagName("PVStateValue");
for (int k = 0; k < svNodes.getLength(); k++) {
final Element keyElement = el(svNodes, k);
if (keyElement == null) continue;
final String key = attr(keyElement, "key");
final String value = attr(keyElement, "value");
if (value != null) {
// E.g.: <PVStateValue key="linesPerFrame" value="186" />
table.put(key, new ValueItem(value, attr(keyElement, "description")));
continue;
}
// value is itself a table of values
final ValueTable subTable = new ValueTable();
table.put(key, subTable);
// process <IndexedValue> elements; e.g.:
// <IndexedValue index="0" value="605" description="Ch1 High Voltage" />
final NodeList ivNodes = keyElement.getElementsByTagName("IndexedValue");
for (int i = 0; i < ivNodes.getLength(); i++) {
final Element ivElement = el(ivNodes, i);
if (ivElement == null) continue;
final String index = attr(ivElement, "index");
if (index == null) continue; // invalid <IndexedValue> element
final String iValue = attr(ivElement, "value");
final String iDescription = attr(ivElement, "description");
subTable.put(index, new ValueItem(iValue, iDescription));
}
// process <SubindexedValue> elements; e.g.:
// <SubindexedValues index="ZAxis">
// <SubindexedValue subindex="0" value="-9" description="Focus" />
// <SubindexedValue subindex="1" value="62.45" description="Piezo" />
// </SubindexedValues>
final NodeList sivNodes =
keyElement.getElementsByTagName("SubindexedValues");
for (int i = 0; i < sivNodes.getLength(); i++) {
final Element sivElement = el(sivNodes, i);
if (sivElement == null) continue;
final String index = attr(sivElement, "index");
if (index == null) continue; // invalid <SubindexedValues> element
final ValueTable subSubTable = new ValueTable();
subTable.put(index, subSubTable);
// iterate over <SubindexedValue> children
final NodeList subNodes =
sivElement.getElementsByTagName("SubindexedValue");
for (int s = 0; s < subNodes.getLength(); s++) {
final Element subElement = el(subNodes, s);
final String subindex = attr(subElement, "subindex");
if (subindex == null) continue; // invalid <SubindexedValue> element
final String sValue = attr(subElement, "value");
final String sDescription = attr(subElement, "description");
subSubTable.put(subindex, new ValueItem(sValue, sDescription));
}
}
}
}
/**
* Parses details of the activated channels into the {@link #activeChannels}
* data structure.
*/
private void parseChannels() {
final Value channels = config.get("channel");
if (!(channels instanceof ValueTable)) return;
final ValueTable channelsTable = (ValueTable) channels;
for (final String key : channelsTable.keySet()) {
final Value value = channelsTable.get(key);
// verify that the channel is active
if (!b(value(value))) continue; // channel not active
// parse the channel index (converting to a 1-based index!)
final int channelIndex = i(key) + 1;
// add the channel index to the active channels list
activeChannels.add(channelIndex);
}
}
/**
* Checks that the given element has the specified name.
*
* @throws IllegalArgumentException if the name does not match.
*/
private void checkElement(final Element el, final String name) {
if (!el.getNodeName().equals(name)) {
throw new IllegalArgumentException("Not a " + name + " element");
}
}
/** Gets the first child element with the given name. */
private Element getFirstChild(final Element el, final String name) {
// NB: Unfortunately, the Element interface has no API method to obtain
// _only_ direct children with a given name; the getElementsByTagName
// method returns _all_ descendant elements with the given name.
final NodeList nodeList = el.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
final Element child = el(nodeList, i);
if (child == null) continue;
if (name.equals(child.getNodeName())) return child;
}
return null;
}
/** Gets the {@code index}th element from the given list of nodes. */
private Element el(final NodeList nodes, final int index) {
final Node node = nodes.item(index);
if (!(node instanceof Element)) return null;
return (Element) node;
}
/** Gets the attribute value with the given name, or null if not defined. */
private String attr(final Element el, final String name) {
return el.hasAttribute(name) ? el.getAttribute(name) : null;
}
/** Returns {@code value.value()}, or null if {@code value} is null. */
private String value(final Value value) {
return value == null ? null : value.value();
}
/**
* Returns {@code value.get(key).value()}, or null if {@code value} or
* {@code value.get(key)} is null.
*/
private String value(final Value value, final String key) {
if (value == null) return null;
final Value v = value.get(key);
return v == null ? null : v.value();
}
/**
* Returns {@code value.get(index).value()}, or null if {@code value} or
* {@code value.get(index)} is null.
*/
private String value(final Value value, final int index) {
if (value == null) return null;
final Value v = value.get(index);
return v == null ? null : v.value();
}
/** Converts the given string to a {@code boolean}. */
private boolean b(final String value) {
return Boolean.parseBoolean(value);
}
/**
* Converts the given string to a {@link Double}, or {@code null} if
* incompatible.
*/
private Double d(final String value) {
if (value == null) return null;
try {
return new Double(value);
}
catch (final NumberFormatException exc) {
// TODO: log it
return null;
}
}
/**
* Converts the given string to an {@link Integer}, or null if incompatible.
*/
private Integer i(final String value) {
if (value == null) return null;
try {
return new Integer(value);
}
catch (final NumberFormatException exc) {
// TODO: log it
return null;
}
}
/**
* Gets the {@code i}th token of the given string, split according to the
* specific regular expression.
*/
private String token(final String s, final String regex, final int i) {
if (s == null) return null;
final String[] tokens = s.split(regex);
return tokens.length > i ? tokens[i] : null;
}
/** Gets the values of the given map, sorted by key. */
private <K extends Comparable<? super K>, V> ArrayList<V> valuesByKey(
final Map<K, V> map)
{
final ArrayList<K> keys = new ArrayList<K>(map.size());
final ArrayList<V> values = new ArrayList<V>(map.size());
keys.addAll(map.keySet());
Collections.sort(keys);
for (final K key : keys) {
values.add(map.get(key));
}
return values;
}
// -- Helper classes --
/** A Prairie {@code <Sequence>}. */
public class Sequence {
/**
* {@code <Frame>} elements beneath this {@code <Sequence>}, keyed on each
* frame's {@code index}.
*/
private final HashMap<Integer, Frame> frames =
new HashMap<Integer, Frame>();
/** Table of key/value pairs for this {@code <Sequence>}. */
private final ValueTable sequenceValues = new ValueTable();
/** The first actual {@code <Frame>} element for this {@code <Sequence>}. */
private Frame firstFrame;
/** Minimum index value. */
private int indexMin = Integer.MAX_VALUE;
/** Maximum index value. */
private int indexMax = Integer.MIN_VALUE;
/** {@code type} attribute of this {@code <Sequence>}. */
private String type;
/** {@code cycle} of this {@code <Sequence>}. */
private Integer cycle;
/**
* Creates a new sequence by parsing the given {@code <Sequence>} element.
*/
public Sequence(final Element sequenceElement) {
parse(sequenceElement);
}
/** Parses metadata from the given {@code Sequence} element. */
public void parse(final Element sequenceElement) {
checkElement(sequenceElement, "Sequence");
// parse <PVStateShard> key/value block
parsePVStateShard(sequenceElement, sequenceValues);
type = attr(sequenceElement, "type");
cycle = i(attr(sequenceElement, "cycle"));
if (cycle == null) {
throw new IllegalArgumentException("Sequence missing cycle attribute");
}
// iterate over all Frame elements
final NodeList frameNodes = sequenceElement.getElementsByTagName("Frame");
for (int f = 0; f < frameNodes.getLength(); f++) {
final Element frameElement = el(frameNodes, f);
if (frameElement == null) continue;
final Frame frame = new Frame(this, frameElement);
if (firstFrame == null) firstFrame = frame;
final int index = frame.getIndex();
if (index < indexMin) indexMin = index;
if (index > indexMax) indexMax = index;
frames.put(index, frame);
}
}
/** Gets the {@code type} associated with this {@code Sequence}. */
public String getType() {
return type;
}
/**
* Gets whether this {@code Sequence} should be considered a time series.
*/
public boolean isTimeSeries() {
return "TSeries Timed Element".equals(type);
}
/** Gets the {@code cycle} associated with this {@code Sequence}. */
public int getCycle() {
return cycle;
}
/**
* Gets the minimum index value. Matches the smallest {@code index}
* attribute found, and hence will not necessarily equal {@code 1} (though
* in practice it usually does).
*/
public int getIndexMin() {
return indexMin;
}
/**
* Gets the maximum index value. Matches the largest {@code index} attribute
* found, and hence will not necessarily equal {@code frames#size()} (though
* in practice it usually does).
*/
public int getIndexMax() {
return indexMax;
}
/**
* Gets the number of recorded indices at this {@code Sequence}. This value
* is equal to {@link #getIndexMax()} - {@link #getIndexMin()} + 1.
*/
public int getIndexCount() {
return indexMax - indexMin + 1;
}
/** Gets the first {@code Frame} of the {@code Sequence}. */
public Frame getFirstFrame() {
return firstFrame;
}
/** Gets the {@code Frame} with the given {@code index}. */
public Frame getFrame(final int index) {
return frames.get(index);
}
/**
* Gets the {@code Frame} at the given ({@code cycle}, {@code index},
* {@code channel}).
*/
public PFile getFile(final int index, final int channel) {
final Frame frame = getFrame(index);
if (frame == null) return null;
return frame.getFile(channel);
}
/**
* Gets the {@code value} of the given {@code key}, beneath this
* {@code Sequence}, inferring the value from the parent {@code <PVScan>}
* section as needed.
*/
public Value getValue(final String key) {
if (sequenceValues.containsKey(key)) return sequenceValues.get(key);
return PrairieMetadata.this.getValue(key);
}
/** Gets the table of {@code Frame} key/value pairs. */
public ValueTable getValues() {
return sequenceValues;
}
}
/** A Prairie {@code <Frame>}, beneath a {@code <Sequence>}. */
public class Frame {
/** The {@code <Sequence>} containing this {@code <Frame>}. */
private Sequence sequence;
/**
* {@code <File>} elements beneath this {@code <Frame>}, keyed on each
* file's {@code channel}.
*/
private final HashMap<Integer, PFile> files = new HashMap<Integer, PFile>();
/** Table of key/value pairs for this {@code <Frame>}. */
private final ValueTable frameValues = new ValueTable();
/** The first actual {@code <File>} element for this {@code <Frame>}. */
private PFile firstFile;
/** {@code relativeTime} attribute of this {@code <Frame>}. */
private Double relativeTime;
/** {@code absoluteTime} attribute of this {@code <Frame>}. */
private Double absoluteTime;
/** {@code index} of this {@code <Frame>}. */
private Integer index;
/** Creates a new frame by parsing the given {@code <Frame>} element. */
public Frame(final Sequence sequence, final Element frameElement) {
this.sequence = sequence;
parse(frameElement);
}
// -- Frame methods --
/** Gets the {@code <Sequence>} containing this {@code <Frame>}. */
public Sequence getSequence() {
return sequence;
}
/** Parses metadata from the given {@code Frame} element. */
public void parse(final Element frameElement) {
checkElement(frameElement, "Frame");
// parse <PVStateShard> key/value block
parsePVStateShard(frameElement, frameValues);
relativeTime = d(attr(frameElement, "relativeTime"));
absoluteTime = d(attr(frameElement, "absoluteTime"));
index = i(attr(frameElement, "index"));
if (index == null) {
throw new IllegalArgumentException("Frame missing index attribute");
}
// iterate over all File elements
final NodeList fileNodes = frameElement.getElementsByTagName("File");
for (int f = 0; f < fileNodes.getLength(); f++) {
final Element fileElement = el(fileNodes, f);
if (fileElement == null) continue;
final PFile file = new PFile(this, fileElement);
if (firstFile == null) firstFile = file;
final int channel = file.getChannel();
files.put(channel, file);
}
parseKeys(frameElement, frameValues);
}
/** Gets the {@code relativeTime} associated with this {@code Frame}. */
public double getRelativeTime() {
return relativeTime;
}
/** Gets the {@code absoluteTime} associated with this {@code Frame}. */
public double getAbsoluteTime() {
return absoluteTime;
}
/** Gets the {@code index} associated with this {@code Frame}. */
public int getIndex() {
return index;
}
/** Gets the first {@code File} of the {@code Sequence}. */
public PFile getFirstFile() {
return firstFile;
}
/** Gets the {@code File} with the given {@code channel}. */
public PFile getFile(final int channel) {
return files.get(channel);
}
/** Gets the objective lens string for this {@code Frame}. */
public String getObjectiveLens() {
return value(getValue("objectiveLens"));
}
/** Extracts the objective manufacturer from the objective lens string. */
public String getObjectiveManufacturer() {
return token(getObjectiveLens(), " ", 0);
}
/** Extracts the magnification from the objective lens string. */
public Double getMagnification() {
return d(token(getObjectiveLens(), " ", 1));
}
/** Extracts the immersion from the objective lens string. */
public String getImmersion() {
return token(getObjectiveLens(), " ", 2);
}
/** Gets the numerical aperture of the lens for this {@code Frame}. */
public Double getObjectiveLensNA() {
return d(value(getValue("objectiveLensNA")));
}
/** Gets the pixels per line for this {@code Frame}. */
public Integer getPixelsPerLine() {
return i(value(getValue("pixelsPerLine")));
}
/** Gets the lines per frame for this {@code Frame}. */
public Integer getLinesPerFrame() {
return i(value(getValue("linesPerFrame")));
}
/** Gets the X stage position associated with this {@code Frame}. */
public Double getPositionX() {
final Double posX = d(value(getValue("positionCurrent"), "XAxis"));
return posX == null ? null : isInvertX() ? -posX : posX;
}
/** Gets the Y stage position associated with this {@code Frame}. */
public Double getPositionY() {
final Double posY = d(value(getValue("positionCurrent"), "YAxis"));
return posY == null ? null : isInvertY() ? -posY : posY;
}
/** Gets the Z stage position associated with this {@code Frame}. */
public Double getPositionZ() {
return d(value(getValue("positionCurrent"), "ZAxis"));
}
/** Gets the optical zoom associated with this {@code Frame}. */
public Double getOpticalZoom() {
return d(value(getValue("opticalZoom")));
}
/** Gets the microns per pixel along X for this {@code Frame}. */
public Double getMicronsPerPixelX() {
return d(value(getValue("micronsPerPixel"), "XAxis"));
}
/** Gets the microns per pixel along Y for this {@code Frame}. */
public Double getMicronsPerPixelY() {
return d(value(getValue("micronsPerPixel"), "YAxis"));
}
/**
* Gets the {@code c}th offset for this {@code Frame}.
*
* @param c The 0-based(!) channel index for which to obtain the offset.
*/
public Double getOffset(final int c) {
return d(value(getValue("pmtOffset"), c));
}
/**
* Gets the {@code c}th gain for this {@code Frame}.
*
* @param c The 0-based(!) channel index for which to obtain the gain.
*/
public Double getGain(final int c) {
return d(value(getValue("pmtGain"), c));
}
/** Gets the imaging device associated with this {@code Frame}. */
public String getImagingDevice() {
return value(getValue("imagingDevice"));
}
/**
* Gets the {@code value} of the given {@code key}, beneath this
* {@code Frame}, inferring the value from the parent {@code <Sequence>}
* or grandparent {@code <PVScan>} section as needed.
*/
public Value getValue(final String key) {
if (frameValues.containsKey(key)) return frameValues.get(key);
return getSequence().getValue(key);
}
/** Gets the table of {@code Frame} key/value pairs. */
public ValueTable getValues() {
return frameValues;
}
}
/**
* A Prairie {@code <File>} beneath a {@code <Frame>}. It is called
* {@code PFile} rather than {@code File} to avoid confusion with the
* {@link java.io.File} class.
*/
public class PFile {
/** The {@code <Frame>} containing this {@code <File>}. */
private Frame frame;
/** {@code channel} of this {@code <File>}. */
private Integer channel;
/** {@code channelName} attribute of this {@code <File>}. */
private String channelName;
/** {@code filename} attribute of this {@code <File>}. */
private String filename;
/** Creates a new file by parsing the given {@code <File>} element. */
public PFile(final Frame frame, final Element fileElement) {
this.frame = frame;
parse(fileElement);
}
// -- PFile methods --
/** Gets the {@code <Frame>} containing this {@code <File>}. */
public Frame getFrame() {
return frame;
}
/** Parses metadata from the given {@code File} element. */
public void parse(final Element fileElement) {
checkElement(fileElement, "File");
channel = i(attr(fileElement, "channel"));
if (channel == null) {
throw new IllegalArgumentException("File missing channel attribute");
}
channelName = attr(fileElement, "channelName");
filename = attr(fileElement, "filename");
}
/** Gets the {@code channel} associated with this {@code File}. */
public int getChannel() {
return channel;
}
/** Gets the {@code channelName} associated with this {@code File}. */
public String getChannelName() {
return channelName;
}
/** Gets the {@code filename} associated with this {@code File}. */
public String getFilename() {
return filename;
}
}
/**
* A value in a Prairie metadata dictionary.
* <p>
* Prior to PrairieView 5.2, these were expressed as {@code <Key>} elements:
* </p>
*
* <pre>
* <Key key="linesPerFrame" permissions="Read, Write, Save" value="186" />
* <Key key="pmtGain_0" permissions="Write, Save" value="605" />
* <Key key="pmtGain_1" permissions="Write, Save" value="604" />
* <Key key="pmtGain_2" permissions="Write, Save" value="0" />
* <Key key="positionCurrent_XAxis" permissions="Write, Save" value="0.95" />
* <Key key="positionCurrent_YAxis" permissions="Write, Save" value="-4.45" />
* <Key key="positionCurrent_ZAxis" permissions="Write, Save" value="-9,62.45" />
* </pre>
* <p>
* From 5.2 onwards, they are @{code <PVStateValue>} elements:
* </p>
*
* <pre>
* <PVStateValue key="linesPerFrame" value="186" />
* <PVStateValue key="pmtGain">
* <IndexedValue index="0" value="605" description="Ch1 High Voltage" />
* <IndexedValue index="1" value="604" description="Ch2 High Voltage" />
* <IndexedValue index="2" value="0" description="Ch3 High Voltage" />
* </PVStateValue>
* <PVStateValue key="positionCurrent">
* <SubindexedValues index="XAxis">
* <SubindexedValue subindex="0" value="0.95" />
* </SubindexedValues>
* <SubindexedValues index="YAxis">
* <SubindexedValue subindex="0" value="-4.45" />
* </SubindexedValues>
* <SubindexedValues index="ZAxis">
* <SubindexedValue subindex="0" value="-9" description="Focus" />
* <SubindexedValue subindex="1" value="62.45" description="Piezo" />
* </SubindexedValues>
* </PVStateValue>
* </pre>
*/
public static interface Value {
boolean isTable();
Value get(Object key);
Value get(int index);
String value();
String description();
}
/**
* A leaf value with an actual {@link #value()} as well as an optional
* {@link #description()}.
*/