-
Notifications
You must be signed in to change notification settings - Fork 29
/
LFUtilDecodeLytroFolder.m
365 lines (322 loc) · 16.6 KB
/
LFUtilDecodeLytroFolder.m
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
% LFUtilDecodeLytroFolder - decode and optionally colour correct and rectify Lytro light fields
%
% Usage:
%
% LFUtilDecodeLytroFolder
% LFUtilDecodeLytroFolder( InputPath )
% LFUtilDecodeLytroFolder( InputPath, FileOptions, DecodeOptions, RectOptions )
% LFUtilDecodeLytroFolder( InputPath, [], [], RectOptions )
%
% All parameters are optional and take on default values as set in the "Defaults" section at the top
% of the implementation. As such, this can be called as a function or run directly by editing the
% code. When calling as a function, pass an empty array "[]" to omit a parameter.
%
% As released, the default values are set up to match the naming conventions of LFP Reader v2.0.0.
%
% This function demonstrates decoding and optionally colour-correction and rectification of 2D
% lenslet images into 4D light fields. It recursively crawls through a prescribed folder and its
% subfolders, operating on each light field. It can be used incrementally: previously-decoded light
% fields can be subsequently colour-corected, rectified, or both. Previously-completed tasks will
% not be re-applied. A filename pattern can be provided to work on individual files. All paths and
% naming are configurable.
%
% Decoding and rectification follow the process described in:
%
% [1] D. G. Dansereau, O. Pizarro, and S. B. Williams, "Decoding, calibration and rectification for
% lenslet-based plenoptic cameras," in Computer Vision and Pattern Recognition (CVPR), IEEE
% Conference on. IEEE, Jun 2013.
%
% Decoding requires that an appropriate database of white images be created using
% LFUtilProcessWhiteImages. Rectification similarly requires a calibration database be created using
% LFUtilProcessCalibrations.
%
% To decode a single light field, it is simplest to include a file specification in InputPath (see
% below). It is also possible to call LFLytroDecodeImage directly.
%
% Colour correction employs the metadata associated with each Lytro picture. It also applies
% histogram-based contrast adjustment. It calls the functions LFColourCorrect and LFHistEqualize.
%
% Rectification employs a calibration info file to rectify the light field, correcting for lens
% distortion, making pixels square, and yielding an intrinsics matrix which allows easy conversion
% from a pixel index [i,j,k,l] to a ray [s,t,u,v]. A calibration info file is generated by
% processing a series of checkeboard images, following the calibration procedure described in
% LFToolbox.pdf. A calibration only applies to one specific camera at one specific zoom and focus
% setting, and decoded using one specific lenslet grid model. The tool LFUtilProcessCalibrations is
% used to build a database of rectifications, and LFSelectFromDatabase isused to select a
% calibration appropriate to a given light field.
%
% This function was written to deal with Lytro imagery, but adapting it to operate with other
% lenslet-based cameras should be straightforward. For more information on the decoding process,
% refer to LFDecodeLensletImageDirect, [1], and LFToolbox.pdf.
%
% Some optional parameters are not used or documented at this level -- see each of LFCalRectifyLF,
% LFLytroDecodeImage, LFDecodeLensletImageDirect, and LFColourCorrect for further information.
%
%
% Inputs -- all are optional, see code below for default values :
%
% InputPath : Path to folder containing light fields, or to a specific light field, optionally including one or
% more wildcard filename specifications. In case wildcards are used, this searches sub-folders recursively. See
% LFFindFilesRecursive.m for more information and examples of how InputPath is interpreted.
%
% FileOptions : struct controlling file naming and saving
% .SaveResult : Set to false to perform a "dry run"
% .ForceRedo : If true previous results are ignored and decoding starts from scratch
% .SaveFnamePattern : String defining the pattern used in generating the output filename;
% sprintf is used to complete this pattern, such that %s gets replaced
% with the base name of the input light field
% .ThumbFnamePattern : As with SaveFnamePattern, defines the name of the output thumbnail
% image
%
% DecodeOptions : struct controlling the decoding process, see LFDecodeLensletImageDirect for more info
% .OptionalTasks : Cell array containing any combination of 'ColourCorrect' and
% 'Rectify'; an empty array "{}" means no additional tasks are
% requested; case sensitive
% .LensletImageFnamePattern : Pattern used to locate input files -- the pattern %s stands in
% for the base filename
% .ColourHistThresh : Threshold used by LFHistEqualize in optional colour correction
% .WhiteImageDatabasePath : Full path to the white images database, as created by
% LFUtilProcessWhiteImages
% .DoDehex : Controls whether hexagonal sampling is converted to rectangular, default true
% .DoSquareST : Controls whether s,t dimensions are resampled to square pixels, default true
% .ResampMethod : 'fast'(default) or 'triangulation'
% .LevelLimits : a two-element vector defining the black and white levels
% .Precision : 'single'(default) or 'double'
%
% RectOptions : struct controlling the optional rectification process
% .CalibrationDatabaseFname : Full path to the calibration file database, as created by
% LFUtilProcessCalibrations;
%
% Example:
%
% LFUtilDecodeLytroFolder
%
% Run from the top level of the 'Samples' folder will decode all the light fields in all the
% sub-folders, with default settings as set up in the opening section of the code. The
% calibration database created by LFUtilProcessWhiteImages is expected to be in
% 'Cameras/CaliCalibrationDatabase.mat' by default.
%
% LFUtilDecodeLytroFolder('Images', [], struct('OptionalTasks', 'ColourCorrect'))
%
% Run from the top level of the 'Samples' folder will decode and colour correct all light fields in the Images
% folder and its sub-folders.
%
% DecodeOptions.OptionalTasks = {'ColourCorrect', 'Rectify'};
% LFUtilDecodeLytroFolder([], [], DecodeOptions)
%
% Will perform both colour correction and rectification in the Images folder.
%
% LFUtilDecodeLytroFolder('Images/Illum/Lorikeet.lfp')
% LFUtilDecodeLytroFolder('Lorikeet.lfp')
% LFUtilDecodeLytroFolder({'Images', '*Hiding*', 'Jacaranda*'})
% LFUtilDecodeLytroFolder('*.raw')
% LFUtilDecodeLytroFolder({'*0002*', '*0003*'})
%
% Any of these, run from the top level of the 'Samples' folder, will decode the matching files. See
% LFFindFilesRecursive.
%
% User guide: <a href="matlab:which LFToolbox.pdf; open('LFToolbox.pdf')">LFToolbox.pdf</a>
% See also: LFUtilExtractLFPThumbs, LFUtilProcessWhiteImages, LFUtilProcessCalibrations, LFUtilCalLensletCam,
% LFColourCorrect, LFHistEqualize, LFFindFilesRecursive, LFLytroDecodeImage, LFDecodeLensletImageDirect,
% LFSelectFromDatabase
% Copyright (c) 2013-2020 Donald G. Dansereau
function LFUtilDecodeLytroFolder( InputPath, FileOptions, DecodeOptions, RectOptions )
%---Defaults---
InputPath = LFDefaultVal( 'InputPath', 'Images' );
FileOptions = LFDefaultField('FileOptions', 'SaveResult', true);
FileOptions = LFDefaultField('FileOptions', 'ForceRedo', false);
FileOptions = LFDefaultField('FileOptions', 'SaveFnamePattern', '%s__Decoded.mat');
FileOptions = LFDefaultField('FileOptions', 'ThumbFnamePattern', '%s__Decoded_Thumb.png');
DecodeOptions = LFDefaultField('DecodeOptions', 'OptionalTasks', {}); % 'ColourCorrect', 'Rectify'
DecodeOptions = LFDefaultField('DecodeOptions', 'ColourHistThresh', 0.01);
DecodeOptions = LFDefaultField(...
'DecodeOptions', 'WhiteImageDatabasePath', fullfile('Cameras','WhiteImageDatabase.mat'));
RectOptions = LFDefaultField(...
'RectOptions', 'CalibrationDatabaseFname', fullfile('Cameras','CalibrationDatabase.mat'));
% Used to decide if two lenslet grid models are "close enough"... if they're not a warning is raised
RectOptions = LFDefaultField( 'RectOptions', 'MaxGridModelDiff', 1e-5 );
% Massage a single-element OptionalTasks list to behave as a cell array
while( ~iscell(DecodeOptions.OptionalTasks) )
DecodeOptions.OptionalTasks = {DecodeOptions.OptionalTasks};
end
%---Crawl folder structure locating raw lenslet images---
DefaultFileSpec = {'*.lfr', '*.lfp', '*.LFR', '*.raw'}; % gets overriden below, if a file spec is provided
DefaultPath = 'Images';
[FileList, BasePath] = LFFindFilesRecursive( InputPath, DefaultFileSpec, DefaultPath );
fprintf('Found :\n');
disp(FileList)
%---Process each raw lenslet file---
% Store options so we can reset them for each file
OrigDecodeOptions = DecodeOptions;
OrigRectOptions = RectOptions;
for( iFile = 1:length(FileList) )
SaveRequired = false;
%---Start from orig options, avoids values bleeding between iterations---
DecodeOptions = OrigDecodeOptions;
RectOptions = OrigRectOptions;
%---Find current / base filename---
CurFname = FileList{iFile};
CurFname = fullfile(BasePath, CurFname);
% Build filename base without extension, auto-remove '__frame' for legacy .raw format
LFFnameBase = CurFname;
[~,~,Extension] = fileparts(LFFnameBase);
LFFnameBase = LFFnameBase(1:end-length(Extension));
CullIdx = strfind(LFFnameBase, '__frame');
if( ~isempty(CullIdx) )
LFFnameBase = LFFnameBase(1:CullIdx-1);
end
fprintf('\n---%s [%d / %d]...\n', CurFname, iFile, length(FileList));
%---Decode---
fprintf('Decoding...\n');
% First check if a decoded file already exists
[SDecoded, FileExists, CompletedTasks, TasksRemaining, SaveFname] = CheckIfExists( ...
LFFnameBase, DecodeOptions, FileOptions.SaveFnamePattern, FileOptions.ForceRedo );
if( ~FileExists )
% No previous result, decode
[LF, LFMetadata, WhiteImageMetadata, LensletGridModel, DecodeOptions] = ...
LFLytroDecodeImage( CurFname, DecodeOptions );
if( isempty(LF) )
continue;
end
fprintf('Decode complete\n');
SaveRequired = true;
elseif( isempty(TasksRemaining) )
% File exists, and nothing more to do
continue;
else
% File exists and tasks remain: unpack previous decoding results
[LF, LFMetadata, WhiteImageMetadata, LensletGridModel, DecodeOptions] = LFStruct2Var( ...
SDecoded, 'LF', 'LFMetadata', 'WhiteImageMetadata', 'LensletGridModel', 'DecodeOptions' );
clear SDecoded
end
%---Display thumbnail---
Thumb = DispThumb(LF, CurFname, CompletedTasks);
%---Optionally colour correct---
if( ismember( 'ColourCorrect', TasksRemaining ) )
LF = ColourCorrect( LF, LFMetadata, DecodeOptions );
CompletedTasks = [CompletedTasks, 'ColourCorrect'];
SaveRequired = true;
fprintf('Done\n');
%---Display thumbnail---
Thumb = DispThumb(LF, CurFname, CompletedTasks);
end
%---Optionally rectify---
if( ismember( 'Rectify', TasksRemaining ) )
[LF, RectOptions, Success] = Rectify( LF, LFMetadata, DecodeOptions, RectOptions, LensletGridModel );
if( Success )
CompletedTasks = [CompletedTasks, 'Rectify'];
SaveRequired = true;
end
%---Display thumbnail---
Thumb = DispThumb(LF, CurFname, CompletedTasks);
end
%---Check that all tasks are completed---
UncompletedTaskIdx = find(~ismember(TasksRemaining, CompletedTasks));
if( ~isempty(UncompletedTaskIdx) )
UncompletedTasks = [];
for( i=UncompletedTaskIdx )
UncompletedTasks = [UncompletedTasks, ' ', TasksRemaining{UncompletedTaskIdx}];
end
warning(['Could not complete all tasks requested in DecodeOptions.OptionalTasks: ', UncompletedTasks]);
end
DecodeOptions.OptionalTasks = CompletedTasks;
%---Optionally save---
if( SaveRequired && FileOptions.SaveResult )
if( isfloat(LF) )
LF = uint16( LF .* double(intmax('uint16')) );
end
ThumbFname = sprintf(FileOptions.ThumbFnamePattern, LFFnameBase);
fprintf('Saving to:\n\t%s,\n\t%s...\n', SaveFname, ThumbFname);
TimeStamp = datestr(now,'ddmmmyyyy_HHMMSS');
GeneratedByInfo = struct('mfilename', mfilename, 'time', TimeStamp, 'VersionStr', LFToolboxVersion);
save('-v7.3', SaveFname, 'GeneratedByInfo', 'LF', 'LFMetadata', 'WhiteImageMetadata', 'LensletGridModel', 'DecodeOptions', 'RectOptions');
imwrite(Thumb, ThumbFname);
end
end
end
%---------------------------------------------------------------------------------------------------
function [SDecoded, FileExists, CompletedTasks, TasksRemaining, SaveFname] = ...
CheckIfExists( LFFnameBase, DecodeOptions, SaveFnamePattern, ForceRedo )
SDecoded = [];
FileExists = false;
SaveFname = sprintf(SaveFnamePattern, LFFnameBase);
if( ~ForceRedo && exist(SaveFname, 'file') )
%---Task previously completed, check if there's more to do---
FileExists = true;
fprintf( ' %s already exists\n', SaveFname );
PrevDecodeOptions = load( SaveFname, 'DecodeOptions' );
PrevOptionalTasks = PrevDecodeOptions.DecodeOptions.OptionalTasks;
CompletedTasks = PrevOptionalTasks;
TasksRemaining = find(~ismember(DecodeOptions.OptionalTasks, PrevOptionalTasks));
if( ~isempty(TasksRemaining) )
%---Additional tasks remain---
TasksRemaining = {DecodeOptions.OptionalTasks{TasksRemaining}}; % by name
fprintf(' Additional tasks remain, loading existing file...\n');
SDecoded = load( SaveFname );
AllTasks = [SDecoded.DecodeOptions.OptionalTasks, TasksRemaining];
SDecoded.DecodeOptions.OptionalTasks = AllTasks;
%---Convert to float as this is what subsequent operations require---
OrigClass = class(SDecoded.LF);
SDecoded.LF = cast( SDecoded.LF, SDecoded.DecodeOptions.Precision ) ./ ...
cast( intmax(OrigClass), SDecoded.DecodeOptions.Precision );
fprintf('Done\n');
else
%---No further tasks... move on---
fprintf( ' No further tasks requested\n');
TasksRemaining = {};
end
else
%---File doesn't exist, all tasks remain---
TasksRemaining = DecodeOptions.OptionalTasks;
CompletedTasks = {};
end
end
%---------------------------------------------------------------------------------------------------
function Thumb = DispThumb( LF, CurFname, CompletedTasks)
Thumb = squeeze(LF(round(end/2),round(end/2),:,:,:)); % including weight channel for hist equalize
Thumb = uint8(LFHistEqualize(Thumb).*double(intmax('uint8')));
Thumb = Thumb(:,:,1:3); % strip off weight channel
LFDispSetup(Thumb);
Title = CurFname;
for( i=1:length(CompletedTasks) )
Title = [Title, ', ', CompletedTasks{i}];
end
title(Title, 'Interpreter', 'none');
drawnow
end
%---------------------------------------------------------------------------------------------------
function LF = ColourCorrect( LF, LFMetadata, DecodeOptions )
fprintf('Applying colour correction... ');
%---Weight channel is not used by colour correction, so strip it out---
LFWeight = LF(:,:,:,:,4);
LF = LF(:,:,:,:,1:3);
%---Apply the color conversion and saturate---
LF = LFColourCorrect( LF, DecodeOptions.ColourMatrix, DecodeOptions.ColourBalance, DecodeOptions.Gamma );
%---Put the weight channel back---
LF(:,:,:,:,4) = LFWeight;
end
%---------------------------------------------------------------------------------------------------
function [LF, RectOptions, Success] = Rectify( LF, LFMetadata, DecodeOptions, RectOptions, LensletGridModel )
Success = false;
fprintf('Applying rectification... ');
%---Load cal info---
fprintf('Selecting calibration...\n');
[CalInfo, RectOptions] = LFFindCalInfo( LFMetadata, RectOptions );
if( isempty( CalInfo ) )
warning('No suitable calibration found, skipping');
return;
end
%---Compare structs
a = CalInfo.LensletGridModel;
b = LensletGridModel;
a.Orientation = strcmp(a.Orientation, 'horz');
b.Orientation = strcmp(b.Orientation, 'horz');
FractionalDiff = abs( (struct2array(a) - struct2array(b)) ./ struct2array(a) );
if( ~all( FractionalDiff < RectOptions.MaxGridModelDiff ) )
warning(['Lenslet grid models differ -- ideally the same grid model and white image are ' ...
' used to decode during calibration and rectification']);
end
%---Perform rectification---
[LF, RectOptions] = LFCalRectifyLF( LF, CalInfo, RectOptions );
Success = true;
end