-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspe3.jl
252 lines (226 loc) · 7.9 KB
/
spe3.jl
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
using LightXML
const XML_FOOTER_OFFSET = 678
struct Version3 <: SPEVersion
xml::XMLDocument
frameexposurestarted::Union{Missing,Vector{Int64}}
frameexposureended::Union{Missing,Vector{Int64}}
end
function SPEFile{Version3, T}(io::IO, version) where {T}
footer_position = let
seek(io, XML_FOOTER_OFFSET)
read(io, UInt64)
end
xml = let
seek(io, footer_position)
read(io, String) |> parse_string
end
dataformat = find_element(root(xml), "DataFormat")
datablockframe = find_element(dataformat, "DataBlock")
numberofframes = parse(Int, attribute(datablockframe, "count"))
datablocksregionofinterest = [(;
width=parse(Int, attribute(datablock, "width")),
height=parse(Int, attribute(datablock, "height")),
size=parse(Int, attribute(datablock, "size")),
stride=parse(Int, attribute(datablock, "stride"))
) for datablock ∈ child_elements(datablockframe)]
metaformat = find_element(root(xml), "MetaFormat")
# LightField only supports metadata per frame, so we should always have only
# one MetaBlock child.
metablock = if isnothing(metaformat)
nothing
else
find_element(metaformat, "MetaBlock")
end
metadataorder = []
exposurestartedpresent = false
exposureendedpresent = false
# Deciding on a course of action depending on the type of metadata.
if !isnothing(metablock)
for metadatatype in child_elements(metablock)
if name(metadatatype) != "TimeStamp"
@warn "Your SPE file contains metadata that are not supported by SPEFiles.jl at this time. You may want to open an issue on GitHub." name(metadatatype)
# In the spec all data types are 8 bytes long.
push!(metadataorder, (type="skip", size=8))
continue
end
event = attribute(metadatatype, "event")
push!(metadataorder, (type=event, size=8))
if event == "ExposureStarted"
exposurestartedpresent = true
elseif event == "ExposureEnded"
exposureendedpresent = true
else
@warn "Unsupported timestamp event type, it will be ignored." event
end
end
end
if exposurestartedpresent
exposurestarted = zeros(Int64, numberofframes)
else
exposurestarted = missing
end
if exposureendedpresent
exposureended = zeros(Int64, numberofframes)
else
exposureended = missing
end
# Reading binary data
seek(io, HEADER_LENGTH)
frames = SPEFrame{T}[]
for i in 1:numberofframes
frame = SPEFrame{T}()
for roi ∈ datablocksregionofinterest
data = zeros(T, roi.width, roi.height)
read!(io, data)
push!(frame, permutedims(data, (2,1)))
end
push!(frames, frame)
for metadata in metadataorder
# Note: I'm assuming timestamps are always integers here.
if metadata.type == "ExposureStarted"
exposurestarted[i] = read(io, Int64)
elseif metadata.type == "ExposureEnded"
exposureended[i] = read(io, Int64)
else
skip(io, metadata.size)
end
end
end
metadata = Version3(xml, exposurestarted, exposureended)
SPEFile{Version3, T}(frames, version, metadata)
end
xml(f::SPEFile{Version3, T}) where {T} = f.metadata.xml
function Base.size(f::SPEFile{Version3, T}) where {T}
dataformat = find_element(root(xml(f)), "DataFormat")
datablockframe = find_element(dataformat, "DataBlock")
numberofframes = parse(Int, attribute(datablockframe, "count"))
(numberofframes, length(child_elements(datablockframe)))
end
function Base.length(f::SPEFile{Version3, T}) where {T}
length(f.frames) .* sum(length.(first(f.frames)))
end
"""
generalinformation(f::SPEFile{Version3, T})
Return the `GeneralIntormation` tag of the file.
"""
function generalinformation end
generalinformation(f::SPEFile{Version3, T}) where {T} = generalinformation(xml(f))
function generalinformation(xml::XMLDocument)
r = root(xml)
e = find_element(r, "GeneralInformation")
if isnothing(e) missing else e end
end
"""
notes(f::SPEFile{Version3, T})
Return the notes in the file, if they exist.
"""
function notes end
notes(f::SPEFile{Version3, T}) where {T} = notes(xml(f))
function notes(xml::XMLDocument)
infos = generalinformation(xml)
if ismissing(infos)
return missing
end
notes = find_element(infos, "Notes")
if isnothing(notes)
missing
else
content(notes)
end
end
"""
history(f::SPEFile{Version3, T})
Return the history node of the object if it exists.
"""
function history end
history(f::SPEFile{Version3, T}) where {T} = history(xml(f))
function history(xml::XMLDocument)
r = root(xml)
datahistories = find_element(r, "DataHistories")
if isnothing(datahistories)
return missing
end
find_element(datahistories, "DataHistory")
end
"""
origin(f::SPEFile{Version3, T})
Return the origin node of the object if it exists.
"""
function origin end
origin(f::SPEFile{Version3, T}) where {T} = origin(xml(f))
function origin(xml::XMLDocument)
datahistory = history(xml)
if ismissing(datahistory)
return missing
end
find_element(datahistory, "Origin")
end
"""
origin_summary(f::SPEFile{Version3, T})
Return a dict with origin metadata for the file. Keys are "software" "creator" "softwareVersion" "softwareCompany" "created"
"""
origin_summary(f::SPEFile{Version3, T}) where {T} = attributes_dict(origin(f))
"""
experiment(f::SPEFile{Version3, T})
Returns the xml object corresponding to the experiment used to take data. Refer to the SPE file specification for further information on the structure.
"""
function experiment end
experiment(f::SPEFile{Version3, T}) where {T} = experiment(xml(f))
function experiment(xml::XMLDocument)
orig = origin(xml)
if ismissing(orig)
return missing
end
find_element(orig, "Experiment")
end
"""
devices(f::SPEFile{Version3, T})
Returns the xml object corresponding to the devices used in the experiment. Refer to the SPE file specification for further information on the structure.
"""
function devices end
devices(f::SPEFile{Version3, T}) where {T} = devices(xml(f))
function devices(xml::XMLDocument)
expe = experiment(xml)
if ismissing(expe)
return missing
end
find_element(expe, "Devices")
end
exposure(f::SPEFile{Version3, T}) where {T} = exposure(xml(f))
function exposure(xml::XMLDocument)
dev = devices(xml)
if ismissing(dev)
return missing
end
cameras = find_element(dev, "Cameras")
if parse(Int, attribute(cameras, "count")) > 1
error("Exposure is not implemented for multiple cameras.")
end
camera = find_element(cameras, "Camera")
shuttertiming = find_element(camera, "ShutterTiming")
exposuretime = find_element(shuttertiming, "ExposureTime")
parse(Float64, content(exposuretime))
end
wavelength(f::SPEFile{Version3, T}) where {T} = wavelength(xml(f))
function wavelength(xml::XMLDocument)
r = root(xml)
calibrations = find_element(r, "Calibrations")
if isnothing(calibrations)
return missing
end
wavelengthmapping = find_element(calibrations, "WavelengthMapping")
wavelengthnode = find_element(wavelengthmapping, "Wavelength")
if isnothing(wavelengthnode)
@debug "Wavelength node not found. Falling back to WavelengthError."
wavelengthnode = find_element(wavelengthmapping, "WavelengthError")
if isnothing(wavelengthnode)
error("No wavelength found.")
end
λraw = content(wavelengthnode)
parse.(Float64, map(x->first(split(x, ',')), split(λraw, ' ')))
else
λraw = content(wavelengthnode)
parse.(Float64, split(λraw, ','))
end
end
export experiment, devices, origin_summary, notes