1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 16 #include "tensorflow/core/framework/op.h" 17 #include "tensorflow/core/framework/op_kernel.h" 18 #include "tensorflow/core/framework/shape_inference.h" 19 20 namespace tensorflow { 21 22 template <typename T> 23 class SingleImageRandomDotStereogramsOp : public OpKernel { 24 private: 25 int E2Epixels; // Pixels from eye to eye = eye_to_eye_inches * DPI 26 27 int input_Xvalue; // X value of input Z values (width) 28 int input_Yvalue; // Y value of input Z values (height) 29 30 int output_Ximage; // X value of output image (width) 31 int output_Yimage; // Y value of output image (height) 32 int output_Cimage; // color value of output image (color, 1 or 3) (3 not 33 // implemented) 34 35 int data_box_left; // X starting value for DATA window 36 int data_box_top; // Y starting value for DATA window 37 int data_box_width; // width of scan line 38 int data_box_height; // hight of image 39 40 int converge_dot_box_end; // Row convergences dots end on 41 42 uint8* outputImage; // Output Image flat as a buffer (Tensor Connection) 43 double* ZBuffer; // For internal use, allow for MASK, etc later, actual Z 44 // used for Stereogram, XxY (X is the row index, y is col 45 // index like a screen) 46 // 0 (far) -> 1.0(near) range 47 bool hidden_surface_removal; 48 int convergence_dots_size; 49 int dots_per_inch; 50 float eye_separation; 51 float mu; 52 bool normalize; 53 float normalize_max; 54 float normalize_min; 55 float border_level; 56 int number_colors; 57 ::tensorflow::PartialTensorShape output_image_shape; 58 ::tensorflow::PartialTensorShape output_data_window; 59 60 uint8 Cblack = 0; 61 uint8 Cwhite = 255; 62 63 int indexMode = 0; // 0 - truncate XY, 1 - round XY, 2 - Interpolate XY (not 64 // implemented yet, keep default of 0) 65 int interp_x, interp_y; // 1 - yes, 0 - no interpolation directions (not 66 // implemented yet) 67 68 bool debugging = false; 69 separation(double z)70 inline int separation(double z) { 71 return (std::round((1 - mu * z) * E2Epixels / (2 - mu * z))); 72 } 73 get_far_width()74 inline int get_far_width() { return (separation(0.0)); } get_near_width()75 inline int get_near_width() { return (separation(1.0)); } 76 77 public: SingleImageRandomDotStereogramsOp(OpKernelConstruction * context)78 explicit SingleImageRandomDotStereogramsOp(OpKernelConstruction* context) 79 : OpKernel(context) { // Constructor 80 OP_REQUIRES_OK(context, context->GetAttr("hidden_surface_removal", 81 &hidden_surface_removal)); 82 OP_REQUIRES_OK(context, context->GetAttr("convergence_dots_size", 83 &convergence_dots_size)); 84 OP_REQUIRES_OK(context, context->GetAttr("dots_per_inch", &dots_per_inch)); 85 OP_REQUIRES_OK(context, 86 context->GetAttr("eye_separation", &eye_separation)); 87 OP_REQUIRES_OK(context, context->GetAttr("mu", &mu)); 88 OP_REQUIRES_OK(context, context->GetAttr("normalize", &normalize)); 89 OP_REQUIRES_OK(context, context->GetAttr("normalize_max", &normalize_max)); 90 OP_REQUIRES_OK(context, context->GetAttr("normalize_min", &normalize_min)); 91 OP_REQUIRES_OK(context, context->GetAttr("border_level", &border_level)); 92 OP_REQUIRES_OK(context, context->GetAttr("number_colors", &number_colors)); 93 OP_REQUIRES_OK(context, 94 context->GetAttr("output_image_shape", &output_image_shape)); 95 OP_REQUIRES_OK(context, 96 context->GetAttr("output_data_window", &output_data_window)); 97 98 E2Epixels = 99 eye_separation * dots_per_inch; // Initialize pixels from eye to eye 100 } 101 ~SingleImageRandomDotStereogramsOp()102 ~SingleImageRandomDotStereogramsOp() override { // Destructor 103 } 104 Compute(OpKernelContext * context)105 void Compute(OpKernelContext* context) override { 106 const Tensor& input_tensor = context->input(0); 107 input_Xvalue = input_tensor.shape().dim_size( 108 1); // X value is the number of columns of the input matrix 109 input_Yvalue = 110 input_tensor.shape().dim_size(0); // Y value is the number of rows 111 112 output_Ximage = output_image_shape.dim_size(0); 113 output_Yimage = output_image_shape.dim_size(1); 114 output_Cimage = output_image_shape.dim_size(2); 115 116 if (number_colors > 256) // Go to full color image 117 output_Cimage = 3; 118 119 int data_Xwindow = output_data_window.dim_size(0); 120 int data_Ywindow = output_data_window.dim_size(1); 121 122 int deltaX_border_image = output_Ximage - data_Xwindow; 123 int deltaY_border_image = output_Yimage - data_Ywindow; 124 125 if (convergence_dots_size > 126 0) // 3 frame sections in Y direction due to DOTS 127 { 128 deltaY_border_image = 129 deltaY_border_image - 130 convergence_dots_size; // Take off space for Convergence Dots 131 deltaY_border_image = std::max(0, deltaY_border_image); 132 data_box_top = deltaY_border_image / 3; 133 134 if (deltaY_border_image >= 0) { 135 converge_dot_box_end = output_Yimage - 1 - data_box_top; 136 } else { 137 converge_dot_box_end = output_Yimage - 1; 138 } 139 } else // Otherwise only 2, no convergence dot 140 { 141 data_box_top = deltaY_border_image / 2; // Center DATA in Y dimension 142 converge_dot_box_end = output_Yimage - 1; 143 } 144 145 data_box_left = deltaX_border_image / 2; // Center DATA in X dimension 146 data_box_width = data_Xwindow; // width of scan line 147 data_box_height = data_Ywindow; // hight of image 148 149 const T* inputZ = input_tensor.flat<T>().data(); // Flatten input Z buffer 150 151 BuildZBuffer(inputZ); 152 153 // Output a scalar string. 154 Tensor* output_tensor = nullptr; 155 OP_REQUIRES_OK( 156 context, 157 context->allocate_output( 158 0, TensorShape({output_Yimage, output_Ximage, output_Cimage}), 159 &output_tensor)); 160 161 outputImage = output_tensor->flat<uint8>().data(); 162 163 generate_stereogram(); 164 165 delete[] ZBuffer; 166 } 167 168 //*************************************************************************** 169 //*************************************************************************** 170 // Move input into standard Z format to reduce complexity of algorithm 171 // BuildZBuffer(const T * Z,bool log=false)172 void BuildZBuffer(const T* Z, bool log = false) { 173 double MaxValue = 1.0; 174 double MinValue = 0.0; 175 ZBuffer = new double[input_Xvalue * input_Yvalue]; // Used to computer 176 // final Z values before 177 // rendering to output 178 179 if (normalize) { 180 // Init Min/Max to first value 181 if (normalize_max < normalize_min) // Autoscale if MIN>MAX 182 { 183 MaxValue = *Z; 184 MinValue = *Z; 185 186 for (int y = 0; y < input_Yvalue; ++y) 187 for (int x = 0; x < input_Xvalue; ++x) { 188 double value = getZfromInputImage(Z, x, y); 189 if (value > MaxValue) MaxValue = value; 190 if (value < MinValue) MinValue = value; 191 } 192 } else { 193 MaxValue = normalize_max; 194 MinValue = normalize_min; 195 } 196 } 197 198 for (int y = 0; y < input_Yvalue; ++y) 199 for (int x = 0; x < input_Xvalue; ++x) { 200 double value = getZfromInputImage(Z, x, y); 201 202 if (normalize) { 203 value = (value - MinValue) / (MaxValue - MinValue); 204 } 205 206 if (value > 1.0) value = 1.0; 207 if (value < 0.0) value = 0.0; 208 209 *(ZBuffer + (input_Xvalue * y + x)) = value; 210 } 211 } 212 213 //*************************************************************************** 214 //*************************************************************************** getZfromInputImage(const T * Z,int x,int y)215 double getZfromInputImage(const T* Z, int x, int y) { 216 return *(Z + input_Xvalue * y + x); 217 } 218 219 //*************************************************************************** 220 //*************************************************************************** 221 // All normalized, not checking required 222 // Possible Projection issue if DATA is bigger or smaller than Input 223 // Modes include: 224 // Truncate value (Default) 225 // Round-off value 226 // Interpolate between values 227 // getZfromZbuffer(double x,double y)228 double getZfromZbuffer(double x, double y) { 229 int xi, yi; 230 231 switch (indexMode) { 232 case 0: // Truncate 233 xi = int(x); 234 yi = int(y); 235 return (*(ZBuffer + (xi + input_Xvalue * yi))); 236 break; 237 case 1: // Round-off 238 xi = std::round(x); 239 yi = std::round(y); 240 return (*(ZBuffer + (xi + input_Xvalue * yi))); 241 break; 242 case 2: // Interpolate (Not implemented yet, will need 4 points 243 // [x,y],[x+1,y],[x,y+1],[x+1,y+1], then interpolate) 244 xi = int(x); 245 yi = int(y); 246 return (*(ZBuffer + (xi + input_Xvalue * yi))); 247 break; 248 default: // Round-off is the default 249 xi = int(x + 0.5); 250 yi = int(y + 0.5); 251 return (*(ZBuffer + (xi + input_Xvalue * yi))); 252 break; 253 } 254 } 255 256 //*************************************************************************** 257 //*************************************************************************** 258 getOutputImageIndex(int x,int y,int channel)259 int getOutputImageIndex(int x, int y, 260 int channel) { // No error checking for some 261 // optimization, calling routine 262 // required to make sure there is no 263 // violation 264 return ((output_Ximage * output_Cimage) * y + x * output_Cimage + channel); 265 } 266 267 //*************************************************************************** 268 //*************************************************************************** 269 getZFromOutputPixel(int x,int y)270 double getZFromOutputPixel(int x, int y) { 271 // Convert pixel units to Z units, do this as "double" 272 double xofz = static_cast<double>(input_Xvalue) * (x - data_box_left) / 273 (static_cast<double>(data_box_width)); 274 double yofz = static_cast<double>(input_Yvalue) * (y - data_box_top) / 275 (static_cast<double>(data_box_height)); 276 277 if ((xofz < 0) || (yofz < 0) || (yofz >= input_Yvalue) || 278 (xofz >= input_Xvalue)) { // Top of left side border hit or Right 279 // side or bottom border hit, 280 // send BORDER Z value 281 return border_level; 282 } else { 283 return getZfromZbuffer(xofz, yofz); 284 } 285 } 286 287 //*************************************************************************** 288 //*************************************************************************** 289 generate_stereogram()290 void generate_stereogram() { 291 int s, left, right, visible, t, l; 292 double zt, gz; 293 // Scan line 294 uint8* pix; // Scan row color for each pixel 295 int* same; // Used to determine if Pixel needs to be the same as another 296 // pixel in the row 297 298 pix = new uint8[output_Ximage * output_Cimage]; 299 same = new int[output_Ximage]; 300 301 for (int y = 0; y < output_Yimage; ++y) { 302 // Set no dependencies on any pixels, tie each one back to itself 303 for (int x = 0; x < output_Ximage; ++x) same[x] = x; 304 305 for (int x = 0; x < output_Ximage; ++x) { 306 gz = getZFromOutputPixel(x, y); 307 s = separation(gz); 308 left = x - s / 2; 309 right = left + s; 310 311 if ((left >= 0) && (right < output_Ximage)) { 312 t = 1; 313 visible = 1; 314 if (hidden_surface_removal) do { 315 zt = gz + 2 * (2 - mu * gz) * t / (mu * E2Epixels); 316 visible = (getZFromOutputPixel(x - t, y) < zt) && 317 (getZFromOutputPixel(x + t, y) < zt); 318 ++t; 319 } while ((visible) && (zt < 1)); 320 321 if (visible) { 322 l = same[left]; 323 while ((l != left) && (l != right)) 324 if (l < right) { 325 left = l; 326 l = same[left]; 327 } else { 328 same[left] = right; 329 left = right; 330 l = same[left]; 331 right = l; 332 } 333 same[left] = right; 334 } 335 } 336 } 337 // Set colors for scan row, use channels and number_colors 338 for (int x = output_Ximage - 1; x >= 0; x--) { 339 for (int channel = 0; channel < output_Cimage; ++channel) { 340 if (same[x] == x) { // Pick a random color 341 if (number_colors == 2) { 342 if ((rand() % 2) == 0) { 343 pix[x * output_Cimage + channel] = Cblack; 344 } else { 345 pix[x * output_Cimage + channel] = Cwhite; 346 } 347 } else { 348 pix[x * output_Cimage + channel] = rand() % 256; 349 } 350 } else 351 pix[x * output_Cimage + channel] = 352 pix[same[x] * output_Cimage + channel]; 353 354 setpixel(x, y, channel, pix[x * output_Cimage + channel]); 355 } 356 } 357 } 358 359 draw_convergence_dots(); 360 361 delete[] pix; 362 delete[] same; 363 } 364 365 //*************************************************************************** 366 //*************************************************************************** 367 draw_convergence_dots()368 void draw_convergence_dots() { 369 int x1, x2; // center position for convergence dots 370 371 if (convergence_dots_size == 0) // No dot, return 372 return; 373 374 x1 = output_Ximage / 2 - get_far_width() / 2; 375 x2 = output_Ximage / 2 + get_far_width() / 2; 376 377 for (int lloop = 0; lloop < convergence_dots_size; ++lloop) 378 for (int wloop = 0; wloop < convergence_dots_size; ++wloop) 379 for (int channel = 0; channel < output_Cimage; ++channel) { 380 setpixel(x1 - (convergence_dots_size / 2) + wloop, 381 converge_dot_box_end - lloop, channel, Cblack); 382 setpixel(x2 - (convergence_dots_size / 2) + wloop, 383 converge_dot_box_end - lloop, channel, Cblack); 384 } 385 } 386 387 //*************************************************************************** 388 //*************************************************************************** 389 setpixel(int x,int y,int channel,uint8 color)390 void setpixel(int x, int y, int channel, uint8 color) { 391 *(outputImage + getOutputImageIndex(x, y, channel)) = color; 392 } 393 }; 394 395 #define REGISTER_KERNEL(T) \ 396 REGISTER_KERNEL_BUILDER(Name("SingleImageRandomDotStereograms") \ 397 .Device(DEVICE_CPU) \ 398 .TypeConstraint<T>("T"), \ 399 SingleImageRandomDotStereogramsOp<T>); 400 401 REGISTER_KERNEL(int32); 402 REGISTER_KERNEL(int64); 403 REGISTER_KERNEL(float); 404 REGISTER_KERNEL(double); 405 406 #undef REGISTER_KERNEL 407 408 } // end namespace tensorflow 409