1 /*
2 * This file is part of RawTherapee.
3 *
4 * Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
5 *
6 * RawTherapee is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * RawTherapee is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with RawTherapee. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "ffmanager.h"
20 #include "../rtgui/options.h"
21 #include "rawimage.h"
22 #include "imagedata.h"
23 #include "median.h"
24 #include "utils.h"
25
26 namespace rtengine
27 {
28
29 extern const Settings* settings;
30
31 // *********************** class ffInfo **************************************
32
operator =(const ffInfo & o)33 inline ffInfo& ffInfo::operator =(const ffInfo &o)
34 {
35 if (this != &o) {
36 pathname = o.pathname;
37 maker = o.maker;
38 model = o.model;
39 lens = o.lens;
40 focallength = o.focallength;
41 timestamp = o.timestamp;
42 aperture = o.aperture;
43
44 if( ri ) {
45 delete ri;
46 ri = nullptr;
47 }
48 }
49
50 return *this;
51 }
52
operator <(const ffInfo & e2) const53 bool ffInfo::operator <(const ffInfo &e2) const
54 {
55 if( this->maker.compare( e2.maker) >= 0 ) {
56 return false;
57 }
58
59 if( this->model.compare( e2.model) >= 0 ) {
60 return false;
61 }
62
63 if( this->lens.compare( e2.lens) >= 0 ) {
64 return false;
65 }
66
67 if( this->focallength >= e2.focallength ) {
68 return false;
69 }
70
71 if( this->timestamp >= e2.timestamp ) {
72 return false;
73 }
74
75 return true;
76 }
77
key(const std::string & mak,const std::string & mod,const std::string & len,double focal,double apert)78 std::string ffInfo::key(const std::string &mak, const std::string &mod, const std::string &len, double focal, double apert )
79 {
80 std::ostringstream s;
81 s << mak << " " << mod << " ";
82 s.width(5);
83 s << len << " ";
84 s.precision( 2 );
85 s.width(4);
86 s << focal << "mm F" << apert;
87 return s.str();
88 }
89
distance(const std::string & mak,const std::string & mod,const std::string & len,double focallength,double aperture) const90 double ffInfo::distance(const std::string &mak, const std::string &mod, const std::string &len, double focallength, double aperture) const
91 {
92 if( this->maker.compare( mak) != 0 ) {
93 return INFINITY;
94 }
95
96 if( this->model.compare( mod) != 0 ) {
97 return INFINITY;
98 }
99
100 if( this->lens.compare( len) != 0 ) {
101 return INFINITY;
102 }
103
104 double dAperture = 2 * (log(this->aperture) - log(aperture)) / log(2); //more important for vignette
105 double dfocallength = (log(this->focallength / 100.) - log(focallength / 100.)) / log(2); //more important for PRNU
106
107 return sqrt( dfocallength * dfocallength + dAperture * dAperture);
108 }
109
getRawImage()110 RawImage* ffInfo::getRawImage()
111 {
112 if(ri) {
113 return ri;
114 }
115
116 updateRawImage();
117
118 return ri;
119 }
120
121 /* updateRawImage() load into ri the actual pixel data from pathname if there is a single shot
122 * otherwise load each file from the pathNames list and extract a template from the media;
123 * the first file is used also for reading all information other than pixels
124 */
updateRawImage()125 void ffInfo::updateRawImage()
126 {
127 typedef unsigned int acc_t;
128
129 // averaging of flatfields if more than one is found matching the same key.
130 // this may not be necessary, as flatfield is further blurred before being applied to the processed image.
131 if( !pathNames.empty() ) {
132 std::list<Glib::ustring>::iterator iName = pathNames.begin();
133 ri = new RawImage(*iName); // First file used also for extra pixels information (width, height, shutter, filters etc.. )
134 if( ri->loadRaw(true)) {
135 delete ri;
136 ri = nullptr;
137 } else {
138 int H = ri->get_height();
139 int W = ri->get_width();
140 ri->compress_image(0);
141 int rSize = W * ((ri->getSensorType() == ST_BAYER || ri->getSensorType() == ST_FUJI_XTRANS || ri->get_colors() == 1) ? 1 : 3);
142 acc_t **acc = new acc_t*[H];
143
144 for( int row = 0; row < H; row++) {
145 acc[row] = new acc_t[rSize ];
146 }
147
148 // copy first image into accumulators
149 for (int row = 0; row < H; row++)
150 for (int col = 0; col < rSize; col++) {
151 acc[row][col] = ri->data[row][col];
152 }
153
154 int nFiles = 1; // First file data already loaded
155
156 for( ++iName; iName != pathNames.end(); ++iName) {
157 RawImage* temp = new RawImage(*iName);
158
159 if( !temp->loadRaw(true)) {
160 temp->compress_image(0); //\ TODO would be better working on original, because is temporary
161 nFiles++;
162
163 if( ri->getSensorType() == ST_BAYER || ri->getSensorType() == ST_FUJI_XTRANS || ri->get_colors() == 1 ) {
164 for( int row = 0; row < H; row++) {
165 for( int col = 0; col < W; col++) {
166 acc[row][col] += temp->data[row][col];
167 }
168 }
169 } else {
170 for( int row = 0; row < H; row++) {
171 for( int col = 0; col < W; col++) {
172 acc[row][3 * col + 0] += temp->data[row][3 * col + 0];
173 acc[row][3 * col + 1] += temp->data[row][3 * col + 1];
174 acc[row][3 * col + 2] += temp->data[row][3 * col + 2];
175 }
176 }
177 }
178 }
179
180 delete temp;
181 }
182
183 for (int row = 0; row < H; row++) {
184 for (int col = 0; col < rSize; col++) {
185 ri->data[row][col] = acc[row][col] / nFiles;
186 }
187
188 delete [] acc[row];
189 }
190
191 delete [] acc;
192 }
193 } else {
194 ri = new RawImage(pathname);
195 if( ri->loadRaw(true)) {
196 delete ri;
197 ri = nullptr;
198 } else {
199 ri->compress_image(0);
200 }
201 }
202
203 if(ri) {
204 // apply median to avoid this step being executed each time a flat field gets applied
205 int H = ri->get_height();
206 int W = ri->get_width();
207 float *cfatmp = (float (*)) malloc (H * W * sizeof * cfatmp);
208
209 #ifdef _OPENMP
210 #pragma omp parallel for schedule(dynamic,16)
211 #endif
212
213 for (int i = 0; i < H; i++) {
214 int iprev = i < 2 ? i + 2 : i - 2;
215 int inext = i > H - 3 ? i - 2 : i + 2;
216
217 for (int j = 0; j < W; j++) {
218 int jprev = j < 2 ? j + 2 : j - 2;
219 int jnext = j > W - 3 ? j - 2 : j + 2;
220
221 cfatmp[i * W + j] = median(ri->data[iprev][j], ri->data[i][jprev], ri->data[i][j], ri->data[i][jnext], ri->data[inext][j]);
222 }
223 }
224
225 memcpy(ri->data[0], cfatmp, W * H * sizeof(float));
226
227 free (cfatmp);
228
229 }
230 }
231
232 // ************************* class FFManager *********************************
233
init(Glib::ustring pathname)234 void FFManager::init( Glib::ustring pathname )
235 {
236 std::vector<Glib::ustring> names;
237
238 auto dir = Gio::File::create_for_path (pathname);
239
240 if (!dir || !dir->query_exists()) {
241 return;
242 }
243
244 try {
245
246 auto enumerator = dir->enumerate_children ("standard::name");
247
248 while (auto file = enumerator->next_file ()) {
249 names.emplace_back (Glib::build_filename (pathname, file->get_name ()));
250 }
251
252 } catch (Glib::Exception&) {}
253
254 ffList.clear();
255
256 for (size_t i = 0; i < names.size(); i++) {
257 try {
258 addFileInfo(names[i]);
259 } catch( std::exception& e ) {}
260 }
261
262 // Where multiple shots exist for same group, move filename to list
263 for( ffList_t::iterator iter = ffList.begin(); iter != ffList.end(); ++iter ) {
264 ffInfo &i = iter->second;
265
266 if( !i.pathNames.empty() && !i.pathname.empty() ) {
267 i.pathNames.push_back( i.pathname );
268 i.pathname.clear();
269 }
270
271 if( settings->verbose ) {
272 if( !i.pathname.empty() ) {
273 printf( "%s: %s\n", i.key().c_str(), i.pathname.c_str());
274 } else {
275 printf( "%s: MEAN of \n ", i.key().c_str());
276
277 for( std::list<Glib::ustring>::iterator iter = i.pathNames.begin(); iter != i.pathNames.end(); ++iter ) {
278 printf( "%s, ", iter->c_str() );
279 }
280
281 printf("\n");
282 }
283 }
284 }
285
286 currentPath = pathname;
287 return;
288 }
289
addFileInfo(const Glib::ustring & filename,bool pool)290 ffInfo* FFManager::addFileInfo (const Glib::ustring& filename, bool pool)
291 {
292 auto ext = getFileExtension(filename);
293
294 if (ext.empty() || !options.is_extention_enabled(ext)) {
295 return nullptr;
296 }
297
298 auto file = Gio::File::create_for_path(filename);
299
300 if (!file ) {
301 return nullptr;
302 }
303
304 if (!file->query_exists()) {
305 return nullptr;
306 }
307
308 try {
309
310 auto info = file->query_info("standard::name,standard::type,standard::is-hidden");
311
312 if (!info || info->get_file_type() == Gio::FILE_TYPE_DIRECTORY) {
313 return nullptr;
314 }
315
316 if (!options.fbShowHidden && info->is_hidden()) {
317 return nullptr;
318 }
319
320 RawImage ri(filename);
321 int res = ri.loadRaw(false); // Read information about shot
322
323 if (res != 0) {
324 return nullptr;
325 }
326
327 ffList_t::iterator iter;
328
329 if(!pool) {
330 ffInfo n(filename, "", "", "", 0, 0, 0);
331 iter = ffList.emplace("", n);
332 return &(iter->second);
333 }
334
335 FramesData idata(filename);
336 /* Files are added in the map, divided by same maker/model,lens and aperture*/
337 std::string key(ffInfo::key(idata.getMake(), idata.getModel(), idata.getLens(), idata.getFocalLen(), idata.getFNumber()));
338 iter = ffList.find(key);
339
340 if(iter == ffList.end()) {
341 ffInfo n(filename, idata.getMake(), idata.getModel(), idata.getLens(), idata.getFocalLen(), idata.getFNumber(), idata.getDateTimeAsTS());
342 iter = ffList.emplace(key, n);
343 } else {
344 while(iter != ffList.end() && iter->second.key() == key && ABS(iter->second.timestamp - ri.get_timestamp()) > 60 * 60 * 6) { // 6 hour difference
345 ++iter;
346 }
347
348 if(iter != ffList.end()) {
349 iter->second.pathNames.push_back(filename);
350 } else {
351 ffInfo n(filename, idata.getMake(), idata.getModel(), idata.getLens(), idata.getFocalLen(), idata.getFNumber(), idata.getDateTimeAsTS());
352 iter = ffList.emplace(key, n);
353 }
354 }
355
356 return &(iter->second);
357
358 } catch (Gio::Error&) {}
359
360 return nullptr;
361 }
362
getStat(int & totFiles,int & totTemplates)363 void FFManager::getStat( int &totFiles, int &totTemplates)
364 {
365 totFiles = 0;
366 totTemplates = 0;
367
368 for( ffList_t::iterator iter = ffList.begin(); iter != ffList.end(); ++iter ) {
369 ffInfo &i = iter->second;
370
371 if( i.pathname.empty() ) {
372 totTemplates++;
373 totFiles += i.pathNames.size();
374 } else {
375 totFiles++;
376 }
377 }
378 }
379
380 /* The search for the best match is twofold:
381 * if perfect matches for make and model are found, then the list is scanned for lesser distance in time
382 * otherwise if no match is found, the whole list is searched for lesser distance in lens and aperture
383 */
find(const std::string & mak,const std::string & mod,const std::string & len,double focal,double apert,time_t t)384 ffInfo* FFManager::find( const std::string &mak, const std::string &mod, const std::string &len, double focal, double apert, time_t t )
385 {
386 if( ffList.empty() ) {
387 return nullptr;
388 }
389
390 std::string key( ffInfo::key(mak, mod, len, focal, apert) );
391 ffList_t::iterator iter = ffList.find( key );
392
393 if( iter != ffList.end() ) {
394 ffList_t::iterator bestMatch = iter;
395 time_t bestDeltaTime = ABS(iter->second.timestamp - t);
396
397 for(++iter; iter != ffList.end() && !key.compare( iter->second.key() ); ++iter ) {
398 time_t d = ABS(iter->second.timestamp - t );
399
400 if( d < bestDeltaTime ) {
401 bestMatch = iter;
402 bestDeltaTime = d;
403 }
404 }
405
406 return &(bestMatch->second);
407 } else {
408 iter = ffList.begin();
409 ffList_t::iterator bestMatch = iter;
410 double bestD = iter->second.distance( mak, mod, len, focal, apert );
411
412 for( ++iter; iter != ffList.end(); ++iter ) {
413 double d = iter->second.distance( mak, mod, len, focal, apert );
414
415 if( d < bestD ) {
416 bestD = d;
417 bestMatch = iter;
418 }
419 }
420
421 return bestD != INFINITY ? &(bestMatch->second) : nullptr ;
422 }
423 }
424
searchFlatField(const std::string & mak,const std::string & mod,const std::string & len,double focal,double apert,time_t t)425 RawImage* FFManager::searchFlatField( const std::string &mak, const std::string &mod, const std::string &len, double focal, double apert, time_t t )
426 {
427 ffInfo *ff = find( mak, mod, len, focal, apert, t );
428
429 if( ff ) {
430 return ff->getRawImage();
431 } else {
432 return nullptr;
433 }
434 }
435
searchFlatField(const Glib::ustring filename)436 RawImage* FFManager::searchFlatField( const Glib::ustring filename )
437 {
438 for ( ffList_t::iterator iter = ffList.begin(); iter != ffList.end(); ++iter ) {
439 if( iter->second.pathname.compare( filename ) == 0 ) {
440 return iter->second.getRawImage();
441 }
442 }
443
444 ffInfo *ff = addFileInfo( filename , false);
445
446 if(ff) {
447 return ff->getRawImage();
448 }
449
450 return nullptr;
451 }
452
453
454 // Global variable
455 FFManager ffm;
456
457
458 }
459
460