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