ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ImageIO.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <FileSystem.h>
11 #include <ImageIO.h>
12 #include <Logging.h>
13 
14 #include <algorithm>
15 #include <array>
16 #include <cmath>
17 #include <fstream>
18 #include <unordered_map>
19 #include <vector>
20 
24 
25 namespace cloudViewer {
26 namespace t {
27 namespace io {
28 
29 namespace {
30 using signature_decoder_t =
31  std::pair<std::string,
32  std::function<bool(const std::string &, geometry::Image &)>>;
33 static const std::array<signature_decoder_t, 2> signature_decoder_list{
34  {{"\x89\x50\x4e\x47\xd\xa\x1a\xa", ReadImageFromPNG},
35  {"\xFF\xD8\xFF", ReadImageFromJPG}}};
36 static constexpr uint8_t MAX_SIGNATURE_LEN = 8;
37 
38 static const std::unordered_map<
39  std::string,
40  std::function<bool(const std::string &, const geometry::Image &, int)>>
41  file_extension_to_image_write_function{
42  {"png", WriteImageToPNG},
43  {"jpg", WriteImageToJPG},
44  {"jpeg", WriteImageToJPG},
45  };
46 } // namespace
47 
48 std::shared_ptr<geometry::Image> CreateImageFromFile(
49  const std::string &filename) {
50  auto image = std::make_shared<geometry::Image>();
52  return image;
53 }
54 
55 bool ReadImage(const std::string &filename, geometry::Image &image) {
56  std::string signature_buffer(MAX_SIGNATURE_LEN, 0);
57  std::ifstream file(filename, std::ios::binary);
58  file.read(&signature_buffer[0], MAX_SIGNATURE_LEN);
59  std::string err_msg;
60  if (!file) {
61  err_msg = "Read geometry::Image failed for file {}. I/O error.";
62  } else {
63  file.close();
64  for (const auto &signature_decoder : signature_decoder_list) {
65  if (signature_buffer.compare(0, signature_decoder.first.size(),
66  signature_decoder.first) == 0) {
67  return signature_decoder.second(filename, image);
68  }
69  }
70  err_msg =
71  "Read geometry::Image failed for file {}. Unknown file "
72  "signature, only PNG and JPG are supported.";
73  }
74  image.Clear();
75  utility::LogWarning(err_msg.c_str(), filename);
76  return false;
77 }
78 
79 bool WriteImage(const std::string &filename,
80  const geometry::Image &image,
81  int quality /* = kCloudViewerImageIODefaultQuality*/) {
82  std::string filename_ext =
84  if (filename_ext.empty()) {
86  "Write geometry::Image failed: unknown file extension.");
87  return false;
88  }
89  auto map_itr = file_extension_to_image_write_function.find(filename_ext);
90  if (map_itr == file_extension_to_image_write_function.end()) {
92  "Write geometry::Image failed: file extension {} unknown.",
93  filename_ext);
94  return false;
95  }
96  return map_itr->second(filename, image.To(core::Device("CPU:0")), quality);
97 }
98 
99 DepthNoiseSimulator::DepthNoiseSimulator(const std::string &noise_model_path) {
100  // data = np.loadtxt(fname, comments='%', skiprows=5)
101  const char comment_prefix = '%';
102  const int skip_first_n_lines = 5;
104  if (!file.Open(noise_model_path, "r")) {
105  utility::LogError("Read depth model failed: unable to open file: {}",
106  noise_model_path);
107  }
108  std::vector<float> data;
109  const char *line_buffer;
110  for (int i = 0; i < skip_first_n_lines; ++i) {
111  if (!(line_buffer = file.ReadLine())) {
113  "Read depth model failed: file {} is less than {} "
114  "lines.",
115  noise_model_path, skip_first_n_lines);
116  }
117  }
118  while ((line_buffer = file.ReadLine())) {
119  std::string line(line_buffer);
120  line.erase(std::find(line.begin(), line.end(), comment_prefix),
121  line.end());
122  if (!line.empty()) {
123  std::istringstream iss(line);
124  float value;
125  while (iss >> value) {
126  data.push_back(value);
127  }
128  }
129  }
130 
131  model_ = core::Tensor::Zeros({80, 80, 5}, core::Float32,
132  core::Device("CPU:0"));
133  geometry::kernel::TArrayIndexer<int> model_indexer(model_, 3);
134 
135  for (int y = 0; y < 80; ++y) {
136  for (int x = 0; x < 80; ++x) {
137  int idx = (y * 80 + x) * 23 + 3;
138  bool all_less_than_8000 = true;
139  for (int i = 0; i < 5; ++i) {
140  if (data[idx + i] >= 8000) {
141  all_less_than_8000 = false;
142  break;
143  }
144  }
145  if (all_less_than_8000) {
146  // model_[y, x, :] = 0
147  continue;
148  } else {
149  for (int i = 0; i < 5; ++i) {
150  *model_indexer.GetDataPtr<float>(i, x, y) =
151  data[idx + 15 + i];
152  }
153  }
154  }
155  }
156 }
157 
159  float depth_scale) {
160  // Sanity checks.
161  if (im_src.GetDtype() == core::Float32) {
162  if (depth_scale != 1.0) {
164  "Depth scale is ignored when input depth is float32.");
165  }
166  } else if (im_src.GetDtype() == core::UInt16) {
167  if (depth_scale <= 0.0) {
168  utility::LogError("Depth scale must be positive.");
169  }
170  } else {
171  utility::LogError("Unsupported depth image dtype: {}.",
172  im_src.GetDtype().ToString());
173  }
174  if (im_src.GetChannels() != 1) {
175  utility::LogError("Depth image must have 1 channel.");
176  }
177 
178  core::Tensor im_src_tensor = im_src.AsTensor();
179  const core::Device &original_device = im_src_tensor.GetDevice();
180  const core::Dtype &original_dtype = im_src_tensor.GetDtype();
181  int width = im_src.GetCols();
182  int height = im_src.GetRows();
183 
184  im_src_tensor = im_src_tensor.To(core::Device("CPU:0")).Contiguous();
185  if (original_dtype == core::UInt16) {
186  im_src_tensor = im_src_tensor.To(core::Float32) / depth_scale;
187  }
188  core::Tensor im_dst_tensor = im_src_tensor.Clone();
189 
190  utility::random::NormalGenerator<float> gen_coord(0, 0.25);
191  utility::random::NormalGenerator<float> gen_depth(0, 0.027778);
192 
193  geometry::kernel::TArrayIndexer<int> src_indexer(im_src_tensor, 2);
194  geometry::kernel::TArrayIndexer<int> dst_indexer(im_dst_tensor, 2);
195  geometry::kernel::TArrayIndexer<int> model_indexer(model_, 3);
196 
197  // To match the original implementation, we try to keep the same
198  // variable names with reference to the original code. Compared to the
199  // original implementation, parallelization is done in im_dst_tensor
200  // per-pixel level, instead of per-image level. Check out the original
201  // code at: http://redwood-data.org/indoor/data/simdepth.py.
203  core::Device("CPU:0"), width * height,
204  [&] CLOUDVIEWER_DEVICE(int workload_idx) {
205  // TArrayIndexer has reverted coordinate order, use (c, r).
206  int r;
207  int c;
208  src_indexer.WorkloadToCoord(workload_idx, &c, &r);
209 
210  // Pixel shuffle.
211  int x, y;
212  float x_noise = deterministic_debug_mode_ ? 0 : gen_coord();
213  float y_noise = deterministic_debug_mode_ ? 0 : gen_coord();
214  x = std::min(std::max(static_cast<int>(round(c + x_noise)), 0),
215  width - 1);
216  y = std::min(std::max(static_cast<int>(round(r + y_noise)), 0),
217  height - 1);
218 
219  // Down sample.
220  float d = *src_indexer.GetDataPtr<float>(x - x % 2, y - y % 2);
221 
222  // Distortion.
223  int i2 = static_cast<int>((d + 1) / 2);
224  int i1 = i2 - 1;
225  float a_ = (d - (i1 * 2 + 1)) / 2;
226  int x_ = static_cast<int>(x / 8);
227  int y_ = static_cast<int>(y / 6);
228  float model_val0 = *model_indexer.GetDataPtr<float>(
229  std::min(std::max(i1, 0), 4), x_, y_);
230  float model_val1 = *model_indexer.GetDataPtr<float>(
231  std::min(i2, 4), x_, y_);
232  float f = (1 - a_) * model_val0 + a_ * model_val1;
233  if (f == 0) {
234  d = 0;
235  } else {
236  d = d / f;
237  }
238 
239  // Quantization and high freq noise.
240  float dst_d;
241  if (d == 0) {
242  dst_d = 0;
243  } else {
244  float d_noise = deterministic_debug_mode_ ? 0 : gen_depth();
245  dst_d = 35.130 * 8 / round((35.130 / d + d_noise) * 8);
246  }
247  *dst_indexer.GetDataPtr<float>(c, r) = dst_d;
248  });
249 
250  if (original_dtype == core::UInt16) {
251  im_dst_tensor = (im_dst_tensor * depth_scale).To(core::UInt16);
252  }
253  im_dst_tensor = im_dst_tensor.To(original_device);
254 
255  return geometry::Image(im_dst_tensor);
256 }
257 
258 } // namespace io
259 } // namespace t
260 } // namespace cloudViewer
#define CLOUDVIEWER_DEVICE
Definition: CUDAUtils.h:45
std::string filename
std::shared_ptr< core::Tensor > image
int width
int height
std::string ToString() const
Definition: Dtype.h:65
Tensor Contiguous() const
Definition: Tensor.cpp:772
Dtype GetDtype() const
Definition: Tensor.h:1164
static Tensor Zeros(const SizeVector &shape, Dtype dtype, const Device &device=Device("CPU:0"))
Create a tensor fill with zeros.
Definition: Tensor.cpp:406
Device GetDevice() const override
Definition: Tensor.cpp:1435
Tensor Clone() const
Copy Tensor to the same device.
Definition: Tensor.h:502
Tensor To(Dtype dtype, bool copy=false) const
Definition: Tensor.cpp:739
The Image class stores image with customizable rows, cols, channels, dtype and device.
Definition: Image.h:29
core::Dtype GetDtype() const
Get dtype of the image.
Definition: Image.h:93
core::Tensor AsTensor() const
Returns the underlying Tensor of the Image.
Definition: Image.h:124
int64_t GetChannels() const
Get the number of channels of the image.
Definition: Image.h:90
int64_t GetCols() const
Get the number of columns of the image.
Definition: Image.h:87
int64_t GetRows() const
Get the number of rows of the image.
Definition: Image.h:84
CLOUDVIEWER_HOST_DEVICE void WorkloadToCoord(index_t workload, index_t *x_out, index_t *y_out) const
Workload => 2D coordinate.
CLOUDVIEWER_HOST_DEVICE void * GetDataPtr() const
DepthNoiseSimulator(const std::string &noise_model_path)
Constructor.
Definition: ImageIO.cpp:99
geometry::Image Simulate(const geometry::Image &im_src, float depth_scale=1000.0)
Apply noise model to a depth image.
Definition: ImageIO.cpp:158
bool Open(const std::string &filename, const std::string &mode)
Open a file.
Definition: FileSystem.cpp:739
#define LogWarning(...)
Definition: Logging.h:72
#define LogError(...)
Definition: Logging.h:60
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
void ParallelFor(const Device &device, int64_t n, const func_t &func)
Definition: ParallelFor.h:111
const Dtype UInt16
Definition: Dtype.cpp:49
const Dtype Float32
Definition: Dtype.cpp:42
void To(const core::Tensor &src, core::Tensor &dst, double scale, double offset)
Definition: Image.cpp:17
bool ReadImageFromPNG(const std::string &filename, geometry::Image &image)
Definition: FilePNG.cpp:39
bool ReadImage(const std::string &filename, geometry::Image &image)
Definition: ImageIO.cpp:55
bool WriteImageToPNG(const std::string &filename, const geometry::Image &image, int quality)
Definition: FilePNG.cpp:75
std::shared_ptr< geometry::Image > CreateImageFromFile(const std::string &filename)
Definition: ImageIO.cpp:48
bool WriteImage(const std::string &filename, const geometry::Image &image, int quality)
Definition: ImageIO.cpp:79
bool WriteImageToJPG(const std::string &filename, const geometry::Image &image, int quality)
Definition: FileJPG.cpp:113
bool ReadImageFromJPG(const std::string &filename, geometry::Image &image)
Definition: FileJPG.cpp:41
std::string GetFileExtensionInLowerCase(const std::string &filename)
Definition: FileSystem.cpp:281
Generic file read and write utility for python interface.