File Coverage

blib/lib/Image/ExifTool/MRC.pm
Criterion Covered Total %
statement 34 40 85.0
branch 9 16 56.2
condition 2 6 33.3
subroutine 4 4 100.0
pod 0 1 0.0
total 49 67 73.1


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: MRC.pm
3             #
4             # Description: Read MRC (Medical Research Council) image files
5             #
6             # Revisions: 2021-04-21 - P. Harvey Created
7             #
8             # References: 1) https://www.ccpem.ac.uk/mrc_format/mrc2014.php
9             # 2) http://legacy.ccp4.ac.uk/html/library.html
10             # 3) https://github.com/ccpem/mrcfile/blob/master/mrcfile/dtypes.py
11             #
12             # Notes: The header is basically identical to the older CCP4 file format
13             #------------------------------------------------------------------------------
14              
15             package Image::ExifTool::MRC;
16              
17 1     1   4473 use strict;
  1         3  
  1         36  
18 1     1   6 use vars qw($VERSION);
  1         2  
  1         42  
19 1     1   6 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         1693  
20              
21             $VERSION = '1.00';
22              
23             my %bool = (
24             Format => 'int8u',
25             PrintConv => { 0 => 'No', 1 => 'Yes' }
26             );
27              
28             %Image::ExifTool::MRC::Main = (
29             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
30             GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
31             VARS => { NO_LOOKUP => 1 }, # omit tags from lookup
32             FORMAT => 'int32u',
33             NOTES => q{
34             Tags extracted from Medical Research Council (MRC) format imaging files.
35             See L for the specification.
36             },
37             0 => 'ImageWidth',
38             1 => 'ImageHeight',
39             2 => {
40             Name => 'ImageDepth',
41             Notes => q{
42             number of sections. Use ExtractEmbedded option to extract metadata for all
43             sections
44             },
45             RawConv => '$$self{ImageDepth} = $val',
46             },
47             3 => {
48             Name => 'ImageMode',
49             PrintConv => {
50             0 => '8-bit signed integer',
51             1 => '16-bit signed integer',
52             2 => '32-bit signed real',
53             3 => 'complex 16-bit integer',
54             4 => 'complex 32-bit real',
55             6 => '16-bit unsigned integer',
56             },
57             },
58             4 => { Name => 'StartPoint', Format => 'int32u[3]' },
59             7 => { Name => 'GridSize', Format => 'int32u[3]' },
60             10 => { Name => 'CellWidth', Format => 'float', Notes => 'cell size in angstroms' },
61             11 => { Name => 'CellHeight',Format => 'float' },
62             12 => { Name => 'CellDepth', Format => 'float' },
63             13 => { Name => 'CellAlpha', Format => 'float' },
64             14 => { Name => 'CellBeta', Format => 'float' },
65             15 => { Name => 'CellGamma', Format => 'float' },
66             16 => { Name => 'ImageWidthAxis', PrintConv => { 1 => 'X', 2 => 'Y', 3 => 'Z' } },
67             17 => { Name => 'ImageHeightAxis', PrintConv => { 1 => 'X', 2 => 'Y', 3 => 'Z' } },
68             18 => { Name => 'ImageDepthAxis', PrintConv => { 1 => 'X', 2 => 'Y', 3 => 'Z' } },
69             19 => { Name => 'DensityMin', Format => 'float' },
70             20 => { Name => 'DensityMax', Format => 'float' },
71             21 => { Name => 'DensityMean',Format => 'float' },
72             22 => 'SpaceGroupNumber',
73             23 => { Name => 'ExtendedHeaderSize', RawConv => '$$self{ExtendedHeaderSize} = $val' },
74             26 => { Name => 'ExtendedHeaderType', Format => 'string[4]', RawConv => '$$self{ExtendedHeaderType} = $val' },
75             27 => 'MRCVersion',
76             49 => { Name => 'Origin', Format => 'float[3]' },
77             53 => { Name => 'MachineStamp', Format => 'int8u[4]', PrintConv => 'sprintf("0x%.2x 0x%.2x 0x%.2x 0x%.2x",split " ", $val)' },
78             54 => { Name => 'RMSDeviation', Format => 'float' },
79             55 => { Name => 'NumberOfLabels', RawConv => '$$self{NLab} = $val' },
80             56 => { Name => 'Label0', Format => 'string[80]', Condition => '$$self{NLab} > 0' },
81             76 => { Name => 'Label1', Format => 'string[80]', Condition => '$$self{NLab} > 1' },
82             96 => { Name => 'Label2', Format => 'string[80]', Condition => '$$self{NLab} > 2' },
83             116 => { Name => 'Label3', Format => 'string[80]', Condition => '$$self{NLab} > 3' },
84             136 => { Name => 'Label4', Format => 'string[80]', Condition => '$$self{NLab} > 4' },
85             156 => { Name => 'Label5', Format => 'string[80]', Condition => '$$self{NLab} > 5' },
86             176 => { Name => 'Label6', Format => 'string[80]', Condition => '$$self{NLab} > 6' },
87             196 => { Name => 'Label7', Format => 'string[80]', Condition => '$$self{NLab} > 7' },
88             216 => { Name => 'Label8', Format => 'string[80]', Condition => '$$self{NLab} > 8' },
89             236 => { Name => 'Label9', Format => 'string[80]', Condition => '$$self{NLab} > 9' },
90             );
91              
92             %Image::ExifTool::MRC::FEI12 = (
93             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
94             GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
95             VARS => { NO_LOOKUP => 1 }, # omit tags from lookup (way too many!)
96             NOTES => 'Tags extracted from FEI1 and FEI2 extended headers.',
97             0 => { Name => 'MetadataSize', Format => 'int32u', RawConv => '$$self{MetadataSize} = $val' },
98             4 => { Name => 'MetadataVersion', Format => 'int32u' },
99             8 => {
100             Name => 'Bitmask1',
101             Format => 'int32u',
102             RawConv => '$$self{BitM} = $val',
103             PrintConv => 'sprintf("0x%.8x", $val)',
104             },
105             12 => {
106             Name => 'TimeStamp',
107             Format => 'double',
108             Condition => '$$self{BitM} & 0x01',
109             Groups => { 2 => 'Time'},
110             # shift from days since Dec 30, 1899 to Unix epoch of Jan 1, 1970
111             # (my sample looks like local time, although it should be UTC)
112             ValueConv => 'ConvertUnixTime(($val-25569)*24*3600)',
113             PrintConv => '$self->ConvertDateTime($val)',
114             },
115             20 => { Name => 'MicroscopeType', Format => 'string[16]', Condition => '$$self{BitM} & 0x02' },
116             36 => { Name => 'MicroscopeID', Format => 'string[16]', Condition => '$$self{BitM} & 0x04' },
117             52 => { Name => 'Application', Format => 'string[16]', Condition => '$$self{BitM} & 0x08' },
118             68 => { Name => 'AppVersion', Format => 'string[16]', Condition => '$$self{BitM} & 0x10' },
119             84 => { Name => 'HighTension', Format => 'double', Condition => '$$self{BitM} & 0x20', Notes => 'volts' },
120             92 => { Name => 'Dose', Format => 'double', Condition => '$$self{BitM} & 0x40', Notes => 'electrons/m2' },
121             100 => { Name => 'AlphaTilt', Format => 'double', Condition => '$$self{BitM} & 0x80' },
122             108 => { Name => 'BetaTilt', Format => 'double', Condition => '$$self{BitM} & 0x100' },
123             116 => { Name => 'XStage', Format => 'double', Condition => '$$self{BitM} & 0x200' },
124             124 => { Name => 'YStage', Format => 'double', Condition => '$$self{BitM} & 0x400' },
125             132 => { Name => 'ZStage', Format => 'double', Condition => '$$self{BitM} & 0x800' },
126             140 => { Name => 'TiltAxisAngle', Format => 'double', Condition => '$$self{BitM} & 0x1000' },
127             148 => { Name => 'DualAxisRot', Format => 'double', Condition => '$$self{BitM} & 0x2000' },
128             156 => { Name => 'PixelSizeX', Format => 'double', Condition => '$$self{BitM} & 0x4000' },
129             164 => { Name => 'PixelSizeY', Format => 'double', Condition => '$$self{BitM} & 0x8000' },
130             220 => { Name => 'Defocus', Format => 'double', Condition => '$$self{BitM} & 0x400000' },
131             228 => { Name => 'STEMDefocus', Format => 'double', Condition => '$$self{BitM} & 0x800000' },
132             236 => { Name => 'AppliedDefocus', Format => 'double', Condition => '$$self{BitM} & 0x1000000' },
133             244 => { Name => 'InstrumentMode', Format => 'int32u', Condition => '$$self{BitM} & 0x2000000', PrintConv => { 1 => 'TEM', 2 => 'STEM' } },
134             248 => { Name => 'ProjectionMode', Format => 'int32u', Condition => '$$self{BitM} & 0x4000000', PrintConv => { 1 => 'Diffraction', 2 => 'Imaging' } },
135             252 => { Name => 'ObjectiveLens', Format => 'string[16]', Condition => '$$self{BitM} & 0x8000000' },
136             268 => { Name => 'HighMagnificationMode', Format => 'string[16]', Condition => '$$self{BitM} & 0x10000000' },
137             284 => { Name => 'ProbeMode', Format => 'int32u', Condition => '$$self{BitM} & 0x20000000', PrintConv => { 1 => 'Nano', 2 => 'Micro' } },
138             288 => { Name => 'EFTEMOn', %bool, Condition => '$$self{BitM} & 0x40000000' },
139             289 => { Name => 'Magnification', Format => 'double', Condition => '$$self{BitM} & 0x80000000' },
140             297 => {
141             Name => 'Bitmask2',
142             Format => 'int32u',
143             RawConv => '$$self{BitM} = $val',
144             PrintConv => 'sprintf("0x%.8x", $val)',
145             },
146             301 => { Name => 'CameraLength', Format => 'double', Condition => '$$self{BitM} & 0x01' },
147             309 => { Name => 'SpotIndex', Format => 'int32u', Condition => '$$self{BitM} & 0x02' },
148             313 => { Name => 'IlluminationArea',Format => 'double', Condition => '$$self{BitM} & 0x04' },
149             321 => { Name => 'Intensity', Format => 'double', Condition => '$$self{BitM} & 0x08' },
150             329 => { Name => 'ConvergenceAngle',Format => 'double', Condition => '$$self{BitM} & 0x10' },
151             337 => { Name => 'IlluminationMode',Format => 'string[16]', Condition => '$$self{BitM} & 0x20' },
152             353 => { Name => 'WideConvergenceAngleRange', %bool, Condition => '$$self{BitM} & 0x40' },
153             354 => { Name => 'SlitInserted', %bool, Condition => '$$self{BitM} & 0x80' },
154             355 => { Name => 'SlitWidth', Format => 'double', Condition => '$$self{BitM} & 0x100' },
155             363 => { Name => 'AccelVoltOffset', Format => 'double', Condition => '$$self{BitM} & 0x200' },
156             371 => { Name => 'DriftTubeVolt', Format => 'double', Condition => '$$self{BitM} & 0x400' },
157             379 => { Name => 'EnergyShift', Format => 'double', Condition => '$$self{BitM} & 0x800' },
158             387 => { Name => 'ShiftOffsetX', Format => 'double', Condition => '$$self{BitM} & 0x1000' },
159             395 => { Name => 'ShiftOffsetY', Format => 'double', Condition => '$$self{BitM} & 0x2000' },
160             403 => { Name => 'ShiftX', Format => 'double', Condition => '$$self{BitM} & 0x4000' },
161             411 => { Name => 'ShiftY', Format => 'double', Condition => '$$self{BitM} & 0x8000' },
162             419 => { Name => 'IntegrationTime', Format => 'double', Condition => '$$self{BitM} & 0x10000' },
163             427 => { Name => 'BinningWidth', Format => 'int32u', Condition => '$$self{BitM} & 0x20000' },
164             431 => { Name => 'BinningHeight', Format => 'int32u', Condition => '$$self{BitM} & 0x40000' },
165             435 => { Name => 'CameraName', Format => 'string[16]', Condition => '$$self{BitM} & 0x80000' },
166             451 => { Name => 'ReadoutAreaLeft', Format => 'int32u', Condition => '$$self{BitM} & 0x100000' },
167             455 => { Name => 'ReadoutAreaTop', Format => 'int32u', Condition => '$$self{BitM} & 0x200000' },
168             459 => { Name => 'ReadoutAreaRight',Format => 'int32u', Condition => '$$self{BitM} & 0x400000' },
169             463 => { Name => 'ReadoutAreaBottom',Format=> 'int32u', Condition => '$$self{BitM} & 0x800000' },
170             467 => { Name => 'CetaNoiseReduct', %bool, Condition => '$$self{BitM} & 0x1000000' },
171             468 => { Name => 'CetaFramesSummed',Format => 'int32u', Condition => '$$self{BitM} & 0x2000000' },
172             472 => { Name => 'DirectDetElectronCounting', %bool, Condition => '$$self{BitM} & 0x4000000' },
173             473 => { Name => 'DirectDetAlignFrames', %bool, Condition => '$$self{BitM} & 0x8000000' },
174             490 => {
175             Name => 'Bitmask3',
176             Format => 'int32u',
177             RawConv => '$$self{BitM} = $val',
178             PrintConv => 'sprintf("0x%.8x", $val)',
179             },
180             518 => { Name => 'PhasePlate', %bool, Condition => '$$self{BitM} & 0x40' },
181             519 => { Name => 'STEMDetectorName',Format => 'string[16]', Condition => '$$self{BitM} & 0x80' },
182             535 => { Name => 'Gain', Format => 'double', Condition => '$$self{BitM} & 0x100' },
183             543 => { Name => 'Offset', Format => 'double', Condition => '$$self{BitM} & 0x200' },
184             571 => { Name => 'DwellTime', Format => 'double', Condition => '$$self{BitM} & 0x8000' },
185             579 => { Name => 'FrameTime', Format => 'double', Condition => '$$self{BitM} & 0x10000' },
186             587 => { Name => 'ScanSizeLeft', Format => 'int32u', Condition => '$$self{BitM} & 0x20000' },
187             591 => { Name => 'ScanSizeTop', Format => 'int32u', Condition => '$$self{BitM} & 0x40000' },
188             595 => { Name => 'ScanSizeRight', Format => 'int32u', Condition => '$$self{BitM} & 0x80000' },
189             599 => { Name => 'ScanSizeBottom', Format => 'int32u', Condition => '$$self{BitM} & 0x100000' },
190             603 => { Name => 'FullScanFOV_X', Format => 'double', Condition => '$$self{BitM} & 0x200000' },
191             611 => { Name => 'FullScanFOV_Y', Format => 'double', Condition => '$$self{BitM} & 0x400000' },
192             619 => { Name => 'Element', Format => 'string[16]', Condition => '$$self{BitM} & 0x800000' },
193             635 => { Name => 'EnergyIntervalLower', Format => 'double', Condition => '$$self{BitM} & 0x1000000' },
194             643 => { Name => 'EnergyIntervalHigher',Format => 'double', Condition => '$$self{BitM} & 0x2000000' },
195             651 => { Name => 'Method', Format=> 'int32u', Condition => '$$self{BitM} & 0x4000000' },
196             655 => { Name => 'IsDoseFraction', %bool, Condition => '$$self{BitM} & 0x8000000' },
197             656 => { Name => 'FractionNumber', Format => 'int32u', Condition => '$$self{BitM} & 0x10000000' },
198             660 => { Name => 'StartFrame', Format => 'int32u', Condition => '$$self{BitM} & 0x20000000' },
199             664 => { Name => 'EndFrame', Format => 'int32u', Condition => '$$self{BitM} & 0x40000000' },
200             668 => { Name =>'InputStackFilename',Format=> 'string[80]', Condition => '$$self{BitM} & 0x80000000' },
201             748 => {
202             Name => 'Bitmask4',
203             Format => 'int32u',
204             RawConv => '$$self{BitM} = $val',
205             PrintConv => 'sprintf("0x%.8x", $val)',
206             },
207             752 => { Name => 'AlphaTiltMin', Format => 'double', Condition => '$$self{BitM} & 0x01' },
208             760 => { Name => 'AlphaTiltMax', Format => 'double', Condition => '$$self{BitM} & 0x02' },
209             #
210             # FEI2 header starts here
211             #
212             768 => { Name => 'ScanRotation', Format => 'double', Condition => '$$self{BitM} & 0x04' },
213             776 => { Name => 'DiffractionPatternRotation',Format=>'double', Condition => '$$self{BitM} & 0x08' },
214             784 => { Name => 'ImageRotation', Format => 'double', Condition => '$$self{BitM} & 0x10' },
215             792 => { Name => 'ScanModeEnumeration',Format => 'int32u', Condition => '$$self{BitM} & 0x20', PrintConv => { 0 => 'Other', 1 => 'Raster', 2 => 'Serpentine' } },
216             796 => {
217             Name => 'AcquisitionTimeStamp',
218             Format => 'int64u',
219             Condition => '$$self{BitM} & 0x40',
220             Groups => { 2 => 'Time' },
221             # microseconds since 1970 UTC
222             ValueConv => 'ConvertUnixTime($val / 1e6, 1, 6)',
223             PrintConv => '$self->ConvertDateTime($val)',
224             },
225             804 => { Name => 'DetectorCommercialName', Format => 'string[16]', Condition => '$$self{BitM} & 0x80' },
226             820 => { Name => 'StartTiltAngle', Format => 'double', Condition => '$$self{BitM} & 0x100' },
227             828 => { Name => 'EndTiltAngle', Format => 'double', Condition => '$$self{BitM} & 0x200' },
228             836 => { Name => 'TiltPerImage', Format => 'double', Condition => '$$self{BitM} & 0x400' },
229             844 => { Name => 'TitlSpeed', Format => 'double', Condition => '$$self{BitM} & 0x800' },
230             852 => { Name => 'BeamCenterX', Format => 'int32u', Condition => '$$self{BitM} & 0x1000' },
231             856 => { Name => 'BeamCenterY', Format => 'int32u', Condition => '$$self{BitM} & 0x2000' },
232             860 => {
233             Name => 'CFEGFlashTimeStamp',
234             Format => 'int64u',
235             Condition => '$$self{BitM} & 0x4000',
236             Groups => { 2 => 'Time' },
237             ValueConv => 'ConvertUnixTime($val / 1e6, 1, 6)',
238             PrintConv => '$self->ConvertDateTime($val)',
239             },
240             868 => { Name => 'PhasePlatePosition',Format => 'int32u', Condition => '$$self{BitM} & 0x8000' },
241             872 => { Name => 'ObjectiveAperture', Format=>'string[16]',Condition => '$$self{BitM} & 0x10000' },
242             );
243              
244             #------------------------------------------------------------------------------
245             # Extract metadata from a MRC image
246             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
247             # Returns: 1 on success, 0 if this wasn't a valid MRC file
248             sub ProcessMRC($$)
249             {
250 1     1 0 6 my ($et, $dirInfo) = @_;
251 1         3 my $raf = $$dirInfo{RAF};
252 1         2 my ($buff, $tagTablePtr, $i);
253              
254             # verify this is a valid MRC file
255 1 50       5 return 0 unless $raf->Read($buff, 1024) == 1024;
256             # validate axes, "MAP" file type and machine stamp
257 1 50       9 return 0 unless $buff =~ /^.{64}[\x01\x02\x03]\0\0\0[\x01\x02\x03]\0\0\0[\x01\x02\x03]\0\0\0.{132}MAP[\0 ](\x44\x44|\x44\x41|\x11\x11)\0\0/s;
258              
259 1         6 $et->SetFileType();
260 1         6 SetByteOrder('II');
261 1         5 my %dirInfo = (
262             DataPt => \$buff,
263             DirStart => 0,
264             DirLen => length($buff),
265             );
266 1         4 $tagTablePtr = GetTagTable('Image::ExifTool::MRC::Main');
267 1         6 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
268              
269             # (I don't have any samples with extended headers for testing, so these are not yet decoded)
270 1 50 33     9 if ($$et{ExtendedHeaderSize} and $$et{ExtendedHeaderType} =~ /^FEI[12]/) {
271 1 50 33     6 unless ($raf->Read($buff,4)==4 and $raf->Seek(-4,1)) { # read metadata size
272 0         0 $et->Warn('Error reading extended header');
273 0         0 return 1;
274             }
275 1         6 my $size = Get32u(\$buff, 0);
276 1 50       6 if ($size * $$et{ImageDepth} > $$et{ExtendedHeaderSize}) {
277 0         0 $et->Warn('Corrupted extended header');
278 0         0 return 1;
279             }
280 1         4 $dirInfo{DirLen} = $size;
281 1         3 $tagTablePtr = GetTagTable('Image::ExifTool::MRC::FEI12');
282 1         2 for ($i=0; ;) {
283 2         9 $dirInfo{DataPos} = $raf->Tell();
284 2 50       7 $raf->Read($buff, $size) == $size or $et->Warn("Error reading extended header $i"), last;
285 2         9 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
286 2 100       11 last if ++$i >= $$et{ImageDepth};
287 1 50       5 unless ($$et{OPTIONS}{ExtractEmbedded}) {
288 0         0 $et->Warn('Use the ExtractEmbedded option to read metadata for all frames',3);
289 0         0 last;
290             }
291 1         4 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
292             }
293 1         3 delete $$et{DOC_NUM};
294             }
295              
296 1         5 return 1;
297             }
298              
299             1; # end
300              
301             __END__