MVE - Multi-View Environment mve-devel
Loading...
Searching...
No Matches
view.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 2015, Simon Fuhrmann
3 * TU Darmstadt - Graphics, Capture and Massively Parallel Computing
4 * All rights reserved.
5 *
6 * This software may be modified and distributed under the terms
7 * of the BSD 3-Clause license. See the LICENSE.txt file for details.
8 */
9
10#include <fstream>
11#include <iostream>
12#include <cstring>
13#include <cerrno>
14
15#include "util/exception.h"
16#include "util/file_system.h"
17#include "util/tokenizer.h"
18#include "util/ini_parser.h"
19#include "mve/view.h"
20#include "mve/image_io.h"
21
22#define VIEW_IO_META_FILE "meta.ini"
23#define VIEW_IO_BLOB_SIGNATURE "\211MVE_BLOB\n"
24#define VIEW_IO_BLOB_SIGNATURE_LEN 10
25
26/* The signature to identify deprecated MVE files. */
27#define VIEW_MVE_FILE_SIGNATURE "\211MVE\n"
28#define VIEW_MVE_FILE_SIGNATURE_LEN 5
29
31
32void
33View::load_view (std::string const& user_path)
34{
35 std::string safe_path = util::fs::sanitize_path(user_path);
36 safe_path = util::fs::abspath(safe_path);
37 this->deprecated_format_check(safe_path);
38
39 /* Open meta.ini and populate images and blobs. */
40 //std::cout << "View: Loading view: " << path << std::endl;
41 this->clear();
42 try
43 {
44 this->load_meta_data(safe_path);
45 this->populate_images_and_blobs(safe_path);
46 this->path = safe_path;
47 }
48 catch (...)
49 {
50 this->clear();
51 throw;
52 }
53}
54
55void
56View::load_view_from_mve_file (std::string const& filename)
57{
58 /* Open file. */
59 std::ifstream infile(filename.c_str(), std::ios::binary);
60 if (!infile.good())
61 throw util::FileException(filename, std::strerror(errno));
62
63 /* Signature checks. */
64 char signature[VIEW_MVE_FILE_SIGNATURE_LEN];
65 infile.read(signature, VIEW_MVE_FILE_SIGNATURE_LEN);
66 if (!std::equal(signature, signature + VIEW_MVE_FILE_SIGNATURE_LEN,
68 throw util::Exception("Invalid file signature");
69
70 this->clear();
71
72 /* Read headers and create a schedule to read embeddings. */
73 typedef std::pair<std::size_t, char*> ReadBuffer;
74 std::vector<ReadBuffer> embedding_buffers;
75 while (true)
76 {
77 std::string line;
78 std::getline(infile, line);
79 if (infile.eof())
80 throw util::Exception("Premature EOF while reading headers");
81
84 if (line == "end_headers")
85 break;
86
87 /* Tokenize header. */
88 util::Tokenizer tokens;
89 tokens.split(line);
90 if (tokens.empty())
91 throw util::Exception("Invalid header line: ", line);
92
93 /* Populate meta data from headers. */
94 if (tokens[0] == "image" && tokens.size() == 6)
95 {
96 ImageProxy proxy;
97 proxy.is_dirty = true;
98 proxy.name = tokens[1];
99 proxy.width = util::string::convert<int64_t>(tokens[2]);
100 proxy.height = util::string::convert<int64_t>(tokens[3]);
101 proxy.channels = util::string::convert<int64_t>(tokens[4]);
102 proxy.type = mve::ImageBase::get_type_for_string(tokens[5]);
103 proxy.is_initialized = true;
105 (proxy.type, proxy.width, proxy.height, proxy.channels);
106 this->images.push_back(proxy);
107 embedding_buffers.push_back(ReadBuffer(
108 proxy.image->get_byte_size(),
109 proxy.image->get_byte_pointer()));
110 }
111 else if (tokens[0] == "data" && tokens.size() == 3)
112 {
113 BlobProxy proxy;
114 proxy.is_dirty = true;
115 proxy.name = tokens[1];
116 proxy.size = util::string::convert<uint64_t>(tokens[2]);
117 proxy.is_initialized = true;
118 proxy.blob = mve::ByteImage::create(proxy.size, 1, 1);
119 this->blobs.push_back(proxy);
120 embedding_buffers.push_back(ReadBuffer(
121 proxy.blob->get_byte_size(),
122 proxy.blob->get_byte_pointer()));
123 }
124 else if (tokens[0] == "id" && tokens.size() == 2)
125 {
126 this->set_value("view.id", tokens[1]);
127 }
128 else if (tokens[0] == "name" && tokens.size() > 1)
129 {
130 this->set_value("view.name", tokens.concat(1));
131 }
132 else if (tokens[0] == "camera-ext" && tokens.size() == 13)
133 {
134 this->set_value("camera.translation", tokens.concat(1, 3));
135 this->set_value("camera.rotation", tokens.concat(4, 9));
136 }
137 else if (tokens[0] == "camera-int"
138 && tokens.size() >= 2 && tokens.size() <= 7)
139 {
140 this->set_value("camera.focal_length", tokens[1]);
141 if (tokens.size() > 3)
142 this->set_value("camera.radial_distortion", tokens.concat(2, 2));
143 if (tokens.size() > 4)
144 this->set_value("camera.pixel_aspect", tokens[4]);
145 if (tokens.size() > 6)
146 this->set_value("camera.principal_point", tokens.concat(5, 2));
147 }
148 else
149 {
150 std::cerr << "Unrecognized header: " << line << std::endl;
151 }
152 }
153
154 /* Read embeddings and populate view. */
155 for (std::size_t i = 0; i < embedding_buffers.size(); ++i)
156 {
157 std::string line;
158 std::getline(infile, line);
159 if (infile.eof())
160 throw util::Exception("Premature EOF while reading payload");
161
162 util::Tokenizer tokens;
163 tokens.split(line);
164 if (tokens.size() != 3)
165 throw util::Exception("Invalid embedding: ", line);
166
167 ReadBuffer& buffer = embedding_buffers[i];
168 std::size_t byte_size = util::string::convert<std::size_t>(tokens[2]);
169 if (byte_size != buffer.first)
170 throw util::Exception("Unexpected embedding size");
171
172 infile.read(buffer.second, buffer.first);
173 infile.ignore();
174 }
175
176 if (infile.eof())
177 throw util::Exception("Premature EOF while reading payload");
178 infile.close();
179}
180
181void
182View::reload_view (void)
183{
184 if (this->path.empty())
185 throw std::runtime_error("View not initialized");
186 this->load_view(this->path);
187}
188
189void
190View::save_view_as (std::string const& user_path)
191{
192 std::string safe_path = util::fs::sanitize_path(user_path);
193 safe_path = util::fs::abspath(safe_path);
194 //std::cout << "View: Saving view: " << safe_path << std::endl;
195
196 /* Create view directory if needed. */
197 if (util::fs::file_exists(safe_path.c_str()))
198 throw util::FileException(safe_path, "Is not a directory");
199 if (!util::fs::dir_exists(safe_path.c_str()))
200 if (!util::fs::mkdir(safe_path.c_str()))
201 throw util::FileException(safe_path, std::strerror(errno));
202
203 /* Load all images and BLOBS. */
204 for (std::size_t i = 0; i < this->images.size(); ++i)
205 {
206 /* Image references will be copied on save. No need to load it here. */
207 if (!util::fs::is_absolute(this->images[i].filename))
208 this->load_image(&this->images[i], false);
209 this->images[i].is_dirty = true;
210 }
211 for (std::size_t i = 0; i < this->blobs.size(); ++i)
212 {
213 this->load_blob(&this->blobs[i], false);
214 this->blobs[i].is_dirty = true;
215 }
216
217 /* Save meta data, images and BLOBS, and free memory. */
218 this->save_meta_data(safe_path);
219 this->path = safe_path;
220 this->save_view();
221 this->cache_cleanup();
222}
223
224int
225View::save_view (void)
226{
227 if (this->path.empty())
228 throw std::runtime_error("View not initialized");
229
230 /* Save meta data. */
231 int saved = 0;
232 if (this->meta_data.is_dirty)
233 {
234 this->save_meta_data(this->path);
235 saved += 1;
236 }
237
238 /* Save dirty images. */
239 for (std::size_t i = 0; i < this->images.size(); ++i)
240 {
241 if (this->images[i].is_dirty)
242 {
243 this->save_image_intern(&this->images[i]);
244 saved += 1;
245 }
246 }
247
248 /* Save dirty BLOBS. */
249 for (std::size_t i = 0; i < this->blobs.size(); ++i)
250 {
251 if (this->blobs[i].is_dirty)
252 {
253 this->save_blob_intern(&this->blobs[i]);
254 saved += 1;
255 }
256 }
257
258 /* Delete files of removed images and BLOBs. */
259 for (std::size_t i = 0; i < this->to_delete.size(); ++i)
260 {
261 //std::cout << "View: Deleting file: "
262 // << this->to_delete[i] << std::endl;
263
264 std::string fname = util::fs::join_path(this->path, this->to_delete[i]);
265 if (util::fs::file_exists(fname.c_str())
266 && !util::fs::unlink(fname.c_str()))
267 {
268 std::cerr << "View: Error deleting " << fname
269 << ": " << std::strerror(errno) << std::endl;
270 //throw util::FileException(fname, std::strerror(errno));
271 }
272 }
273 this->to_delete.clear();
274
275 return saved;
276}
277
278void
279View::clear (void)
280{
281 this->path.clear();
282 this->meta_data = MetaData();
283 this->images.clear();
284 this->blobs.clear();
285 this->to_delete.clear();
286}
287
288bool
289View::is_dirty (void) const
290{
291 if (this->meta_data.is_dirty)
292 return true;
293 if (!this->to_delete.empty())
294 return true;
295 for (std::size_t i = 0; i < this->images.size(); ++i)
296 if (this->images[i].is_dirty)
297 return true;
298 for (std::size_t i = 0; i < this->blobs.size(); ++i)
299 if (this->blobs[i].is_dirty)
300 return true;
301 return false;
302}
303
304int
305View::cache_cleanup (void)
306{
307 int released = 0;
308 for (std::size_t i = 0; i < this->images.size(); ++i)
309 {
310 ImageProxy& proxy = this->images[i];
311 if (proxy.is_dirty || proxy.image.use_count() != 1)
312 continue;
313 proxy.image.reset();
314 released += 1;
315 }
316 for (std::size_t i = 0; i < this->blobs.size(); ++i)
317 {
318 BlobProxy& proxy = this->blobs[i];
319 if (proxy.is_dirty || proxy.blob.use_count() != 1)
320 continue;
321 proxy.blob.reset();
322 released += 1;
323 }
324 return released;
325}
326
327std::size_t
328View::get_byte_size (void) const
329{
330 std::size_t ret = 0;
331 for (std::size_t i = 0; i < this->images.size(); ++i)
332 if (this->images[i].image != nullptr)
333 ret += this->images[i].image->get_byte_size();
334 for (std::size_t i = 0; i < this->blobs.size(); ++i)
335 if (this->blobs[i].blob != nullptr)
336 ret += this->blobs[i].blob->get_byte_size();
337 return ret;
338}
339
340/* ---------------------------------------------------------------- */
341
342std::string
343View::get_value (std::string const& key) const
344{
345 if (key.empty())
346 throw std::invalid_argument("Empty key");
347 if (key.find_first_of('.') == std::string::npos)
348 throw std::invalid_argument("Missing section identifier");
349 typedef MetaData::KeyValueMap::const_iterator KeyValueIter;
350 KeyValueIter iter = this->meta_data.data.find(key);
351 if (iter == this->meta_data.data.end())
352 return std::string();
353 return iter->second;
354}
355
356void
357View::set_value (std::string const& key, std::string const& value)
358{
359 if (key.empty())
360 throw std::invalid_argument("Empty key");
361 if (key.find_first_of('.') == std::string::npos)
362 throw std::invalid_argument("Missing section identifier");
363 this->meta_data.data[key] = value;
364 this->meta_data.is_dirty = true;
365}
366
367void
368View::delete_value (std::string const& key)
369{
370 this->meta_data.data.erase(key);
371}
372
373void
374View::set_camera (CameraInfo const& camera)
375{
376 this->meta_data.camera = camera;
377 this->meta_data.is_dirty = true;
378
379 /* Re-generate the "camera" section. */
380 this->set_value("camera.focal_length",
381 util::string::get_digits(camera.flen, 10));
382 this->set_value("camera.radial_distortion",
383 util::string::get_digits(camera.dist[0], 10) + " "
384 + util::string::get_digits(camera.dist[1], 10));
385 this->set_value("camera.pixel_aspect",
387 this->set_value("camera.principal_point",
388 util::string::get_digits(camera.ppoint[0], 10) + " "
389 + util::string::get_digits(camera.ppoint[1], 10));
390 this->set_value("camera.rotation", camera.get_rotation_string());
391 this->set_value("camera.translation", camera.get_translation_string());
392}
393
394/* ---------------------------------------------------------------- */
395
397View::get_image (std::string const& name, ImageType type)
398{
399 View::ImageProxy* proxy = this->find_image_intern(name);
400 if (proxy != nullptr)
401 {
402 if (type == IMAGE_TYPE_UNKNOWN)
403 return this->load_image(proxy, false);
404 this->initialize_image(proxy, false);
405 if (proxy->type == type)
406 return this->load_image(proxy, false);
407 }
408 return ImageBase::Ptr();
409}
410
411View::ImageProxy const*
412View::get_image_proxy (std::string const& name, ImageType type)
413{
414 View::ImageProxy* proxy = this->find_image_intern(name);
415 if (proxy != nullptr)
416 {
417 this->initialize_image(proxy, false);
418 if (type == IMAGE_TYPE_UNKNOWN || proxy->type == type)
419 return proxy;
420 }
421 return nullptr;
422}
423
424bool
425View::has_image (std::string const& name, ImageType type)
426{
427 View::ImageProxy* proxy = this->find_image_intern(name);
428 if (proxy == nullptr)
429 return false;
430 if (type == IMAGE_TYPE_UNKNOWN)
431 return true;
432 this->initialize_image(proxy, false);
433 return proxy->type == type;
434}
435
436void
437View::set_image (ImageBase::Ptr image, std::string const& name)
438{
439 if (image == nullptr)
440 throw std::invalid_argument("Null image");
441
442 ImageProxy proxy;
443 proxy.is_dirty = true;
444 proxy.name = name;
445 proxy.is_initialized = true;
446 proxy.width = image->width();
447 proxy.height = image->height();
448 proxy.channels = image->channels();
449 proxy.type = image->get_type();
450 proxy.image = image;
451
452 for (std::size_t i = 0; i < this->images.size(); ++i)
453 if (this->images[i].name == name)
454 {
455 this->images[i] = proxy;
456 return;
457 }
458 this->images.push_back(proxy);
459}
460
461void
462View::set_image_ref (std::string const& filename, std::string name)
463{
464 if (filename.empty() || name.empty())
465 throw std::invalid_argument("Empty argument");
466
467 ImageProxy proxy;
468 proxy.is_dirty = true;
469 proxy.name = name;
470 proxy.filename = util::fs::abspath(filename);
471 proxy.is_initialized = false;
472
473 for (std::size_t i = 0; i < this->images.size(); ++i)
474 if (this->images[i].name == name)
475 {
476 this->images[i] = proxy;
477 return;
478 }
479 this->images.push_back(proxy);
480}
481
482bool
483View::remove_image (std::string const& name)
484{
485 for (ImageProxies::iterator iter = this->images.begin();
486 iter != this->images.end(); ++iter)
487 {
488 if (iter->name == name)
489 {
490 this->to_delete.push_back(iter->filename);
491 this->images.erase(iter);
492 return true;
493 }
494 }
495 return false;
496}
497
498/* ---------------------------------------------------------------- */
499
501View::get_blob (std::string const& name)
502{
503 BlobProxy* proxy = this->find_blob_intern(name);
504 if (proxy != nullptr)
505 return this->load_blob(proxy, false);
506 return ByteImage::Ptr();
507}
508
509View::BlobProxy const*
510View::get_blob_proxy (std::string const& name)
511{
512 BlobProxy* proxy = this->find_blob_intern(name);
513 if (proxy != nullptr)
514 this->initialize_blob(proxy, false);
515 return proxy;
516}
517
518bool
519View::has_blob (std::string const& name)
520{
521 return this->find_blob_intern(name) != nullptr;
522}
523
524void
525View::set_blob (ByteImage::Ptr blob, std::string const& name)
526{
527 if (blob == nullptr)
528 throw std::invalid_argument("Null blob");
529
530 BlobProxy proxy;
531 proxy.is_dirty = true;
532 proxy.name = name;
533 proxy.is_initialized = true;
534 proxy.size = blob->get_byte_size();
535 proxy.blob = blob;
536
537 for (std::size_t i = 0; i < this->blobs.size(); ++i)
538 if (this->blobs[i].name == name)
539 {
540 this->blobs[i] = proxy;
541 return;
542 }
543 this->blobs.push_back(proxy);
544}
545
546bool
547View::remove_blob (std::string const& name)
548{
549 for (BlobProxies::iterator iter = this->blobs.begin();
550 iter != this->blobs.end(); ++iter)
551 {
552 if (iter->name == name)
553 {
554 this->to_delete.push_back(iter->filename);
555 this->blobs.erase(iter);
556 return true;
557 }
558 }
559 return false;
560}
561
562/* ------------------------ Private Members ----------------------- */
563
564void
565View::deprecated_format_check (std::string const& path)
566{
567 /* If the given path is a file, report deprecated format info. */
568 if (util::fs::file_exists(path.c_str()))
569 {
570 char const* text =
571 "The dataset contains views in a deprecated file format.\n"
572 "Please upgrade your datasets using the 'sceneupgrade' app.\n"
573 "See the GitHub wiki for more information about this change.";
574
575 std::cerr << std::endl << "NOTE: " << text << std::endl << std::endl;
576 throw std::invalid_argument(text);
577 }
578}
579
580void
581View::load_meta_data (std::string const& path)
582{
583 std::string const fname = util::fs::join_path(path, VIEW_IO_META_FILE);
584
585 /* Open file and read key/value pairs. */
586 std::ifstream in(fname.c_str());
587 if (!in.good())
588 throw util::FileException(fname, "Error opening");
589 util::parse_ini(in, &this->meta_data.data);
590 this->meta_data.is_dirty = false;
591 in.close();
592
593 /* Get camera data from key/value pairs. */
594 std::string cam_fl = this->get_value("camera.focal_length");
595 std::string cam_dist = this->get_value("camera.radial_distortion");
596 std::string cam_pa = this->get_value("camera.pixel_aspect");
597 std::string cam_pp = this->get_value("camera.principal_point");
598 std::string cam_rot = this->get_value("camera.rotation");
599 std::string cam_trans = this->get_value("camera.translation");
600
601 this->meta_data.camera = CameraInfo();
602 if (!cam_fl.empty())
603 this->meta_data.camera.flen = util::string::convert<float>(cam_fl);
604 if (!cam_dist.empty())
605 {
606 std::stringstream ss(cam_dist);
607 ss >> this->meta_data.camera.dist[0];
608 ss >> this->meta_data.camera.dist[1];
609 }
610 if (!cam_pa.empty())
611 this->meta_data.camera.paspect = util::string::convert<float>(cam_pa);
612 if (!cam_pp.empty())
613 {
614 std::stringstream ss(cam_pp);
615 ss >> this->meta_data.camera.ppoint[0];
616 ss >> this->meta_data.camera.ppoint[1];
617 }
618 if (!cam_rot.empty())
619 this->meta_data.camera.set_rotation_from_string(cam_rot);
620 if (!cam_trans.empty())
621 this->meta_data.camera.set_translation_from_string(cam_trans);
622}
623
624void
625View::save_meta_data (std::string const& path)
626{
627 //std::cout << "View: Saving meta data: " VIEW_IO_META_FILE << std::endl;
628 std::string const fname = util::fs::join_path(path, VIEW_IO_META_FILE);
629 std::string const fname_new = fname + ".new";
630
631 std::ofstream out(fname_new.c_str(), std::ios::binary);
632 if (!out.good())
633 throw util::FileException(fname_new, std::strerror(errno));
634
635 /* Write meta data to file. */
636 out << "# MVE view meta data is stored in INI-file syntax.\n";
637 out << "# This file is generated, formatting will get lost.\n";
638 try
639 {
640 util::write_ini(this->meta_data.data, out);
641 out.close();
642 }
643 catch (...)
644 {
645 out.close();
646 util::fs::unlink(fname_new.c_str());
647 throw;
648 }
649
650 /* On succesfull write, move the new file in place. */
651 this->replace_file(fname, fname_new);
652 this->meta_data.is_dirty = false;
653}
654
655void
656View::populate_images_and_blobs (std::string const& path)
657{
658 util::fs::Directory dir(path);
659 for (std::size_t i = 0; i < dir.size(); ++i)
660 {
661 util::fs::File const& file = dir[i];
662 if (file.name == VIEW_IO_META_FILE)
663 continue;
664
665 std::string ext4 = util::string::right(file.name, 4);
666 std::string ext5 = util::string::right(file.name, 5);
667 ext4 = util::string::lowercase(ext4);
668 ext5 = util::string::lowercase(ext5);
669
670 std::string name = file.name.substr(0, file.name.find_last_of('.'));
671 if (name.empty())
672 {
673 std::cerr << "View: Invalid file name "
674 << file.name << ", skipping." << std::endl;
675 continue;
676 }
677
678 /* Load image. */
679 if (ext4 == ".png" || ext4 == ".jpg" ||
680 ext5 == ".jpeg" || ext5 == ".mvei")
681 {
682 //std::cout << "View: Adding image proxy: "
683 // << file.name << std::endl;
684
685 ImageProxy proxy;
686 proxy.is_dirty = false;
687 proxy.filename = file.name;
688 proxy.name = name;
689 this->images.push_back(proxy);
690 }
691 else if (ext5 == ".blob")
692 {
693 //std::cout << "View: Adding BLOB proxy: "
694 // << file.name << std::endl;
695
696 BlobProxy proxy;
697 proxy.is_dirty = false;
698 proxy.filename = file.name;
699 proxy.name = name;
700 this->blobs.push_back(proxy);
701 }
702 else if (ext4 != ".ply") // silently ignore ply files
703 {
704 std::cerr << "View: Unrecognized extension "
705 << file.name << ", skipping." << std::endl;
706 }
707 }
708}
709
710void
711View::replace_file (std::string const& old_fn, std::string const& new_fn)
712{
713 /* Delete old file. */
714 if (util::fs::file_exists(old_fn.c_str()))
715 if (!util::fs::unlink(old_fn.c_str()))
716 throw util::FileException(old_fn, std::strerror(errno));
717
718 /* Rename new file. */
719 if (!util::fs::rename(new_fn.c_str(), old_fn.c_str()))
720 throw util::FileException(new_fn, std::strerror(errno));
721}
722
723/* ---------------------------------------------------------------- */
724
725View::ImageProxy*
726View::find_image_intern (std::string const& name)
727{
728 for (std::size_t i = 0; i < this->images.size(); ++i)
729 if (this->images[i].name == name)
730 return &this->images[i];
731 return nullptr;
732}
733
734void
735View::initialize_image (ImageProxy* proxy, bool update)
736{
737 if (proxy->is_initialized && !update)
738 return;
739 this->load_image_intern(proxy, true);
740}
741
742ImageBase::Ptr
743View::load_image (ImageProxy* proxy, bool update)
744{
745 if (proxy->image != nullptr && !update)
746 return proxy->image;
747 this->load_image_intern(proxy, false);
748 return proxy->image;
749}
750
751void
752View::load_image_intern (ImageProxy* proxy, bool init_only)
753{
754 if (this->path.empty() && !util::fs::is_absolute(proxy->filename))
755 throw std::runtime_error("View not initialized");
756 if (proxy->filename.empty())
757 throw std::runtime_error("Empty proxy filename");
758 if (proxy->name.empty())
759 throw std::runtime_error("Empty proxy name");
760
761 /* If the file name is absolute, it indicates an image reference. */
762 std::string filename;
763 if (util::fs::is_absolute(proxy->filename))
764 filename = proxy->filename;
765 else
766 filename = util::fs::join_path(this->path, proxy->filename);
767
768 if (init_only)
769 {
770 //std::cout << "View: Initializing image " << filename << std::endl;
771 image::ImageHeaders headers = image::load_file_headers(filename);
772 proxy->width = headers.width;
773 proxy->height = headers.height;
774 proxy->channels = headers.channels;
775 proxy->type = headers.type;
776 proxy->is_initialized = true;
777 proxy->is_dirty = false;
778 return;
779 }
780
781 //std::cout << "View: Loading image " << filename << std::endl;
782 std::string ext4 = util::string::right(proxy->filename, 4);
783 std::string ext5 = util::string::right(proxy->filename, 5);
784 ext4 = util::string::lowercase(ext4);
785 ext5 = util::string::lowercase(ext5);
786 if (ext4 == ".png" || ext4 == ".jpg" || ext5 == ".jpeg")
787 proxy->image = image::load_file(filename);
788 else if (ext5 == ".mvei")
789 proxy->image = image::load_mvei_file(filename);
790 else
791 throw std::runtime_error("Unexpected image type");
792
793 proxy->width = proxy->image->width();
794 proxy->height = proxy->image->height();
795 proxy->channels = proxy->image->channels();
796 proxy->type = proxy->image->get_type();
797 proxy->is_initialized = true;
798 proxy->is_dirty = false;
799}
800
801namespace
802{
803 std::string
804 get_file_extension (std::string const& filename)
805 {
806 std::size_t pos = filename.find_last_of('.');
807 if (pos == std::string::npos)
808 return std::string();
809 return util::string::lowercase(filename.substr(pos));
810 }
811}
812
813void
814View::save_image_intern (ImageProxy* proxy)
815{
816 if (this->path.empty())
817 throw std::runtime_error("View not initialized");
818 if (proxy == nullptr)
819 throw std::runtime_error("Null proxy");
820
821 /* An absolute filename indicates an image reference. Copy file. */
822 if (util::fs::is_absolute(proxy->filename))
823 {
824 std::string ext = get_file_extension(proxy->filename);
825 std::string fname = proxy->name + ext;
826 std::string pname = util::fs::join_path(this->path, fname);
827 //std::cout << "View: Copying image: " << fname << std::endl;
828 util::fs::copy_file(proxy->filename.c_str(), pname.c_str());
829 proxy->filename = fname;
830 proxy->is_dirty = false;
831 return;
832 }
833
834 if (proxy->image == nullptr || proxy->width != proxy->image->width()
835 || proxy->height != proxy->image->height()
836 || proxy->channels != proxy->image->channels()
837 || proxy->type != proxy->image->get_type())
838 throw std::runtime_error("Image specification mismatch");
839
840 /* Generate a new filename for the image. */
841 bool use_png_format = false;
842 if (proxy->image->get_type() == IMAGE_TYPE_UINT8
843 && proxy->image->channels() <= 4)
844 use_png_format = true;
845
846 std::string filename = proxy->name + (use_png_format ? ".png" : ".mvei");
847 std::string fname_orig = util::fs::join_path(this->path, proxy->filename);
848 std::string fname_save = util::fs::join_path(this->path, filename);
849 std::string fname_new = fname_save + ".new";
850
851 /* Save the new image. */
852 //std::cout << "View: Saving image: " << filename << std::endl;
853 if (use_png_format)
854 image::save_png_file(
855 std::dynamic_pointer_cast<ByteImage>(proxy->image), fname_new);
856 else
857 image::save_mvei_file(proxy->image, fname_new);
858
859 /* On succesfull write, move the new file in place. */
860 this->replace_file(fname_save, fname_new);
861
862 /* If the original file was different (e.g. JPG to lossless), remove it. */
863 if (!proxy->filename.empty() && fname_save != fname_orig)
864 {
865 //std::cout << "View: Deleting file: " << fname_orig << std::endl;
866 if (util::fs::file_exists(fname_orig.c_str())
867 && !util::fs::unlink(fname_orig.c_str()))
868 throw util::FileException(fname_orig, std::strerror(errno));
869 }
870
871 /* Fully update the proxy. */
872 proxy->filename = filename;
873 proxy->width = proxy->image->width();
874 proxy->height = proxy->image->height();
875 proxy->channels = proxy->image->channels();
876 proxy->type = proxy->image->get_type();
877 proxy->is_initialized = true;
878 proxy->is_dirty = false;
879}
880
881/* ---------------------------------------------------------------- */
882
883View::BlobProxy*
884View::find_blob_intern (std::string const& name)
885{
886 for (std::size_t i = 0; i < this->blobs.size(); ++i)
887 if (this->blobs[i].name == name)
888 return &this->blobs[i];
889 return nullptr;
890}
891
892void
893View::initialize_blob (BlobProxy* proxy, bool update)
894{
895 if (proxy->is_initialized && !update)
896 return;
897 this->load_blob_intern(proxy, true);
898}
899
900ByteImage::Ptr
901View::load_blob (BlobProxy* proxy, bool update)
902{
903 if (proxy->blob != nullptr && !update)
904 return proxy->blob;
905 this->load_blob_intern(proxy, false);
906 return proxy->blob;
907}
908
909void
910View::load_blob_intern (BlobProxy* proxy, bool init_only)
911{
912 if (this->path.empty())
913 throw std::runtime_error("View not initialized");
914 if (proxy->name.empty())
915 return;
916
917 /* Load blob and update meta data. */
918 std::string filename = util::fs::join_path(this->path, proxy->filename);
919 std::ifstream in(filename.c_str(), std::ios::binary);
920 if (!in.good())
921 throw util::FileException(filename, std::strerror(errno));
922
923 /* Read file signature. */
924 char signature[VIEW_IO_BLOB_SIGNATURE_LEN];
925 in.read(signature, VIEW_IO_BLOB_SIGNATURE_LEN);
926 if (!std::equal(signature, signature + VIEW_IO_BLOB_SIGNATURE_LEN,
928 throw util::Exception("Invalid BLOB file signature");
929
930 /* Read blob size. */
931 uint64_t size;
932 in.read(reinterpret_cast<char*>(&size), sizeof(uint64_t));
933 if (!in.good())
934 throw util::FileException(filename, "EOF while reading BLOB headers");
935
936 if (init_only)
937 {
938 //std::cout << "View: Initializing BLOB: " << filename << std::endl;
939 proxy->size = size;
940 proxy->is_initialized = true;
941 return;
942 }
943
944 /* Read blob payload. */
945 //std::cout << "View: Loading BLOB: " << filename << std::endl;
946 // FIXME: This limits BLOBs size to 2^31 bytes.
947 ByteImage::Ptr blob = ByteImage::create(size, 1, 1);
948 in.read(blob->get_byte_pointer(), blob->get_byte_size());
949 if (!in.good())
950 throw util::FileException(filename, "EOF while reading BLOB payload");
951
952 proxy->blob = blob;
953 proxy->size = size;
954 proxy->is_initialized = true;
955}
956
957void
958View::save_blob_intern (BlobProxy* proxy)
959{
960 if (this->path.empty())
961 throw std::runtime_error("View not initialized");
962 if (proxy == nullptr || proxy->blob == nullptr)
963 throw std::runtime_error("Null proxy or data");
964 if (proxy->blob->get_byte_size() != proxy->size)
965 throw std::runtime_error("BLOB size mismatch");
966
967 /* If the object has never been saved, the filename is empty. */
968 if (proxy->filename.empty())
969 proxy->filename = proxy->name + ".blob";
970
971 /* Create a .new file and save blob. */
972 std::string fname_orig = util::fs::join_path(this->path, proxy->filename);
973 std::string fname_new = fname_orig + ".new";
974
975 // Check if file exists? Create unique temp name?
976 //std::cout << "View: Saving BLOB " << proxy->filename << std::endl;
977 std::ofstream out(fname_new.c_str(), std::ios::binary);
978 if (!out.good())
979 throw util::FileException(fname_new, std::strerror(errno));
980
982 out.write(reinterpret_cast<char const*>(&proxy->size), sizeof(uint64_t));
983 out.write(proxy->blob->get_byte_pointer(), proxy->blob->get_byte_size());
984 if (!out.good())
985 throw util::FileException(fname_new, std::strerror(errno));
986 out.close();
987
988 /* On succesfull write, move the new file in place. */
989 this->replace_file(fname_orig, fname_new);
990
991 /* Fully update the proxy. */
992 proxy->size = proxy->blob->get_byte_size();
993 proxy->is_initialized = true;
994 proxy->is_dirty = false;
995}
996
997/* ---------------------------------------------------------------- */
998
999void
1000View::debug_print (void)
1001{
1002 for (std::size_t i = 0; i < this->images.size(); ++i)
1003 this->initialize_image(&this->images[i], false);
1004 for (std::size_t i = 0; i < this->blobs.size(); ++i)
1005 this->initialize_blob(&this->blobs[i], false);
1006
1007 std::cout << std::endl;
1008 std::cout << "Path: " << this->path << std::endl;
1009 std::cout << "View Name: " << this->get_value("view.name") << std::endl;
1010 std::cout << "View key/value pairs:" << std::endl;
1011
1012 typedef MetaData::KeyValueMap::const_iterator KeyValueIter;
1013 for (KeyValueIter iter = this->meta_data.data.begin();
1014 iter != this->meta_data.data.end(); iter++)
1015 std::cout << " " << iter->first << " = " << iter->second << std::endl;
1016
1017 std::cout << "View images:" << std::endl;
1018 for (std::size_t i = 0; i < this->images.size(); ++i)
1019 {
1020 ImageProxy const& proxy = this->images[i];
1021 std::cout << " " << proxy.name << " (" << proxy.filename << ")"
1022 << ", size " << proxy.width << "x" << proxy.height << "x" << proxy.channels
1023 << ", type " << proxy.type
1024 << (proxy.image != nullptr ? " (in memory)" : "") << std::endl;
1025 }
1026
1027 std::cout << "View BLOBs:" << std::endl;
1028 for (std::size_t i = 0; i < this->blobs.size(); ++i)
1029 {
1030 BlobProxy const& proxy = this->blobs[i];
1031 std::cout << " " << proxy.name << " (" << proxy.filename << ")"
1032 << ", size " << proxy.size << std::endl;
1033 }
1034}
1035
std::shared_ptr< ImageBase > Ptr
Definition image_base.h:55
static ImageType get_type_for_string(std::string const &type_string)
Returns the type for a valid type string, otherwise UNKNOWN.
Definition image_base.h:267
std::shared_ptr< Image< T > > Ptr
Definition image.h:42
static Ptr create(void)
Smart pointer image constructor.
Definition image.h:191
Universal, simple exception class.
Definition exception.h:24
Exception class for file exceptions with additional filename.
Definition exception.h:53
Simple tokenizer.
Definition tokenizer.h:29
std::string concat(std::size_t pos, std::size_t num=0) const
Concatenates 'num' tokens with a space character starting from token at position 'pos'.
Definition tokenizer.h:108
void split(std::string const &str, char delim=' ', bool keep_empty=false)
Very simple tokenziation at a given delimiter characater.
Definition tokenizer.h:62
Directory abstraction to scan directory contents.
#define MVE_NAMESPACE_BEGIN
Definition defines.h:13
#define MVE_NAMESPACE_END
Definition defines.h:14
ImageBase::Ptr create_for_type(ImageType type, int64_t width, int64_t height, int64_t chans)
Creates an image instance for a given type.
Definition image.h:137
ImageType
Identifiers for image types.
Definition image_base.h:28
@ IMAGE_TYPE_UNKNOWN
Definition image_base.h:29
bool unlink(char const *pathname)
Unlinks (deletes) the given file.
std::string sanitize_path(std::string const &path)
Canonicalize slashes in the given path.
std::string abspath(std::string const &path)
Returns the absolute representation of the given path.
bool is_absolute(std::string const &path)
Checks whether the given path is absolute.
bool mkdir(char const *pathname)
Creates a new directory.
void copy_file(char const *src, char const *dst)
Copies a file from 'src' to 'dst', throws FileException on error.
bool dir_exists(char const *pathname)
Determines if the given path is a directory.
bool rename(char const *from, char const *to)
Renames the given file 'from' to new name 'to'.
std::string join_path(std::string const &path1, std::string const &path2)
Concatenate and canonicalize two paths.
bool file_exists(char const *pathname)
Determines if the given path is a file.
void clip_newlines(std::string *str)
Clips newlines from the end of the string, in-place.
Definition strings.h:317
void clip_whitespaces(std::string *str)
Clips whitespaces from the front and end of the string, in-place.
Definition strings.h:299
std::string get_digits(T const &value, int digits)
Returns string with 'digits' of precision.
Definition strings.h:133
std::string lowercase(std::string const &str)
Returns a lower-case version of the string.
Definition strings.h:458
std::string right(std::string const &str, std::size_t chars)
Returns the rightmost 'chars' characters of 'str'.
Definition strings.h:452
void write_ini(std::map< std::string, std::string > const &map, std::ostream &stream)
Writes an INI file for the key/value pairs in the map.
Definition ini_parser.cc:83
void parse_ini(std::istream &stream, std::map< std::string, std::string > *map)
Parses a file in INI format and places key/value pairs in the map.
Definition ini_parser.cc:30
Per-view camera information with various helper functions.
Definition camera.h:24
float paspect
Pixel aspect ratio pixel_width / pixel_height.
Definition camera.h:160
std::string get_translation_string(void) const
Retuns the translation in string format.
Definition camera.cc:237
float ppoint[2]
Principal point in x- and y-direction.
Definition camera.h:158
float flen
Focal length.
Definition camera.h:156
std::string get_rotation_string(void) const
Retuns the rotation in string format.
Definition camera.cc:227
float dist[2]
Image distortion parameters.
Definition camera.h:162
Proxy for BLOBs (Binary Large OBjects).
Definition view.h:114
std::string filename
Filename is empty if the BLOB has not been saved yet, or relative if the BLOB is mapped to the view o...
Definition view.h:125
uint64_t size
Definition view.h:129
bool is_dirty
Indicates if the BLOB is unsaved or has been changed.
Definition view.h:116
std::string name
The name of the BLOB.
Definition view.h:119
ByteImage::Ptr blob
Definition view.h:132
Proxy for images.
Definition view.h:87
std::string filename
The filename is empty if the image has not been saved yet.
Definition view.h:99
ImageType type
Definition view.h:106
bool is_dirty
Indicates if the image is unsaved or has been changed.
Definition view.h:89
std::string name
The name of the image.
Definition view.h:92
ImageBase::Ptr image
Definition view.h:109
View meta information that stores key/value pairs and the camera.
Definition view.h:77
std::string name
#define VIEW_IO_BLOB_SIGNATURE_LEN
Definition view.cc:24
#define VIEW_IO_META_FILE
Definition view.cc:22
#define VIEW_MVE_FILE_SIGNATURE_LEN
Definition view.cc:28
#define VIEW_IO_BLOB_SIGNATURE
Definition view.cc:23
#define VIEW_MVE_FILE_SIGNATURE
Definition view.cc:27