<commit_before>/* Copyright 2016 Carnegie Mellon University, NVIDIA Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "lightscan/storage/storage_config.h"
#include "lightscan/storage/storage_backend.h"
#include "lightscan/util/common.h"
#include "lightscan/util/video.h"
#include "lightscan/util/caffe.h"

#include <opencv2/opencv.hpp>

#include <mpi.h>
#include <pthread.h>
#include <cstdlib>
#include <string>
#include <libgen.h>
#include <atomic>

extern "C" {
#include "libavformat/avformat.h"
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}

using namespace lightscan;

const std::string DB_PATH = "/Users/abpoms/kcam";
const std::string IFRAME_PATH_POSTFIX = "_iframes";
const std::string METADATA_PATH_POSTFIX = "_metadata";
const std::string PROCESSED_VIDEO_POSTFIX = "_processed";
const int NUM_GPUS = 1;
const int BATCH_SIZE = 1;

#define THREAD_RETURN_SUCCESS() \
  do {                                           \
    void* val = malloc(sizeof(int));             \
    *((int*)val) = EXIT_SUCCESS;                 \
    pthread_exit(val);                           \
  } while (0);

///////////////////////////////////////////////////////////////////////////////
/// Path utils

std::string dirname_s(const std::string& path) {
  char* path_copy = strdup(path.c_str());
  char* dir = dirname(path_copy);
  return std::string(dir);
}

std::string basename_s(const std::string& path) {
  char* path_copy = strdup(path.c_str());
  char* base = basename(path_copy);
  return std::string(base);
}

std::string processed_video_path(const std::string& video_path) {
  return dirname_s(video_path) + "/" +
    basename_s(video_path) + PROCESSED_VIDEO_POSTFIX + ".mp4";
}

std::string metadata_path(const std::string& video_path) {
  return dirname_s(video_path) + "/" +
    basename_s(video_path) + METADATA_PATH_POSTFIX + ".bin";
}

std::string iframe_path(const std::string& video_path) {
  return dirname_s(video_path) + "/" +
    basename_s(video_path) + IFRAME_PATH_POSTFIX + ".bin";
}

///////////////////////////////////////////////////////////////////////////////
/// MPI utils
inline bool is_master(int rank) {
  return rank == 0;
}

///////////////////////////////////////////////////////////////////////////////
///

void startup(int argc, char** argv) {
  MPI_Init(&argc, &argv);
  av_register_all();
  FLAGS_minloglevel = 2;
}

///////////////////////////////////////////////////////////////////////////////
/// Thread to asynchronously load video
void convert_av_frame_to_rgb(
  SwsContext*& sws_context,
  AVFrame* frame,
  char* buffer)
{
  size_t buffer_size =
    av_image_get_buffer_size(AV_PIX_FMT_RGB24, frame->width, frame->height, 1);

  // Convert image to RGB
  sws_context = sws_getCachedContext(
    sws_context,

    frame->width, frame->height,
    static_cast<AVPixelFormat>(frame->format),

    frame->width, frame->height, AV_PIX_FMT_RGB24,
    SWS_BICUBIC, 0, 0, 0);

  if (sws_context == nullptr) {
    fprintf(stderr, "Error trying to get sws context\n");
    assert(false);
  }

  AVFrame rgb_format;
  int alloc_fail = av_image_alloc(rgb_format.data,
                                  rgb_format.linesize,
                                  frame->width,
                                  frame->height,
                                  AV_PIX_FMT_RGB24,
                                  1);

  if (alloc_fail < 0) {
    fprintf(stderr, "Error while allocating avpicture for conversion\n");
    assert(false);
  }

  sws_scale(sws_context,
            frame->data /* input data */,
            frame->linesize /* input layout */,
            0 /* x start location */,
            frame->height /* height of input image */,
            rgb_format.data /* output data */,
            rgb_format.linesize /* output layout */);

  av_image_copy_to_buffer(reinterpret_cast<uint8_t*>(buffer),
                          buffer_size,
                          rgb_format.data,
                          rgb_format.linesize,
                          AV_PIX_FMT_RGB24,
                          frame->width,
                          frame->height,
                          1);

  av_freep(&rgb_format.data[0]);
}

struct LoadVideoArgs {
  // Input arguments
  StorageConfig* storage_config;
  std::string video_path;
  std::string iframe_path;
  int frame_start;
  int frame_end;
  VideoMetadata metadata;
  // Output arguments
  size_t frames_buffer_size;
  char* decoded_frames_buffer; // Should have space for start - end frames
  std::atomic<int>* frames_written;
};

void* load_video_thread(void* arg) {
  // Setup connection to load video
  LoadVideoArgs& args = *reinterpret_cast<LoadVideoArgs*>(arg);

  // Setup a distinct storage backend for each IO thread
  StorageBackend* storage =
    StorageBackend::make_from_config(args.storage_config);

  // Open the iframe file to setup keyframe data
  std::vector<int> keyframe_positions;
  std::vector<int64_t> keyframe_timestamps;
  {
    RandomReadFile* iframe_file;
    storage->make_random_read_file(args.iframe_path, iframe_file);

    (void)read_keyframe_info(
      iframe_file, 0, keyframe_positions, keyframe_timestamps);

    delete iframe_file;
  }

  // Open the video file for reading
  RandomReadFile* file;
  storage->make_random_read_file(args.video_path, file);

  VideoDecoder decoder(file, keyframe_positions, keyframe_timestamps);
  decoder.seek(args.frame_start);

  size_t frame_size =
    av_image_get_buffer_size(AV_PIX_FMT_RGB24,
                             args.metadata.width,
                             args.metadata.height,
                             1);

  SwsContext* sws_context;
  int current_frame = args.frame_start;
  while (current_frame < args.frame_end) {
    AVFrame* frame = decoder.decode();
    assert(frame != nullptr);

    size_t frames_buffer_offset =
      frame_size * (current_frame - args.frame_start);
    assert(frames_buffer_offset < args.frames_buffer_size);
    char* current_frame_buffer_pos =
      args.decoded_frames_buffer + frames_buffer_offset;

    convert_av_frame_to_rgb(sws_context, frame, current_frame_buffer_pos);

    *args.frames_written += 1;
    current_frame += 1;
  }

  // Cleanup
  delete file;
  delete storage;

  THREAD_RETURN_SUCCESS();
}

///////////////////////////////////////////////////////////////////////////////
/// Thread to asynchronously save out results
struct SaveVideoArgs {
};

void* save_video_thread(void* arg) {
  // Setup connection to save video
  THREAD_RETURN_SUCCESS();
}

///////////////////////////////////////////////////////////////////////////////
/// Main processing thread that runs the read, evaluate net, write loop
struct ProcessArgs {
  int gpu_device_id;
  StorageConfig* storage_config;
  std::string video_path;
  std::string iframe_path;
  int frame_start;
  int frame_end;
  VideoMetadata metadata;
};

void* process_thread(void* arg) {
  ProcessArgs& args = *reinterpret_cast<ProcessArgs*>(arg);

  size_t frame_size =
    av_image_get_buffer_size(AV_PIX_FMT_RGB24,
                             args.metadata.width,
                             args.metadata.height,
                             1);
  size_t frame_buffer_size = frame_size * (args.frame_end - args.frame_start);
  char* frame_buffer = new char[frame_buffer_size];
  std::atomic<int> frames_written{0};

  // Create IO threads for reading and writing
  LoadVideoArgs load_args;
  load_args.storage_config = args.storage_config;
  load_args.video_path = args.video_path;
  load_args.iframe_path = args.iframe_path;
  load_args.frame_start = args.frame_start;
  load_args.frame_end = args.frame_end;
  load_args.metadata = args.metadata;
  load_args.frames_buffer_size = frame_buffer_size;
  load_args.decoded_frames_buffer = frame_buffer;
  load_args.frames_written = &frames_written;
  pthread_t load_thread;
  pthread_create(&load_thread, NULL, load_video_thread, &load_args);

  // pthread_t* save_thread;
  // pthread_create(save_thread, NULL, save_video_thread, NULL);

  // Setup caffe net
  NetInfo net_info = load_neural_net(NetType::ALEX_NET, args.gpu_device_id);
  caffe::Net<float>* net = net_info.net;

  // Resize net input blob for batch size
  const boost::shared_ptr<caffe::Blob<float>> data_blob{
    net->blob_by_name("data")};
  if (data_blob->shape(0) != BATCH_SIZE) {
    data_blob->Reshape({
        BATCH_SIZE, 3, net_info.input_size, net_info.input_size});
  }

  int dim = net_info.input_size;

  cv::Mat unsized_mean_mat(
    net_info.mean_width, net_info.mean_height, CV_32FC3, net_info.mean_image);
  cv::Mat mean_mat;
  cv::resize(unsized_mean_mat, mean_mat, cv::Size(dim, dim));

  int current_frame = args.frame_start;
  while (current_frame + BATCH_SIZE < args.frame_end) {
    // Read batch of frames
    if ((current_frame + BATCH_SIZE - args.frame_start) >= frames_written)
      continue;

    // Decompress batch of frame
    printf("processing frame %d\n", current_frame);

    // Process batch of frames
    caffe::Blob<float> net_input{BATCH_SIZE, 3, dim, dim};
    float* net_input_buffer = net_input.mutable_cpu_data();

    for (int i = 0; i < BATCH_SIZE; ++i) {
      char* buffer = frame_buffer + frame_size * (i + current_frame);
      cv::Mat input_mat(
        args.metadata.height, args.metadata.width, CV_8UC3, buffer);
      cv::cvtColor(input_mat, input_mat, CV_RGB2BGR);
      cv::Mat conv_input;
      cv::resize(input_mat, conv_input, cv::Size(dim, dim));
      cv::Mat float_conv_input;
      conv_input.convertTo(float_conv_input, CV_32FC3);
      cv::Mat normed_input = float_conv_input - mean_mat;
      //to_conv_input(&std::get<0>(in_vec[i]), &conv_input, &mean);
      memcpy(net_input_buffer + i * (dim * dim * 3),
             normed_input.data,
             dim * dim * 3 * sizeof(float));
    }

    net->Forward({&net_input});

    // Save batch of frames

    current_frame += BATCH_SIZE;
  }

  // Epilogue for processing less than a batch of frames

  // Cleanup
  delete[] frame_buffer;
  delete net;

  THREAD_RETURN_SUCCESS();
}

void shutdown() {
  MPI_Finalize();
}

int main(int argc, char **argv) {
  startup(argc, argv);

  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  std::string video_path =
    "kcam-videos-20140910_195012_247.mp4";

  // Setup storage config
  StorageConfig* config =
    StorageConfig::make_disk_config(DB_PATH);
  StorageBackend* storage = StorageBackend::make_from_config(config);

  // Check if we have already preprocessed the video
  FileInfo video_info;
  StoreResult result =
    storage->get_file_info(processed_video_path(video_path), video_info);
  if (result == StoreResult::FileDoesNotExist) {
    // Preprocess video and then exit
    if (is_master(rank)) {
      log_ls.print("Video not processed yet. Processing now...\n");
      //video_path = "../../../tmp/lightscan3n1YnH";
      //video_path = "../../../tmp/lightscanLNaRk3";
      preprocess_video(storage,
                       video_path,
                       processed_video_path(video_path),
                       metadata_path(video_path),
                       iframe_path(video_path));
    }
  } else {
    // Get video metadata to pass to all workers and determine work distribution
    // from frame count
    VideoMetadata metadata;
    {
      std::unique_ptr<RandomReadFile> metadata_file;
      exit_on_error(
        make_unique_random_read_file(storage,
                                     metadata_path(video_path),
                                     metadata_file));
      (void) read_video_metadata(metadata_file.get(), 0, metadata);
    }

    // Parse args to determine video offset

    // Create processing threads for each gpu
    ProcessArgs processing_thread_args[NUM_GPUS];
    pthread_t processing_threads[NUM_GPUS];
    for (int i = 0; i < NUM_GPUS; ++i) {
      ProcessArgs& args = processing_thread_args[i];
      args.gpu_device_id = i;
      args.storage_config = config;
      args.video_path = video_path;
      args.iframe_path = iframe_path(video_path);
      args.frame_start = 0;
      args.frame_end = 2000;
      args.metadata = metadata;
      pthread_create(&processing_threads[i],
                     NULL,
                     process_thread,
                     &processing_thread_args[i]);
    }

    // Wait till done
    for (int i = 0; i < NUM_GPUS; ++i) {
      void* result;

      int err = pthread_join(processing_threads[i], &result);
      if (err != 0) {
        fprintf(stderr, "error in pthread_join\n");
        exit(EXIT_FAILURE);
      }

      printf("Joined with thread %d; returned value was %d\n",
             i, *((int *)result));
      free(result);      /* Free memory allocated by thread */
    }
  }

 // Cleanup
 delete storage;
 delete config;

 shutdown();

 return EXIT_SUCCESS;
}
<commit_msg>Distribute frames evenly among GPUs<commit_after>/* Copyright 2016 Carnegie Mellon University, NVIDIA Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "lightscan/storage/storage_config.h"
#include "lightscan/storage/storage_backend.h"
#include "lightscan/util/common.h"
#include "lightscan/util/video.h"
#include "lightscan/util/caffe.h"

#include <opencv2/opencv.hpp>

#include <mpi.h>
#include <pthread.h>
#include <cstdlib>
#include <string>
#include <libgen.h>
#include <atomic>

extern "C" {
#include "libavformat/avformat.h"
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}

using namespace lightscan;

const std::string DB_PATH = "/Users/abpoms/kcam";
const std::string IFRAME_PATH_POSTFIX = "_iframes";
const std::string METADATA_PATH_POSTFIX = "_metadata";
const std::string PROCESSED_VIDEO_POSTFIX = "_processed";
const int NUM_GPUS = 1;
const int BATCH_SIZE = 8;

#define THREAD_RETURN_SUCCESS() \
  do {                                           \
    void* val = malloc(sizeof(int));             \
    *((int*)val) = EXIT_SUCCESS;                 \
    pthread_exit(val);                           \
  } while (0);

///////////////////////////////////////////////////////////////////////////////
/// Path utils

std::string dirname_s(const std::string& path) {
  char* path_copy = strdup(path.c_str());
  char* dir = dirname(path_copy);
  return std::string(dir);
}

std::string basename_s(const std::string& path) {
  char* path_copy = strdup(path.c_str());
  char* base = basename(path_copy);
  return std::string(base);
}

std::string processed_video_path(const std::string& video_path) {
  return dirname_s(video_path) + "/" +
    basename_s(video_path) + PROCESSED_VIDEO_POSTFIX + ".mp4";
}

std::string metadata_path(const std::string& video_path) {
  return dirname_s(video_path) + "/" +
    basename_s(video_path) + METADATA_PATH_POSTFIX + ".bin";
}

std::string iframe_path(const std::string& video_path) {
  return dirname_s(video_path) + "/" +
    basename_s(video_path) + IFRAME_PATH_POSTFIX + ".bin";
}

///////////////////////////////////////////////////////////////////////////////
/// MPI utils
inline bool is_master(int rank) {
  return rank == 0;
}

///////////////////////////////////////////////////////////////////////////////
///

void startup(int argc, char** argv) {
  MPI_Init(&argc, &argv);
  av_register_all();
  FLAGS_minloglevel = 2;
}

///////////////////////////////////////////////////////////////////////////////
/// Thread to asynchronously load video
void convert_av_frame_to_rgb(
  SwsContext*& sws_context,
  AVFrame* frame,
  char* buffer)
{
  size_t buffer_size =
    av_image_get_buffer_size(AV_PIX_FMT_RGB24, frame->width, frame->height, 1);

  // Convert image to RGB
  sws_context = sws_getCachedContext(
    sws_context,

    frame->width, frame->height,
    static_cast<AVPixelFormat>(frame->format),

    frame->width, frame->height, AV_PIX_FMT_RGB24,
    SWS_BICUBIC, 0, 0, 0);

  if (sws_context == nullptr) {
    fprintf(stderr, "Error trying to get sws context\n");
    assert(false);
  }

  AVFrame rgb_format;
  int alloc_fail = av_image_alloc(rgb_format.data,
                                  rgb_format.linesize,
                                  frame->width,
                                  frame->height,
                                  AV_PIX_FMT_RGB24,
                                  1);

  if (alloc_fail < 0) {
    fprintf(stderr, "Error while allocating avpicture for conversion\n");
    assert(false);
  }

  sws_scale(sws_context,
            frame->data /* input data */,
            frame->linesize /* input layout */,
            0 /* x start location */,
            frame->height /* height of input image */,
            rgb_format.data /* output data */,
            rgb_format.linesize /* output layout */);

  av_image_copy_to_buffer(reinterpret_cast<uint8_t*>(buffer),
                          buffer_size,
                          rgb_format.data,
                          rgb_format.linesize,
                          AV_PIX_FMT_RGB24,
                          frame->width,
                          frame->height,
                          1);

  av_freep(&rgb_format.data[0]);
}

struct LoadVideoArgs {
  // Input arguments
  StorageConfig* storage_config;
  std::string video_path;
  std::string iframe_path;
  int frame_start;
  int frame_end;
  VideoMetadata metadata;
  // Output arguments
  size_t frames_buffer_size;
  char* decoded_frames_buffer; // Should have space for start - end frames
  std::atomic<int>* frames_written;
};

void* load_video_thread(void* arg) {
  // Setup connection to load video
  LoadVideoArgs& args = *reinterpret_cast<LoadVideoArgs*>(arg);

  // Setup a distinct storage backend for each IO thread
  StorageBackend* storage =
    StorageBackend::make_from_config(args.storage_config);

  // Open the iframe file to setup keyframe data
  std::vector<int> keyframe_positions;
  std::vector<int64_t> keyframe_timestamps;
  {
    RandomReadFile* iframe_file;
    storage->make_random_read_file(args.iframe_path, iframe_file);

    (void)read_keyframe_info(
      iframe_file, 0, keyframe_positions, keyframe_timestamps);

    delete iframe_file;
  }

  // Open the video file for reading
  RandomReadFile* file;
  storage->make_random_read_file(args.video_path, file);

  VideoDecoder decoder(file, keyframe_positions, keyframe_timestamps);
  decoder.seek(args.frame_start);

  size_t frame_size =
    av_image_get_buffer_size(AV_PIX_FMT_RGB24,
                             args.metadata.width,
                             args.metadata.height,
                             1);

  SwsContext* sws_context;
  int current_frame = args.frame_start;
  while (current_frame < args.frame_end) {
    AVFrame* frame = decoder.decode();
    assert(frame != nullptr);

    size_t frames_buffer_offset =
      frame_size * (current_frame - args.frame_start);
    assert(frames_buffer_offset < args.frames_buffer_size);
    char* current_frame_buffer_pos =
      args.decoded_frames_buffer + frames_buffer_offset;

    convert_av_frame_to_rgb(sws_context, frame, current_frame_buffer_pos);

    *args.frames_written += 1;
    current_frame += 1;
  }

  // Cleanup
  delete file;
  delete storage;

  THREAD_RETURN_SUCCESS();
}

///////////////////////////////////////////////////////////////////////////////
/// Thread to asynchronously save out results
struct SaveVideoArgs {
};

void* save_video_thread(void* arg) {
  // Setup connection to save video
  THREAD_RETURN_SUCCESS();
}

///////////////////////////////////////////////////////////////////////////////
/// Main processing thread that runs the read, evaluate net, write loop
struct ProcessArgs {
  int gpu_device_id;
  StorageConfig* storage_config;
  std::string video_path;
  std::string iframe_path;
  int frame_start;
  int frame_end;
  VideoMetadata metadata;
};

void* process_thread(void* arg) {
  ProcessArgs& args = *reinterpret_cast<ProcessArgs*>(arg);

  size_t frame_size =
    av_image_get_buffer_size(AV_PIX_FMT_RGB24,
                             args.metadata.width,
                             args.metadata.height,
                             1);
  size_t frame_buffer_size = frame_size * (args.frame_end - args.frame_start);
  char* frame_buffer = new char[frame_buffer_size];
  std::atomic<int> frames_written{0};

  // Create IO threads for reading and writing
  LoadVideoArgs load_args;
  load_args.storage_config = args.storage_config;
  load_args.video_path = args.video_path;
  load_args.iframe_path = args.iframe_path;
  load_args.frame_start = args.frame_start;
  load_args.frame_end = args.frame_end;
  load_args.metadata = args.metadata;
  load_args.frames_buffer_size = frame_buffer_size;
  load_args.decoded_frames_buffer = frame_buffer;
  load_args.frames_written = &frames_written;
  pthread_t load_thread;
  pthread_create(&load_thread, NULL, load_video_thread, &load_args);

  // pthread_t* save_thread;
  // pthread_create(save_thread, NULL, save_video_thread, NULL);

  // Setup caffe net
  NetInfo net_info = load_neural_net(NetType::ALEX_NET, args.gpu_device_id);
  caffe::Net<float>* net = net_info.net;

  // Resize net input blob for batch size
  const boost::shared_ptr<caffe::Blob<float>> data_blob{
    net->blob_by_name("data")};
  if (data_blob->shape(0) != BATCH_SIZE) {
    data_blob->Reshape({
        BATCH_SIZE, 3, net_info.input_size, net_info.input_size});
  }

  int dim = net_info.input_size;

  cv::Mat unsized_mean_mat(
    net_info.mean_width, net_info.mean_height, CV_32FC3, net_info.mean_image);
  cv::Mat mean_mat;
  cv::resize(unsized_mean_mat, mean_mat, cv::Size(dim, dim));

  int current_frame = args.frame_start;
  while (current_frame + BATCH_SIZE < args.frame_end) {
    // Read batch of frames
    if ((current_frame + BATCH_SIZE - args.frame_start) >= frames_written)
      continue;

    // Decompress batch of frame
    printf("processing frame %d\n", current_frame);

    // Process batch of frames
    caffe::Blob<float> net_input{BATCH_SIZE, 3, dim, dim};
    float* net_input_buffer = net_input.mutable_cpu_data();

    for (int i = 0; i < BATCH_SIZE; ++i) {
      char* buffer = frame_buffer + frame_size * (i + current_frame);
      cv::Mat input_mat(
        args.metadata.height, args.metadata.width, CV_8UC3, buffer);
      cv::cvtColor(input_mat, input_mat, CV_RGB2BGR);
      cv::Mat conv_input;
      cv::resize(input_mat, conv_input, cv::Size(dim, dim));
      cv::Mat float_conv_input;
      conv_input.convertTo(float_conv_input, CV_32FC3);
      cv::Mat normed_input = float_conv_input - mean_mat;
      //to_conv_input(&std::get<0>(in_vec[i]), &conv_input, &mean);
      memcpy(net_input_buffer + i * (dim * dim * 3),
             normed_input.data,
             dim * dim * 3 * sizeof(float));
    }

    net->Forward({&net_input});

    // Save batch of frames

    current_frame += BATCH_SIZE;
  }

  // Epilogue for processing less than a batch of frames

  // Cleanup
  delete[] frame_buffer;
  delete net;

  THREAD_RETURN_SUCCESS();
}

void shutdown() {
  MPI_Finalize();
}

int main(int argc, char **argv) {
  startup(argc, argv);

  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  std::string video_path =
    "kcam-videos-20140910_195012_247.mp4";

  // Setup storage config
  StorageConfig* config =
    StorageConfig::make_disk_config(DB_PATH);
  StorageBackend* storage = StorageBackend::make_from_config(config);

  // Check if we have already preprocessed the video
  FileInfo video_info;
  StoreResult result =
    storage->get_file_info(processed_video_path(video_path), video_info);
  if (result == StoreResult::FileDoesNotExist) {
    // Preprocess video and then exit
    if (is_master(rank)) {
      log_ls.print("Video not processed yet. Processing now...\n");
      //video_path = "../../../tmp/lightscan3n1YnH";
      //video_path = "../../../tmp/lightscanLNaRk3";
      preprocess_video(storage,
                       video_path,
                       processed_video_path(video_path),
                       metadata_path(video_path),
                       iframe_path(video_path));
    }
  } else {
    // Get video metadata to pass to all workers and determine work distribution
    // from frame count
    VideoMetadata metadata;
    {
      std::unique_ptr<RandomReadFile> metadata_file;
      exit_on_error(
        make_unique_random_read_file(storage,
                                     metadata_path(video_path),
                                     metadata_file));
      (void) read_video_metadata(metadata_file.get(), 0, metadata);
    }

    // Parse args to determine video offset

    // Create processing threads for each gpu
    ProcessArgs processing_thread_args[NUM_GPUS];
    pthread_t processing_threads[NUM_GPUS];

    int total_frames = 2000;
    int frames_allocated = 0;
    for (int i = 0; i < NUM_GPUS; ++i) {
      int frames =
        std::ceil((total_frames - frames_allocated) * 1.0 / (NUM_GPUS - i));
      int frame_start = frames_allocated;
      int frame_end = frame_start + frames;
      frames_allocated += frames;

      ProcessArgs& args = processing_thread_args[i];
      args.gpu_device_id = i;
      args.storage_config = config;
      args.video_path = video_path;
      args.iframe_path = iframe_path(video_path);
      args.frame_start = frame_start;
      args.frame_end = frame_end;
      args.metadata = metadata;
      pthread_create(&processing_threads[i],
                     NULL,
                     process_thread,
                     &processing_thread_args[i]);
    }

    // Wait till done
    for (int i = 0; i < NUM_GPUS; ++i) {
      void* result;

      int err = pthread_join(processing_threads[i], &result);
      if (err != 0) {
        fprintf(stderr, "error in pthread_join\n");
        exit(EXIT_FAILURE);
      }

      printf("Joined with thread %d; returned value was %d\n",
             i, *((int *)result));
      free(result);      /* Free memory allocated by thread */
    }
  }

 // Cleanup
 delete storage;
 delete config;

 shutdown();

 return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>/* Copyright 2016 Carnegie Mellon University, NVIDIA Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "lightscan/ingest.h"
#include "lightscan/engine.h"

#include "lightscan/storage/storage_config.h"
#include "lightscan/storage/storage_backend.h"
#include "lightscan/util/common.h"
#include "lightscan/util/video.h"
#include "lightscan/util/caffe.h"
#include "lightscan/util/queue.h"
#include "lightscan/util/profiler.h"

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/errors.hpp>

#include <cuda.h>
#include "lightscan/util/cuda.h"

#include <mpi.h>
#include <cstdlib>
#include <string>
#include <libgen.h>
#include <atomic>

extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}

using namespace lightscan;

namespace po = boost::program_options;

namespace {

int read_last_processed_video(
  StorageBackend* storage,
  const std::string& dataset_name)
{
  StoreResult result;

  const std::string last_written_path =
    dataset_name + "_dataset/last_written.bin";
  std::unique_ptr<RandomReadFile> file;
  result = make_unique_random_read_file(storage, last_written_path, file);

  if (result == StoreResult::FileDoesNotExist) {
    return -1;
  }

  uint64_t pos = 0;
  size_t size_read;

  int32_t last_processed_video;
  EXP_BACKOFF(
    file->read(pos,
               sizeof(int32_t),
               reinterpret_cast<char*>(&last_processed_video),
               size_read),
    result);
  assert(result == StoreResult::Success ||
         result == StoreResult::EndOfFile);
  assert(size_read == sizeof(int32_t));

  return last_processed_video;
}

void write_last_processed_video(
  StorageBackend* storage,
  const std::string& dataset_name,
  int file_index)
{
  const std::string last_written_path =
    dataset_name + "_dataset/last_written.bin";
  std::unique_ptr<WriteFile> file;
  make_unique_write_file(storage, last_written_path, file);

  StoreResult result;
  EXP_BACKOFF(
    file->append(sizeof(int32_t),
                 reinterpret_cast<const char*>(&file_index)),
    result);
  exit_on_error(result);
}

}

void startup(int argc, char** argv) {
  MPI_Init(&argc, &argv);
  av_register_all();
  FLAGS_minloglevel = 2;
  CUD_CHECK(cuInit(0));
}

void shutdown() {
  MPI_Finalize();
}

int main(int argc, char** argv) {
  std::string cmd;
  // Common among commands
  std::string dataset_name; // name of dataset to create/operate on
  // For ingest command
  std::string video_paths_file; // paths of video files to turn into dataset
  // For run command
  std::string job_name; // name of job to refer to after run
  std::string net_descriptor_file; // path to file describing network to use
  {
    po::variables_map vm;

    po::options_description main_desc("Allowed options");
    main_desc.add_options()
      ("help", "Produce help message")
      ("command", po::value<std::string>(),
       "Command to execute")
      ("subargs", po::value<std::vector<std::string> >(),
       "Arguments for command")
      ("config_file", po::value<std::string>(),
       "System configuration (# gpus, batch, etc) in toml format. "
       "Explicit command line options will overide file settings.")
      ("gpus_per_node", po::value<int>(), "Number of GPUs per node")
      ("batch_size", po::value<int>(), "Neural Net input batch size")
      ("batches_per_work_item", po::value<int>(),
       "Number of batches in each work item")
      ("tasks_in_queue_per_gpu", po::value<int>(),
       "Number of tasks a node will try to maintain in the work queue per GPU")
      ("load_workers_per_node", po::value<int>(),
       "Number of worker threads processing load jobs per node");
      ("save_workers_per_node", po::value<int>(),
       "Number of worker threads processing save jobs per node");

      po::positional_options_description main_pos;
      main_pos.add("command", 1);
      main_pos.add("subargs", -1);

      std::vector<std::string> opts;
      try {
        auto parsed = po::command_line_parser(argc, argv).
          options(main_desc).
          positional(main_pos).
          allow_unregistered().
          run();
        po::store(parsed, vm);
        po::notify(vm);

        // Collect all the unrecognized options from the first pass.
        // This will include the (positional) command name, so we need to erase
        // that.
        opts = po::collect_unrecognized(parsed.options, po::include_positional);
        opts.erase(opts.begin());

      } catch (const po::required_option& e) {
        if (vm.count("help")) {
          std::cout << main_desc << std::endl;
          return 1;
        } else {
          throw e;
        }
      }

      if (vm.count("help")) {
        std::cout << main_desc << std::endl;
        return 1;
      }

      if (vm.count("config_file")) {
        std::string config_file_path = vm["config_file"].as<std::string>();
      }

      if (vm.count("gpus_per_node")) {
        GPUS_PER_NODE = vm["gpus_per_node"].as<int>();
      }
      if (vm.count("batch_size")) {
        GLOBAL_BATCH_SIZE = vm["batch_size"].as<int>();
      }
      if (vm.count("batches_per_work_item")) {
        BATCHES_PER_WORK_ITEM = vm["batches_per_work_item"].as<int>();
      }
      if (vm.count("tasks_in_queue_per_gpu")) {
        TASKS_IN_QUEUE_PER_GPU = vm["tasks_in_queue_per_gpu"].as<int>();
      }
      if (vm.count("load_workers_per_node")) {
        LOAD_WORKERS_PER_NODE = vm["load_workers_per_node"].as<int>();
      }
      if (vm.count("save_workers_per_node")) {
        SAVE_WORKERS_PER_NODE = vm["save_workers_per_node"].as<int>();
      }

      cmd = vm["command"].as<std::string>();

      if (cmd == "ingest") {
        po::options_description ingest_desc("ingest options");
        ingest_desc.add_options()
          ("help", "Produce help message")
          ("dataset_name", po::value<std::string>()->required(),
           "Unique name of the dataset to store persistently")
          ("video_paths_file", po::value<std::string>()->required(),
           "File which contains paths to video files to process");

        po::positional_options_description ingest_pos;
        ingest_pos.add("dataset_name", 1);
        ingest_pos.add("video_paths_file", 1);

        try {
          vm.clear();
          po::store(po::command_line_parser(opts)
                    .options(ingest_desc)
                    .positional(ingest_pos)
                    .run(),
                    vm);
          po::notify(vm);
        } catch (const po::required_option& e) {
          if (vm.count("help")) {
            std::cout << ingest_desc << std::endl;
            return 1;
          } else {
            throw e;
          }
        }

        dataset_name = vm["dataset_name"].as<std::string>();
        video_paths_file = vm["video_paths_file"].as<std::string>();

      } else if (cmd == "run") {
        po::options_description run_desc("run options");
        run_desc.add_options()
          ("help", "Produce help message")
          ("job_name", po::value<std::string>()->required(),
           "Unique name to refer to the output of the job after completion")
          ("dataset_name", po::value<std::string>()->required(),
           "Unique name of the dataset to store persistently")
          ("net_descriptor_file", po::value<std::string>()->required(),
           "File which contains a description of the net to use");

        po::positional_options_description run_pos;
        run_pos.add("job_name", 1);
        run_pos.add("dataset_name", 1);
        run_pos.add("net_descriptor_file", 1);

        try {
          po::store(po::command_line_parser(opts)
                    .options(run_desc)
                    .positional(run_pos)
                    .run(),
                    vm);
          po::notify(vm);
        } catch (const po::required_option& e) {
          if (vm.count("help")) {
            std::cout << run_desc << std::endl;
            return 1;
          } else {
            throw e;
          }
        }

        job_name = vm["job_name"].as<std::string>();
        dataset_name = vm["dataset_name"].as<std::string>();
        net_descriptor_file = vm["net_descriptor_file"].as<std::string>();

      } else {
        return 1;
      }
  }

  startup(argc, argv);

  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  int num_nodes;
  MPI_Comm_size(MPI_COMM_WORLD, &num_nodes);

  // Setup storage config
  StorageConfig* config =
    StorageConfig::make_disk_config(DB_PATH);

  if (cmd == "ingest") {
    log_ls.print("Creating dataset %s...\n", dataset_name.c_str());
    // Read in list of video paths and assign unique name to each
    DatasetDescriptor descriptor;
    std::vector<std::string>& video_paths = descriptor.original_video_paths;
    std::vector<std::string>& item_names = descriptor.item_names;
    {
      int video_count = 0;
      std::fstream fs(video_paths_file, std::fstream::in);
      while (fs) {
        std::string path;
        fs >> path;
        if (path.empty()) continue;
        video_paths.push_back(path);
        item_names.push_back(std::to_string(video_count++));
      }
    }

    StorageBackend* storage = StorageBackend::make_from_config(config);

    // Start from the file after the one we last processed succesfully before
    // crashing/exiting
    int last_processed_index = read_last_processed_video(storage, dataset_name);

    // Keep track of videos which we can't parse
    std::vector<std::string> bad_paths;
    for (size_t i = last_processed_index + 1; i < video_paths.size(); ++i) {
      const std::string& path = video_paths[i];
      const std::string& item_name = item_names[i];

      if (is_master(rank)) {
        log_ls.print("Ingesting video %s...\n", path.c_str());
        bool valid_video =
          preprocess_video(storage, dataset_name, path, item_name);
        if (!valid_video) {
          bad_paths.push_back(path);
        }

        // Track the last succesfully processed dataset so we know where
        // to resume if we crash or exit early
        write_last_processed_video(storage, dataset_name, static_cast<int>(i));
      }
    }
    if (!bad_paths.empty()) {
      std::fstream bad_paths_file("bad_videos.txt", std::fstream::out);
      for (const std::string& bad_path : bad_paths) {
        bad_paths_file << bad_path << std::endl;
      }
      bad_paths_file.close();
    }

    // Write out dataset descriptor
    {
      const std::string dataset_file_path =
        dataset_descriptor_path(dataset_name);
      std::unique_ptr<WriteFile> output_file;
      make_unique_write_file(storage, dataset_file_path, output_file);

      serialize_dataset_descriptor(output_file.get(), descriptor);
    }
    // Reset last processed so that we start from scratch next time
    // TODO(apoms): alternatively we could delete the file but apparently
    // that was never designed into the storage interface!
    write_last_processed_video(storage, dataset_name, -1);

    delete storage;

  } else if (cmd == "run") {
    run_job(config, job_name, dataset_name, net_descriptor_file);
  }

  // Cleanup
  delete config;

  shutdown();

  return EXIT_SUCCESS;
}
<commit_msg>Handle file DNE case<commit_after>/* Copyright 2016 Carnegie Mellon University, NVIDIA Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "lightscan/ingest.h"
#include "lightscan/engine.h"

#include "lightscan/storage/storage_config.h"
#include "lightscan/storage/storage_backend.h"
#include "lightscan/util/common.h"
#include "lightscan/util/video.h"
#include "lightscan/util/caffe.h"
#include "lightscan/util/queue.h"
#include "lightscan/util/profiler.h"

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/errors.hpp>

#include <cuda.h>
#include "lightscan/util/cuda.h"

#include <mpi.h>
#include <cstdlib>
#include <string>
#include <libgen.h>
#include <atomic>

extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}

using namespace lightscan;

namespace po = boost::program_options;

namespace {

int read_last_processed_video(
  StorageBackend* storage,
  const std::string& dataset_name)
{
  StoreResult result;

  const std::string last_written_path =
    dataset_name + "_dataset/last_written.bin";

  // File will not exist when first running ingest so check first
  // and return default value if not there
  FileInfo info;
  result = storage->get_file_info(last_written_path, info);
  (void) info;
  if (result == StoreResult::FileDoesNotExist) {
    return -1;
  }

  std::unique_ptr<RandomReadFile> file;
  result = make_unique_random_read_file(storage, last_written_path, file);

  uint64_t pos = 0;
  size_t size_read;

  int32_t last_processed_video;
  EXP_BACKOFF(
    file->read(pos,
               sizeof(int32_t),
               reinterpret_cast<char*>(&last_processed_video),
               size_read),
    result);
  assert(result == StoreResult::Success ||
         result == StoreResult::EndOfFile);
  assert(size_read == sizeof(int32_t));

  return last_processed_video;
}

void write_last_processed_video(
  StorageBackend* storage,
  const std::string& dataset_name,
  int file_index)
{
  const std::string last_written_path =
    dataset_name + "_dataset/last_written.bin";
  std::unique_ptr<WriteFile> file;
  make_unique_write_file(storage, last_written_path, file);

  StoreResult result;
  EXP_BACKOFF(
    file->append(sizeof(int32_t),
                 reinterpret_cast<const char*>(&file_index)),
    result);
  exit_on_error(result);
}

}

void startup(int argc, char** argv) {
  MPI_Init(&argc, &argv);
  av_register_all();
  FLAGS_minloglevel = 2;
  CUD_CHECK(cuInit(0));
}

void shutdown() {
  MPI_Finalize();
}

int main(int argc, char** argv) {
  std::string cmd;
  // Common among commands
  std::string dataset_name; // name of dataset to create/operate on
  // For ingest command
  std::string video_paths_file; // paths of video files to turn into dataset
  // For run command
  std::string job_name; // name of job to refer to after run
  std::string net_descriptor_file; // path to file describing network to use
  {
    po::variables_map vm;

    po::options_description main_desc("Allowed options");
    main_desc.add_options()
      ("help", "Produce help message")
      ("command", po::value<std::string>(),
       "Command to execute")
      ("subargs", po::value<std::vector<std::string> >(),
       "Arguments for command")
      ("config_file", po::value<std::string>(),
       "System configuration (# gpus, batch, etc) in toml format. "
       "Explicit command line options will overide file settings.")
      ("gpus_per_node", po::value<int>(), "Number of GPUs per node")
      ("batch_size", po::value<int>(), "Neural Net input batch size")
      ("batches_per_work_item", po::value<int>(),
       "Number of batches in each work item")
      ("tasks_in_queue_per_gpu", po::value<int>(),
       "Number of tasks a node will try to maintain in the work queue per GPU")
      ("load_workers_per_node", po::value<int>(),
       "Number of worker threads processing load jobs per node");
      ("save_workers_per_node", po::value<int>(),
       "Number of worker threads processing save jobs per node");

      po::positional_options_description main_pos;
      main_pos.add("command", 1);
      main_pos.add("subargs", -1);

      std::vector<std::string> opts;
      try {
        auto parsed = po::command_line_parser(argc, argv).
          options(main_desc).
          positional(main_pos).
          allow_unregistered().
          run();
        po::store(parsed, vm);
        po::notify(vm);

        // Collect all the unrecognized options from the first pass.
        // This will include the (positional) command name, so we need to erase
        // that.
        opts = po::collect_unrecognized(parsed.options, po::include_positional);
        opts.erase(opts.begin());

      } catch (const po::required_option& e) {
        if (vm.count("help")) {
          std::cout << main_desc << std::endl;
          return 1;
        } else {
          throw e;
        }
      }

      if (vm.count("help")) {
        std::cout << main_desc << std::endl;
        return 1;
      }

      if (vm.count("config_file")) {
        std::string config_file_path = vm["config_file"].as<std::string>();
      }

      if (vm.count("gpus_per_node")) {
        GPUS_PER_NODE = vm["gpus_per_node"].as<int>();
      }
      if (vm.count("batch_size")) {
        GLOBAL_BATCH_SIZE = vm["batch_size"].as<int>();
      }
      if (vm.count("batches_per_work_item")) {
        BATCHES_PER_WORK_ITEM = vm["batches_per_work_item"].as<int>();
      }
      if (vm.count("tasks_in_queue_per_gpu")) {
        TASKS_IN_QUEUE_PER_GPU = vm["tasks_in_queue_per_gpu"].as<int>();
      }
      if (vm.count("load_workers_per_node")) {
        LOAD_WORKERS_PER_NODE = vm["load_workers_per_node"].as<int>();
      }
      if (vm.count("save_workers_per_node")) {
        SAVE_WORKERS_PER_NODE = vm["save_workers_per_node"].as<int>();
      }

      cmd = vm["command"].as<std::string>();

      if (cmd == "ingest") {
        po::options_description ingest_desc("ingest options");
        ingest_desc.add_options()
          ("help", "Produce help message")
          ("dataset_name", po::value<std::string>()->required(),
           "Unique name of the dataset to store persistently")
          ("video_paths_file", po::value<std::string>()->required(),
           "File which contains paths to video files to process");

        po::positional_options_description ingest_pos;
        ingest_pos.add("dataset_name", 1);
        ingest_pos.add("video_paths_file", 1);

        try {
          vm.clear();
          po::store(po::command_line_parser(opts)
                    .options(ingest_desc)
                    .positional(ingest_pos)
                    .run(),
                    vm);
          po::notify(vm);
        } catch (const po::required_option& e) {
          if (vm.count("help")) {
            std::cout << ingest_desc << std::endl;
            return 1;
          } else {
            throw e;
          }
        }

        dataset_name = vm["dataset_name"].as<std::string>();
        video_paths_file = vm["video_paths_file"].as<std::string>();

      } else if (cmd == "run") {
        po::options_description run_desc("run options");
        run_desc.add_options()
          ("help", "Produce help message")
          ("job_name", po::value<std::string>()->required(),
           "Unique name to refer to the output of the job after completion")
          ("dataset_name", po::value<std::string>()->required(),
           "Unique name of the dataset to store persistently")
          ("net_descriptor_file", po::value<std::string>()->required(),
           "File which contains a description of the net to use");

        po::positional_options_description run_pos;
        run_pos.add("job_name", 1);
        run_pos.add("dataset_name", 1);
        run_pos.add("net_descriptor_file", 1);

        try {
          po::store(po::command_line_parser(opts)
                    .options(run_desc)
                    .positional(run_pos)
                    .run(),
                    vm);
          po::notify(vm);
        } catch (const po::required_option& e) {
          if (vm.count("help")) {
            std::cout << run_desc << std::endl;
            return 1;
          } else {
            throw e;
          }
        }

        job_name = vm["job_name"].as<std::string>();
        dataset_name = vm["dataset_name"].as<std::string>();
        net_descriptor_file = vm["net_descriptor_file"].as<std::string>();

      } else {
        return 1;
      }
  }

  startup(argc, argv);

  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  int num_nodes;
  MPI_Comm_size(MPI_COMM_WORLD, &num_nodes);

  // Setup storage config
  StorageConfig* config =
    StorageConfig::make_disk_config(DB_PATH);

  if (cmd == "ingest") {
    log_ls.print("Creating dataset %s...\n", dataset_name.c_str());
    // Read in list of video paths and assign unique name to each
    DatasetDescriptor descriptor;
    std::vector<std::string>& video_paths = descriptor.original_video_paths;
    std::vector<std::string>& item_names = descriptor.item_names;
    {
      int video_count = 0;
      std::fstream fs(video_paths_file, std::fstream::in);
      while (fs) {
        std::string path;
        fs >> path;
        if (path.empty()) continue;
        video_paths.push_back(path);
        item_names.push_back(std::to_string(video_count++));
      }
    }

    StorageBackend* storage = StorageBackend::make_from_config(config);

    // Start from the file after the one we last processed succesfully before
    // crashing/exiting
    int last_processed_index = read_last_processed_video(storage, dataset_name);

    // Keep track of videos which we can't parse
    std::vector<std::string> bad_paths;
    for (size_t i = last_processed_index + 1; i < video_paths.size(); ++i) {
      const std::string& path = video_paths[i];
      const std::string& item_name = item_names[i];

      if (is_master(rank)) {
        log_ls.print("Ingesting video %s...\n", path.c_str());
        bool valid_video =
          preprocess_video(storage, dataset_name, path, item_name);
        if (!valid_video) {
          bad_paths.push_back(path);
        }

        // Track the last succesfully processed dataset so we know where
        // to resume if we crash or exit early
        write_last_processed_video(storage, dataset_name, static_cast<int>(i));
      }
    }
    if (!bad_paths.empty()) {
      std::fstream bad_paths_file("bad_videos.txt", std::fstream::out);
      for (const std::string& bad_path : bad_paths) {
        bad_paths_file << bad_path << std::endl;
      }
      bad_paths_file.close();
    }

    // Write out dataset descriptor
    {
      const std::string dataset_file_path =
        dataset_descriptor_path(dataset_name);
      std::unique_ptr<WriteFile> output_file;
      make_unique_write_file(storage, dataset_file_path, output_file);

      serialize_dataset_descriptor(output_file.get(), descriptor);
    }
    // Reset last processed so that we start from scratch next time
    // TODO(apoms): alternatively we could delete the file but apparently
    // that was never designed into the storage interface!
    write_last_processed_video(storage, dataset_name, -1);

    delete storage;

  } else if (cmd == "run") {
    run_job(config, job_name, dataset_name, net_descriptor_file);
  }

  // Cleanup
  delete config;

  shutdown();

  return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before><commit_msg>gui: ensure next node is not past the end of the list<commit_after><|endoftext|>


<commit_before>// ========================================================================== //
// This file is part of DO-CV, a basic set of libraries in C++ for computer
// vision.
//
// Copyright (C) 2013 David Ok <david.ok8@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.
// ========================================================================== //

#include <gtest/gtest.h>

#include <DO/Sara/Defines.hpp>

using namespace std;

TEST(DO_Sara_Core_Test, definesTest)
{
  EXPECT_EQ(string(DO_SARA_VERSION), "1.0.0");
  EXPECT_TRUE(string(src_path("")).find("test/Core") != string::npos);
}

int main(int argc, char** argv)
{
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
<commit_msg>MAINT: fix unit tests.<commit_after>// ========================================================================== //
// This file is part of DO-CV, a basic set of libraries in C++ for computer
// vision.
//
// Copyright (C) 2013 David Ok <david.ok8@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.
// ========================================================================== //

#include <gtest/gtest.h>

#include <DO/Sara/Defines.hpp>

using namespace std;

TEST(DO_Sara_Core_Test, definesTest)
{
  EXPECT_EQ(string(DO_SARA_VERSION), "1.1.0");
  EXPECT_TRUE(string(src_path("")).find("test/Core") != string::npos);
}

int main(int argc, char** argv)
{
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
<|endoftext|>


<commit_before>#include "util/bitmap.h"
#include "tabbed-box.h"

#include "menu/menu.h"

#include "util/font.h"

#include "gui/context-box.h"

using namespace Gui;

#if 0
/* FIXME add rounded tabs */
static void roundTab( const Bitmap & work, int radius, int x1, int y1, int x2, int y2, int color, bool bottom = true ){
    const int width = x2 - x1;
    const int height = y2 - y1;
    radius = Mid(0, radius, Min((x1+width - x1)/2, (y1+height - y1)/2));

    work.circleFill(x1+radius, y1+radius, radius, color);
    work.circleFill((x1+width)-radius, y1+radius, radius, color);
    work.circleFill(x1+radius, (y1+height)-radius, radius, color);
    work.circleFill((x1+width)-radius, (y1+height)-radius, radius, color);
    work.rectangleFill( x1+radius, y1, x2-radius, y1+radius, color);
    work.rectangleFill( x1, y1+radius, x2, y2-radius, color);
    work.rectangleFill( x1+radius, y2-radius, x2-radius, y2, color);

    work.line(x1+radius, y1, x1+width-radius, y1, color);
    work.line(x1+radius, y1+height, x1+width-radius,y1+height, color);
    work.line(x1, y1+radius,x1, y1+height-radius, color);
    work.line(x1+width, y1+radius,x1+width, y1+height-radius, color);

    arc(work, x1+radius, y1+radius, S_PI-1.115, radius, color);
    arc(work, x1+radius + (width - radius *2), y1+radius, -S_PI/2 +0.116, radius, color);
    arc(work, x1+width-radius, y1+height-radius, -0.110, radius ,color);
    arc(work, x1+radius, y1+height-radius, S_PI/2-0.119, radius, color);
}
#endif

Tab::Tab():
context(new ContextBox()),
active(false){
    // Set alpha to 0 as we are not interested in the box
    context->colors.borderAlpha = 0;
    context->colors.bodyAlpha = 0;
}
Tab::~Tab(){
    delete context;
}

TabbedBox::TabbedBox():
current(0),
fontWidth(24),
fontHeight(24),
inTab(false),
tabWidthMax(0),
tabFontColor(Bitmap::makeColor(255,255,255)),
currentTabFontColor(Bitmap::makeColor(0,0,255)){
    activeTabFontColor = new Effects::Gradient(50, tabFontColor, currentTabFontColor);
}

TabbedBox::TabbedBox(const TabbedBox & b):
activeTabFontColor(NULL){
    this->location = b.location;
    this->workArea = b.workArea;
}

TabbedBox::~TabbedBox(){
    for (std::vector<Gui::Tab *>::iterator i = tabs.begin(); i != tabs.end(); ++i){
        Gui::Tab * tab = *i;
        if (tab){
            delete tab;
        }
    }

    if (activeTabFontColor){
	delete activeTabFontColor;
    }
}

TabbedBox &TabbedBox::operator=( const TabbedBox &copy){
    location = copy.location;
    workArea = copy.workArea;

    return *this;
}

// Logic
void TabbedBox::act(){
    if (!tabs.empty()){
	const Font & vFont = Font::getFont(font, fontWidth, fontHeight);
	//tabWidthMax = location.getWidth()/tabs.size();
	const int width = vFont.textLength(tabs[current]->name.c_str()) + 5;
	tabWidthMax = (location.getWidth() - width) / (tabs.size() - 1);
    } else {
	return;
    }
    if (!tabs[current]->active){
	tabs[current]->active = true;
    }
    tabs[current]->context->act();
    if (inTab){
	if (activeTabFontColor){
	    activeTabFontColor->update();
	}
    }
}

// Render
void TabbedBox::render(const Bitmap & work){
    const int tabHeight = fontHeight + 5;
    checkWorkArea();
    // Check if we are using a rounded box
    if (location.getRadius() > 0){
        //roundRectFill( *workArea, (int)location.getRadius(), 0, 0, location.getWidth()-1, location.getHeight()-1, colors.body );
        //roundRect( *workArea, (int)location.getRadius(), 0, 0, location.getWidth()-1, location.getHeight()-1, colors.border );
    } else {
        workArea->rectangleFill(0, tabHeight, location.getWidth()-1, location.getHeight()-1, colors.body );
        workArea->rectangle(0, tabHeight, location.getWidth()-1, location.getHeight()-1, colors.border );
    }

    tabs[current]->context->render(*workArea);

    renderTabs(*workArea);

    Bitmap::transBlender( 0, 0, 0, colors.bodyAlpha );

    /* FIXME: only render the background in translucent mode, the text should
     * not be translucent
     */
    workArea->drawTrans(location.getX(), location.getY(), work);
}

// Add tab
void TabbedBox::addTab(const std::string & name, const std::vector<ContextItem *> & list){
    for (std::vector<Tab *>::iterator i = tabs.begin(); i != tabs.end(); ++i){
	Tab * tab = *i;
	if (tab->name == name){
	    return;
	}
    }
    Tab * tab = new Tab();
    tab->name = name;
    tab->context->setList(list);
    tab->context->setFont(font, fontWidth, fontHeight);
    tab->context->location.setPosition(Gui::AbsolutePoint(0, fontHeight+5));
    tab->context->location.setPosition2(Gui::AbsolutePoint(location.getWidth(), location.getHeight()));
    tab->context->open();
    tabs.push_back(tab);
}

void TabbedBox::moveTab(int direction){
    tabs[current]->context->close();
    tabs[current]->active = false;
    current = (current + direction + tabs.size()) % tabs.size();
    /*
    if (current == 0){
        current = tabs.size()-1;
    } else {
        current--;
    }
    */
    tabs[current]->context->open();
    tabs[current]->active = true;
}

void TabbedBox::up(){
    if (tabs.size() == 0){
        return;
    }
    if (!inTab){
        moveTab(-1);
    } else {
        tabs[current]->context->previous();
    }
}

void TabbedBox::down(){
    if (tabs.size() == 0){
        return;
    }
    if (!inTab){
        moveTab(1);
    } else {
        tabs[current]->context->next();
    }
}

void TabbedBox::left(){
    if (tabs.size() == 0){
        return;
    }
    if (!inTab){
        moveTab(-1);
    } else {
        tabs[current]->context->adjustLeft();
    }
}

void TabbedBox::right(){
    if (tabs.size() == 0){
        return;
    }
    if (!inTab){
        moveTab(1);
    } else {
        tabs[current]->context->adjustRight();
    }
}

void TabbedBox::toggleTabSelect(){
    inTab = !inTab;
}

unsigned int TabbedBox::getCurrentIndex() const {
    if (tabs.size() == 0){
        return 0;
    }
    return this->tabs[current]->context->getCurrentIndex();
}

void TabbedBox::setTabFontColor(int color){
    tabFontColor = color;
    if (activeTabFontColor){
	delete activeTabFontColor;
    }
    activeTabFontColor = new Effects::Gradient(50, tabFontColor, currentTabFontColor);
}

void TabbedBox::setSelectedTabFontColor(int color){
    currentTabFontColor = color;
    if (activeTabFontColor){
	delete activeTabFontColor;
    }
    activeTabFontColor = new Effects::Gradient(50, tabFontColor, currentTabFontColor);
}

void TabbedBox::renderTabs(const Bitmap & bmp){
    const int tabHeight = fontHeight + 5;
    const Font & vFont = Font::getFont(font, fontWidth, fontHeight);

    int x = 0;

    for (std::vector<Gui::Tab *>::iterator i = tabs.begin(); i != tabs.end(); ++i){
        Gui::Tab * tab = *i;
        const int textWidth = vFont.textLength(tab->name.c_str()) + 5;
	// for last tab
	int modifier = 0;
	// Check last tab so we can ensure proper sizing
	if ( i == (tabs.begin() + tabs.size() -1)){
	    if ( ( (tabWidthMax * (tabs.size() - 1) ) + textWidth ) != (unsigned int)location.getWidth() ){
		modifier = location.getWidth() - x - (tab->active ? textWidth : tabWidthMax);
	    }
	}

	if (tab->context->location.getRadius() > 0){
        } else {
            if (tab->active){
		if (!inTab){
		    bmp.rectangle(x, 0, x+textWidth + modifier - 1, tabHeight, colors.border);
		    bmp.rectangleFill( x+1, 1, x+textWidth + modifier - 2, tabHeight, colors.body );

		    bmp.setClipRect(x, 0, x+textWidth + modifier, tabHeight-1);
		    vFont.printf(x + (((textWidth + modifier)/2)-(((textWidth + modifier) - 5)/2)), 0, currentTabFontColor, bmp, tab->name, 0 );
		} else {
		    bmp.rectangle(x, 0, x+textWidth + modifier -1, tabHeight, colors.border);
		    bmp.rectangleFill( x+1, 1, x+textWidth-2 + modifier, tabHeight, colors.body );

		    bmp.setClipRect(x, 0, x+textWidth + modifier, tabHeight-1);
		    vFont.printf(x + (((textWidth + modifier)/2)-(((textWidth + modifier) - 5)/2)), 0, activeTabFontColor->current(), bmp, tab->name, 0 );
		}

		x+=textWidth + modifier;
            } else {
		bmp.rectangle(x, 0, x+tabWidthMax + modifier -1, tabHeight, tabColors.border);
                bmp.rectangleFill( x+1, 1, x+tabWidthMax + modifier -2, tabHeight-2, tabColors.body );

		bmp.setClipRect(x+2, 1, x+tabWidthMax + modifier -3, tabHeight-1);
		vFont.printf(x + (((tabWidthMax + modifier)/2)-((textWidth + modifier)/2)), 0, tabFontColor, bmp, tab->name, 0 );
		x+=tabWidthMax + modifier;
            }
	    bmp.setClipRect(0, 0, bmp.getWidth(), bmp.getHeight());
        }
    }
}
<commit_msg>Make line more even with regard to current tab.<commit_after>#include "util/bitmap.h"
#include "tabbed-box.h"

#include "menu/menu.h"

#include "util/font.h"

#include "gui/context-box.h"

using namespace Gui;

#if 0
/* FIXME add rounded tabs */
static void roundTab( const Bitmap & work, int radius, int x1, int y1, int x2, int y2, int color, bool bottom = true ){
    const int width = x2 - x1;
    const int height = y2 - y1;
    radius = Mid(0, radius, Min((x1+width - x1)/2, (y1+height - y1)/2));

    work.circleFill(x1+radius, y1+radius, radius, color);
    work.circleFill((x1+width)-radius, y1+radius, radius, color);
    work.circleFill(x1+radius, (y1+height)-radius, radius, color);
    work.circleFill((x1+width)-radius, (y1+height)-radius, radius, color);
    work.rectangleFill( x1+radius, y1, x2-radius, y1+radius, color);
    work.rectangleFill( x1, y1+radius, x2, y2-radius, color);
    work.rectangleFill( x1+radius, y2-radius, x2-radius, y2, color);

    work.line(x1+radius, y1, x1+width-radius, y1, color);
    work.line(x1+radius, y1+height, x1+width-radius,y1+height, color);
    work.line(x1, y1+radius,x1, y1+height-radius, color);
    work.line(x1+width, y1+radius,x1+width, y1+height-radius, color);

    arc(work, x1+radius, y1+radius, S_PI-1.115, radius, color);
    arc(work, x1+radius + (width - radius *2), y1+radius, -S_PI/2 +0.116, radius, color);
    arc(work, x1+width-radius, y1+height-radius, -0.110, radius ,color);
    arc(work, x1+radius, y1+height-radius, S_PI/2-0.119, radius, color);
}
#endif

Tab::Tab():
context(new ContextBox()),
active(false){
    // Set alpha to 0 as we are not interested in the box
    context->colors.borderAlpha = 0;
    context->colors.bodyAlpha = 0;
}
Tab::~Tab(){
    delete context;
}

TabbedBox::TabbedBox():
current(0),
fontWidth(24),
fontHeight(24),
inTab(false),
tabWidthMax(0),
tabFontColor(Bitmap::makeColor(255,255,255)),
currentTabFontColor(Bitmap::makeColor(0,0,255)){
    activeTabFontColor = new Effects::Gradient(50, tabFontColor, currentTabFontColor);
}

TabbedBox::TabbedBox(const TabbedBox & b):
activeTabFontColor(NULL){
    this->location = b.location;
    this->workArea = b.workArea;
}

TabbedBox::~TabbedBox(){
    for (std::vector<Gui::Tab *>::iterator i = tabs.begin(); i != tabs.end(); ++i){
        Gui::Tab * tab = *i;
        if (tab){
            delete tab;
        }
    }

    if (activeTabFontColor){
	delete activeTabFontColor;
    }
}

TabbedBox &TabbedBox::operator=( const TabbedBox &copy){
    location = copy.location;
    workArea = copy.workArea;

    return *this;
}

// Logic
void TabbedBox::act(){
    if (!tabs.empty()){
	const Font & vFont = Font::getFont(font, fontWidth, fontHeight);
	//tabWidthMax = location.getWidth()/tabs.size();
	const int width = vFont.textLength(tabs[current]->name.c_str()) + 5;
	tabWidthMax = (location.getWidth() - width) / (tabs.size() - 1);
    } else {
	return;
    }
    if (!tabs[current]->active){
	tabs[current]->active = true;
    }
    tabs[current]->context->act();
    if (inTab){
	if (activeTabFontColor){
	    activeTabFontColor->update();
	}
    }
}

// Render
void TabbedBox::render(const Bitmap & work){
    const int tabHeight = fontHeight + 5;
    checkWorkArea();
    // Check if we are using a rounded box
    if (location.getRadius() > 0){
        //roundRectFill( *workArea, (int)location.getRadius(), 0, 0, location.getWidth()-1, location.getHeight()-1, colors.body );
        //roundRect( *workArea, (int)location.getRadius(), 0, 0, location.getWidth()-1, location.getHeight()-1, colors.border );
    } else {
        workArea->rectangleFill(0, tabHeight, location.getWidth()-1, location.getHeight()-1, colors.body );
        workArea->rectangle(0, tabHeight, location.getWidth()-1, location.getHeight()-1, colors.border );
    }

    tabs[current]->context->render(*workArea);

    renderTabs(*workArea);

    Bitmap::transBlender( 0, 0, 0, colors.bodyAlpha );

    /* FIXME: only render the background in translucent mode, the text should
     * not be translucent
     */
    workArea->drawTrans(location.getX(), location.getY(), work);
}

// Add tab
void TabbedBox::addTab(const std::string & name, const std::vector<ContextItem *> & list){
    for (std::vector<Tab *>::iterator i = tabs.begin(); i != tabs.end(); ++i){
	Tab * tab = *i;
	if (tab->name == name){
	    return;
	}
    }
    Tab * tab = new Tab();
    tab->name = name;
    tab->context->setList(list);
    tab->context->setFont(font, fontWidth, fontHeight);
    tab->context->location.setPosition(Gui::AbsolutePoint(0, fontHeight+5));
    tab->context->location.setPosition2(Gui::AbsolutePoint(location.getWidth(), location.getHeight()));
    tab->context->open();
    tabs.push_back(tab);
}

void TabbedBox::moveTab(int direction){
    tabs[current]->context->close();
    tabs[current]->active = false;
    current = (current + direction + tabs.size()) % tabs.size();
    /*
    if (current == 0){
        current = tabs.size()-1;
    } else {
        current--;
    }
    */
    tabs[current]->context->open();
    tabs[current]->active = true;
}

void TabbedBox::up(){
    if (tabs.size() == 0){
        return;
    }
    if (!inTab){
        moveTab(-1);
    } else {
        tabs[current]->context->previous();
    }
}

void TabbedBox::down(){
    if (tabs.size() == 0){
        return;
    }
    if (!inTab){
        moveTab(1);
    } else {
        tabs[current]->context->next();
    }
}

void TabbedBox::left(){
    if (tabs.size() == 0){
        return;
    }
    if (!inTab){
        moveTab(-1);
    } else {
        tabs[current]->context->adjustLeft();
    }
}

void TabbedBox::right(){
    if (tabs.size() == 0){
        return;
    }
    if (!inTab){
        moveTab(1);
    } else {
        tabs[current]->context->adjustRight();
    }
}

void TabbedBox::toggleTabSelect(){
    inTab = !inTab;
}

unsigned int TabbedBox::getCurrentIndex() const {
    if (tabs.size() == 0){
        return 0;
    }
    return this->tabs[current]->context->getCurrentIndex();
}

void TabbedBox::setTabFontColor(int color){
    tabFontColor = color;
    if (activeTabFontColor){
	delete activeTabFontColor;
    }
    activeTabFontColor = new Effects::Gradient(50, tabFontColor, currentTabFontColor);
}

void TabbedBox::setSelectedTabFontColor(int color){
    currentTabFontColor = color;
    if (activeTabFontColor){
	delete activeTabFontColor;
    }
    activeTabFontColor = new Effects::Gradient(50, tabFontColor, currentTabFontColor);
}

void TabbedBox::renderTabs(const Bitmap & bmp){
    const int tabHeight = fontHeight + 5;
    const Font & vFont = Font::getFont(font, fontWidth, fontHeight);

    int x = 0;

    for (std::vector<Gui::Tab *>::iterator i = tabs.begin(); i != tabs.end(); ++i){
        Gui::Tab * tab = *i;
        const int textWidth = vFont.textLength(tab->name.c_str()) + 5;
	// for last tab
	int modifier = 0;
	// Check last tab so we can ensure proper sizing
	if ( i == (tabs.begin() + tabs.size() -1)){
	    if ( ( (tabWidthMax * (tabs.size() - 1) ) + textWidth ) != (unsigned int)location.getWidth() ){
		modifier = location.getWidth() - x - (tab->active ? textWidth : tabWidthMax);
	    }
	}

	if (tab->context->location.getRadius() > 0){
        } else {
            if (tab->active){
		if (!inTab){
		    bmp.rectangle(x, 0, x+textWidth + modifier - 1, tabHeight, colors.border);
		    bmp.rectangleFill( x+1, 1, x+textWidth + modifier - 2, tabHeight, colors.body );

		    bmp.setClipRect(x, 0, x+textWidth + modifier, tabHeight-1);
		    vFont.printf(x + (((textWidth + modifier)/2)-(((textWidth + modifier) - 5)/2)), 0, currentTabFontColor, bmp, tab->name, 0 );
		} else {
		    bmp.rectangle(x, 0, x+textWidth + modifier -1, tabHeight, colors.border);
		    bmp.rectangleFill( x+1, 1, x+textWidth-2 + modifier, tabHeight, colors.body );

		    bmp.setClipRect(x, 0, x+textWidth + modifier, tabHeight-1);
		    vFont.printf(x + (((textWidth + modifier)/2)-(((textWidth + modifier) - 5)/2)), 0, activeTabFontColor->current(), bmp, tab->name, 0 );
		}

		x+=textWidth + modifier;
            } else {
		bmp.rectangle(x, 0, x+tabWidthMax + modifier -1, tabHeight, tabColors.border);
		bmp.hLine(x, tabHeight, x+textWidth + modifier - 1, colors.border);
                bmp.rectangleFill( x+1, 1, x+tabWidthMax + modifier -2, tabHeight-2, tabColors.body );

		bmp.setClipRect(x+2, 1, x+tabWidthMax + modifier -3, tabHeight-1);
		vFont.printf(x + (((tabWidthMax + modifier)/2)-((textWidth + modifier)/2)), 0, tabFontColor, bmp, tab->name, 0 );
		x+=tabWidthMax + modifier;
            }
	    bmp.setClipRect(0, 0, bmp.getWidth(), bmp.getHeight());
        }
    }
}
<|endoftext|>


<commit_before>#ifndef GPIO_HPP
#define GPIO_HPP

#include <cstdlib>
#include <vector>
#include <iostream>
#include <fstream>

#include <stdio.h>
#include <string.h>
#include <unistd.h>

class GPIO
{
public:
	enum PwmPin {
		P8_13 = 0, // int values are path indices in _pwmPinPaths
		P8_19 = 1,
		P9_14 = 2,
		P9_16 = 3
	};

	enum Pin {
		P8_10 = 68, // int values are pin indices in sysfs
		P8_12 = 44,
		P8_15 = 47,
		P8_17 = 27,
		P9_17 = 4,
		P9_18 = 5,
		P9_21 = 3,
		P9_23 = 49,
		P9_24 = 15,
		P9_26 = 14,
		P9_41 = 20,
		P9_42 = 7
	};

	GPIO();
	~GPIO();

	void setPin(const Pin pin, const bool value);
	void setPwm(const PwmPin pin, const float duty);

private:
	bool containsPin(const Pin pin);
	void exportPin(const Pin pin);
	int echo(const std::string target, const int value);
	int echo(const std::string target, const char *value);
	std::string matchPath(std::string pattern);
	inline std::string append(const std::string base, const std::string suffix)
	{
		std::string tmp(base);
		tmp.append(suffix);
		return tmp;
	}

	const unsigned int PWM_PERIOD;
	std::vector<int> _exportedPins;
	std::vector<std::string> _pwmPinPaths;
};

#endif
<commit_msg>Doxygen!<commit_after>#ifndef GPIO_HPP
#define GPIO_HPP

#include <cstdlib>
#include <vector>
#include <iostream>
#include <fstream>

#include <stdio.h>
#include <string.h>
#include <unistd.h>

class GPIO
{
public:
	/**
	 * PWM pins.
	 * Assigned int values are path indices in the _pwmPinPaths list.
	 */
	enum PwmPin {
		P8_13 = 0,
		P8_19 = 1,
		P9_14 = 2,
		P9_16 = 3
	};

	/**
	 * Plain GPIOs.
	 * Assigned int values are pin indices in sysfs.
	 */
	enum Pin {
		P8_10 = 68,
		P8_12 = 44,
		P8_15 = 47,
		P8_17 = 27,
		P9_17 = 4,
		P9_18 = 5,
		P9_21 = 3,
		P9_23 = 49,
		P9_24 = 15,
		P9_26 = 14,
		P9_41 = 20,
		P9_42 = 7
	};

	GPIO();
	~GPIO();

	/**
	 * @brief Sets a GPIO pin to the specified value.
	 *
	 * @param pin The GPIO to set.
	 * @param value Desired pin value.
	 */
	void setPin(const Pin pin, const bool value);

	/**
	 * @brief Sets a PWM pin's duty cycle.
	 *
	 * @param pin The PWM pin to set.
	 * @param duty Duty cycle percentage, 0 < duty < 1.
	 */
	void setPwm(const PwmPin pin, const float duty);

private:
	bool containsPin(const Pin pin);
	void exportPin(const Pin pin);
	int echo(const std::string target, const int value);
	int echo(const std::string target, const char *value);
	std::string matchPath(std::string pattern);
	inline std::string append(const std::string base, const std::string suffix)
	{
		std::string tmp(base);
		tmp.append(suffix);
		return tmp;
	}

	const unsigned int PWM_PERIOD;
	std::vector<int> _exportedPins;
	std::vector<std::string> _pwmPinPaths;
};

#endif
<|endoftext|>


<commit_before>//
//  http_tls_shared.cc
//

#include "plat_os.h"
#include "plat_net.h"

#include <cassert>
#include <cstring>
#include <iostream>
#include <sstream>
#include <functional>
#include <algorithm>
#include <thread>
#include <mutex>
#include <memory>
#include <string>
#include <vector>
#include <deque>
#include <map>

#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#include "io.h"
#include "hex.h"
#include "url.h"
#include "log.h"
#include "socket.h"
#include "resolver.h"
#include "config_parser.h"
#include "config.h"
#include "pollset.h"
#include "protocol.h"
#include "http_tls_shared.h"

/* http_tls_shared */

bool http_tls_shared::tls_session_debug = true;
std::mutex http_tls_shared::session_mutex;
http_tls_session_map http_tls_shared::session_map;
std::once_flag http_tls_shared::lock_init_flag;
std::vector<std::shared_ptr<std::mutex>> http_tls_shared::locks;

void http_tls_shared::tls_threadid_function(CRYPTO_THREADID *thread_id)
{
    CRYPTO_THREADID_set_pointer(thread_id, (void*)pthread_self());
}

void http_tls_shared::tls_locking_function(int mode, int n, const char *file, int line)
{
    std::call_once(lock_init_flag, [](){
        size_t num_locks = CRYPTO_num_locks();
        locks.resize(CRYPTO_num_locks());
        for (size_t i = 0; i < num_locks; i++) {
            locks[i] = std::make_shared<std::mutex>();
        }
    });

    if (mode & CRYPTO_LOCK) {
        locks[n]->lock();
    } else if (mode & CRYPTO_UNLOCK) {
        locks[n]->unlock();
    }
}

int http_tls_shared::tls_log_errors(const char *str, size_t len, void *bio)
{
    fprintf(stderr, "%s", str);
    return 0;
}

int http_tls_shared::tls_new_session_cb(struct ssl_st *ssl, SSL_SESSION *sess)
{
    unsigned int sess_id_len;
    const unsigned char *sess_id = SSL_SESSION_get_id(sess, &sess_id_len);
    std::string sess_key = hex::encode(sess_id, sess_id_len);
    session_mutex.lock();

    size_t sess_der_len = i2d_SSL_SESSION(sess, NULL);
    unsigned char *sess_der = new unsigned char[sess_der_len];
    if (sess_der) {
        i2d_SSL_SESSION(sess, &sess_der);
        auto si = session_map.insert(http_tls_session_entry(sess_key, std::make_shared<http_tls_session>()));
        http_tls_session &tls_sess = *si.first->second;
        tls_sess.sess_der_len = sess_der_len;
        tls_sess.sess_der = sess_der;
        session_mutex.unlock();
        if (tls_session_debug) {
            log_debug("%s: added session: id=%s", __func__, sess_key.c_str());
        }
        return 0;
    } else {
        if (tls_session_debug) {
            log_debug("%s: failed to add session: id=%s", __func__, sess_key.c_str());
        }
        session_mutex.unlock();
        return -1;
    }
}

void http_tls_shared::tls_remove_session_cb(struct ssl_ctx_st *ctx, SSL_SESSION *sess)
{
    unsigned int sess_id_len;
    const unsigned char *sess_id = SSL_SESSION_get_id(sess, &sess_id_len);
    std::string sess_key = hex::encode(sess_id, sess_id_len);
    session_mutex.lock();
    session_map.erase(sess_key);
    session_mutex.unlock();
    if (tls_session_debug) {
        log_debug("%s: removed session: id=%s", __func__, sess_key.c_str());
    }
}

SSL_SESSION * http_tls_shared::tls_get_session_cb(struct ssl_st *ssl, unsigned char *sess_id, int sess_id_len, int *copy)
{
    *copy = 0;
    std::string sess_key = hex::encode(sess_id, sess_id_len);
    session_mutex.lock();
    auto si = session_map.find(sess_key);
    if (si != session_map.end()) {
        auto sess = si->second;
        session_mutex.unlock();
        // TODO - implement session timeout
        if (tls_session_debug) {
            log_debug("%s: lookup session: cache hit: id=%s", __func__, sess_key.c_str());
        }
        return d2i_SSL_SESSION(NULL, (const uint8_t**)&sess->sess_der, sess->sess_der_len);
    }
    session_mutex.unlock();
    if (tls_session_debug) {
        log_debug("%s: lookup session: cache miss: id=%s", __func__, sess_key.c_str());
    }
    return nullptr;
}

SSL_CTX* http_tls_shared::init_client(protocol *proto, config_ptr cfg)
{
    SSL_library_init();
    SSL_load_error_strings();

    CRYPTO_set_locking_callback(http_tls_shared::tls_locking_function);
    CRYPTO_THREADID_set_callback(http_tls_shared::tls_threadid_function);

    SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
#ifdef SSL_OP_NO_SSLv2
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
#endif
#ifdef SSL_OP_NO_SSLv3
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
#endif
#ifdef SSL_OP_NO_COMPRESSION
    SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#endif

    if ((!SSL_CTX_load_verify_locations(ctx, cfg->tls_ca_file.c_str(), NULL)) ||
        (!SSL_CTX_set_default_verify_paths(ctx))) {
        ERR_print_errors_cb(http_tls_shared::tls_log_errors, NULL);
        log_fatal_exit("%s failed to load cacert: %s",
                       proto->name.c_str(), cfg->tls_ca_file.c_str());
    } else {
        log_debug("%s loaded cacert: %s",
                  proto->name.c_str(), cfg->tls_ca_file.c_str());
    }
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
    SSL_CTX_set_verify_depth(ctx, 9);

    return ctx;
}

SSL_CTX* http_tls_shared::init_server(protocol *proto, config_ptr cfg)
{
    SSL_library_init();
    SSL_load_error_strings();

    CRYPTO_set_locking_callback(http_tls_shared::tls_locking_function);
    CRYPTO_THREADID_set_callback(http_tls_shared::tls_threadid_function);

    SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
#ifdef SSL_OP_NO_SSLv2
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
#endif
#ifdef SSL_OP_NO_SSLv3
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
#endif
#ifdef SSL_OP_NO_COMPRESSION
    SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#endif

#if 0
    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_NO_INTERNAL |
                                        SSL_SESS_CACHE_SERVER);
    SSL_CTX_sess_set_new_cb(ctx, http_tls_shared::tls_new_session_cb);
    SSL_CTX_sess_set_remove_cb(ctx, http_tls_shared::tls_remove_session_cb);
    SSL_CTX_sess_set_get_cb(ctx, http_tls_shared::tls_get_session_cb);
#endif

    if (SSL_CTX_use_certificate_file(ctx,
                                     cfg->tls_cert_file.c_str(), SSL_FILETYPE_PEM) <= 0)
    {
        ERR_print_errors_cb(http_tls_shared::tls_log_errors, NULL);
        log_fatal_exit("%s failed to load certificate: %s",
                       proto->name.c_str(), cfg->tls_cert_file.c_str());
    } else {
        log_info("%s loaded cert: %s",
                 proto->name.c_str(), cfg->tls_cert_file.c_str());
    }

    if (SSL_CTX_use_PrivateKey_file(ctx,
                                    cfg->tls_key_file.c_str(), SSL_FILETYPE_PEM) <= 0)
    {
        ERR_print_errors_cb(http_tls_shared::tls_log_errors, NULL);
        log_fatal_exit("%s failed to load private key: %s",
                       proto->name.c_str(), cfg->tls_key_file.c_str());
    } else {
        log_info("%s loaded key: %s",
                 proto->name.c_str(), cfg->tls_key_file.c_str());
    }

    return ctx;
}
<commit_msg>Add SSL_SESS_CACHE_NO_AUTO_CLEAR flag to SSL_CTX_set_session_cache_mode<commit_after>//
//  http_tls_shared.cc
//

#include "plat_os.h"
#include "plat_net.h"

#include <cassert>
#include <cstring>
#include <iostream>
#include <sstream>
#include <functional>
#include <algorithm>
#include <thread>
#include <mutex>
#include <memory>
#include <string>
#include <vector>
#include <deque>
#include <map>

#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#include "io.h"
#include "hex.h"
#include "url.h"
#include "log.h"
#include "socket.h"
#include "resolver.h"
#include "config_parser.h"
#include "config.h"
#include "pollset.h"
#include "protocol.h"
#include "http_tls_shared.h"

/* http_tls_shared */

bool http_tls_shared::tls_session_debug = true;
std::mutex http_tls_shared::session_mutex;
http_tls_session_map http_tls_shared::session_map;
std::once_flag http_tls_shared::lock_init_flag;
std::vector<std::shared_ptr<std::mutex>> http_tls_shared::locks;

void http_tls_shared::tls_threadid_function(CRYPTO_THREADID *thread_id)
{
    CRYPTO_THREADID_set_pointer(thread_id, (void*)pthread_self());
}

void http_tls_shared::tls_locking_function(int mode, int n, const char *file, int line)
{
    std::call_once(lock_init_flag, [](){
        size_t num_locks = CRYPTO_num_locks();
        locks.resize(CRYPTO_num_locks());
        for (size_t i = 0; i < num_locks; i++) {
            locks[i] = std::make_shared<std::mutex>();
        }
    });

    if (mode & CRYPTO_LOCK) {
        locks[n]->lock();
    } else if (mode & CRYPTO_UNLOCK) {
        locks[n]->unlock();
    }
}

int http_tls_shared::tls_log_errors(const char *str, size_t len, void *bio)
{
    fprintf(stderr, "%s", str);
    return 0;
}

int http_tls_shared::tls_new_session_cb(struct ssl_st *ssl, SSL_SESSION *sess)
{
    unsigned int sess_id_len;
    const unsigned char *sess_id = SSL_SESSION_get_id(sess, &sess_id_len);
    std::string sess_key = hex::encode(sess_id, sess_id_len);
    session_mutex.lock();
    size_t sess_der_len = i2d_SSL_SESSION(sess, NULL);
    unsigned char *sess_der = new unsigned char[sess_der_len];
    if (sess_der) {
        i2d_SSL_SESSION(sess, &sess_der);
        auto si = session_map.insert(http_tls_session_entry(sess_key, std::make_shared<http_tls_session>()));
        http_tls_session &tls_sess = *si.first->second;
        tls_sess.sess_der_len = sess_der_len;
        tls_sess.sess_der = sess_der;
        session_mutex.unlock();
        if (tls_session_debug) {
            log_debug("%s: added session: id=%s", __func__, sess_key.c_str());
        }
        return 0;
    } else {
        if (tls_session_debug) {
            log_debug("%s: failed to add session: id=%s", __func__, sess_key.c_str());
        }
        session_mutex.unlock();
        return -1;
    }
}

void http_tls_shared::tls_remove_session_cb(struct ssl_ctx_st *ctx, SSL_SESSION *sess)
{
    unsigned int sess_id_len;
    const unsigned char *sess_id = SSL_SESSION_get_id(sess, &sess_id_len);
    std::string sess_key = hex::encode(sess_id, sess_id_len);
    session_mutex.lock();
    session_map.erase(sess_key);
    session_mutex.unlock();
    if (tls_session_debug) {
        log_debug("%s: removed session: id=%s", __func__, sess_key.c_str());
    }
}

SSL_SESSION * http_tls_shared::tls_get_session_cb(struct ssl_st *ssl, unsigned char *sess_id, int sess_id_len, int *copy)
{
    *copy = 0;
    std::string sess_key = hex::encode(sess_id, sess_id_len);
    session_mutex.lock();
    auto si = session_map.find(sess_key);
    if (si != session_map.end()) {
        auto sess = si->second;
        session_mutex.unlock();
        // TODO - implement session timeout
        if (tls_session_debug) {
            log_debug("%s: lookup session: cache hit: id=%s", __func__, sess_key.c_str());
        }
        return d2i_SSL_SESSION(NULL, (const uint8_t**)&sess->sess_der, sess->sess_der_len);
    }
    session_mutex.unlock();
    if (tls_session_debug) {
        log_debug("%s: lookup session: cache miss: id=%s", __func__, sess_key.c_str());
    }
    return nullptr;
}

SSL_CTX* http_tls_shared::init_client(protocol *proto, config_ptr cfg)
{
    SSL_library_init();
    SSL_load_error_strings();

    CRYPTO_set_locking_callback(http_tls_shared::tls_locking_function);
    CRYPTO_THREADID_set_callback(http_tls_shared::tls_threadid_function);

    SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
#ifdef SSL_OP_NO_SSLv2
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
#endif
#ifdef SSL_OP_NO_SSLv3
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
#endif
#ifdef SSL_OP_NO_COMPRESSION
    SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#endif

    if ((!SSL_CTX_load_verify_locations(ctx, cfg->tls_ca_file.c_str(), NULL)) ||
        (!SSL_CTX_set_default_verify_paths(ctx))) {
        ERR_print_errors_cb(http_tls_shared::tls_log_errors, NULL);
        log_fatal_exit("%s failed to load cacert: %s",
                       proto->name.c_str(), cfg->tls_ca_file.c_str());
    } else {
        log_debug("%s loaded cacert: %s",
                  proto->name.c_str(), cfg->tls_ca_file.c_str());
    }
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
    SSL_CTX_set_verify_depth(ctx, 9);

    return ctx;
}

SSL_CTX* http_tls_shared::init_server(protocol *proto, config_ptr cfg)
{
    SSL_library_init();
    SSL_load_error_strings();

    CRYPTO_set_locking_callback(http_tls_shared::tls_locking_function);
    CRYPTO_THREADID_set_callback(http_tls_shared::tls_threadid_function);

    SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
#ifdef SSL_OP_NO_SSLv2
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
#endif
#ifdef SSL_OP_NO_SSLv3
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
#endif
#ifdef SSL_OP_NO_COMPRESSION
    SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#endif

#if 0
    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_NO_INTERNAL |
                                        SSL_SESS_CACHE_NO_AUTO_CLEAR |
                                        SSL_SESS_CACHE_SERVER);
    SSL_CTX_sess_set_new_cb(ctx, http_tls_shared::tls_new_session_cb);
    SSL_CTX_sess_set_remove_cb(ctx, http_tls_shared::tls_remove_session_cb);
    SSL_CTX_sess_set_get_cb(ctx, http_tls_shared::tls_get_session_cb);
#endif

    if (SSL_CTX_use_certificate_file(ctx,
                                     cfg->tls_cert_file.c_str(), SSL_FILETYPE_PEM) <= 0)
    {
        ERR_print_errors_cb(http_tls_shared::tls_log_errors, NULL);
        log_fatal_exit("%s failed to load certificate: %s",
                       proto->name.c_str(), cfg->tls_cert_file.c_str());
    } else {
        log_info("%s loaded cert: %s",
                 proto->name.c_str(), cfg->tls_cert_file.c_str());
    }

    if (SSL_CTX_use_PrivateKey_file(ctx,
                                    cfg->tls_key_file.c_str(), SSL_FILETYPE_PEM) <= 0)
    {
        ERR_print_errors_cb(http_tls_shared::tls_log_errors, NULL);
        log_fatal_exit("%s failed to load private key: %s",
                       proto->name.c_str(), cfg->tls_key_file.c_str());
    } else {
        log_info("%s loaded key: %s",
                 proto->name.c_str(), cfg->tls_key_file.c_str());
    }

    return ctx;
}
<|endoftext|>


<commit_before>#include "imageprocessor.h"
#include <leptonica/allheaders.h>
#include <tesseract/ocrclass.h>
#include <QString>
#include <QDebug>
#include <QStandardPaths>
#include <QImageWriter>
#include <QImage>
#include <QStringList>
#include <QTransform>

Pix* preprocess(Pix *image, int sX, int sY, int smoothX, int smoothY, float scoreFract) {

    image = pixConvertRGBToGrayFast(image);
    image = pixUnsharpMaskingGray(image, 5, 2.5);
    l_int32 pthresh;
    image = pixOtsuThreshOnBackgroundNorm(image, NULL, sX, sY, smoothX, smoothY, 100, 50, 255, scoreFract, &pthresh);
    return image;

}

void writeToDisk(Pix *img) {

    l_uint8* ptr_memory;
    size_t len;
    pixWriteMemBmp(&ptr_memory, &len, img);

    QImage testimage;
    testimage.loadFromData((uchar *)ptr_memory, len);
    testimage.save(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) +
                   QString("/textractor_preprocessed.jpg"), "jpg", 100);
}

QString clean(char* outText, tesseract::TessBaseAPI *api) {

    QString text = QString::fromLocal8Bit(outText);

    // Lets do some cleaning based on the word confidence value
    QStringList results = text.split(" ");
    int *confidences = api->AllWordConfidences();
    int i = 0;

    while(i < results.size()) {
        if(confidences[i] < 20) {
            results.removeAt(i);
        }
        ++i;
    }

    text = results.join(" ").toUtf8();

    delete [] confidences;
    delete [] outText;
    return text;
}

QString run(QString imagepath,
            tesseract::TessBaseAPI *api,
            ETEXT_DESC* monitor,
            SettingsManager *settings,
            QPair<QString, int> &info) {

    info.first = QString("Initializing...");
    PIX *pixs;

    QImage img(imagepath);
    img.setDotsPerMeterX(11811.025); // magic value :D = 300 dpi
    img.setDotsPerMeterY(11811.025);
    QTransform transform;
    img = img.transformed(transform.rotate(info.second));
    // if scaled up, the image will take a lot of space and OCR becomes really slow
    //img = img.scaled(img.width() / 2, img.height() / 2, Qt::KeepAspectRatio);
    img.save(imagepath, "jpg", 100);

    char* path = imagepath.toLocal8Bit().data();
    pixs = pixRead(path);

    info.first = QString("Preprocessing the image...");
    pixs = preprocess(pixs, 200, 200, 0, 0, 0.09);

    writeToDisk(pixs);

    api->Init(NULL, settings->getLanguageCode().toLocal8Bit().data());
    api->SetPageSegMode(tesseract::PSM_AUTO);
    api->SetImage(pixs);
    api->SetSourceResolution(300);

    char *outText;
    info.first = QString("Running OCR...");
    api->Recognize(monitor);
    outText = api->GetUTF8Text();

    info.first = QString("Postprocessing...");
    pixDestroy(&pixs);
    api->Clear();

    return clean(outText, api);
}
<commit_msg>Moved Clear() call later (caused crash), added automatic skew detection and rotation<commit_after>#include "imageprocessor.h"
#include <leptonica/allheaders.h>
#include <tesseract/ocrclass.h>
#include <QString>
#include <QDebug>
#include <QStandardPaths>
#include <QImageWriter>
#include <QImage>
#include <QStringList>
#include <QTransform>

Pix* preprocess(Pix *image, int sX, int sY, int smoothX, int smoothY, float scoreFract) {

    image = pixConvertRGBToGrayFast(image);
    image = pixUnsharpMaskingGray(image, 5, 2.5);
    l_int32 pthresh;
    image = pixOtsuThreshOnBackgroundNorm(image, NULL, sX, sY, smoothX, smoothY, 100, 50, 255, scoreFract, &pthresh);
    l_float32 angle;
    Pix* image_deskewed;
    image_deskewed = pixFindSkewAndDeskew(image, 1, &angle, NULL);
    if(image_deskewed != NULL) {
        pixDestroy(&image);
        return image_deskewed;
    }
    return image;

}

void writeToDisk(Pix *img) {

    l_uint8* ptr_memory;
    size_t len;
    pixWriteMemBmp(&ptr_memory, &len, img);

    QImage testimage;
    testimage.loadFromData((uchar *)ptr_memory, len);
    testimage.save(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) +
                   QString("/textractor_preprocessed.jpg"), "jpg", 100);
}

QString clean(char* outText, tesseract::TessBaseAPI *api) {

    QString text = QString::fromLocal8Bit(outText);

    // Lets do some cleaning based on the word confidence value
    QStringList results = text.split(" ");
    int *confidences = api->AllWordConfidences();
    int i = 0;

    while(i < results.size()) {
        if(confidences[i] < 20) {
            results.removeAt(i);
        }
        ++i;
    }

    text = results.join(" ").toUtf8();

    delete [] confidences;
    delete [] outText;
    return text;
}

QString run(QString imagepath,
            tesseract::TessBaseAPI *api,
            ETEXT_DESC* monitor,
            SettingsManager *settings,
            QPair<QString, int> &info) {

    info.first = QString("Initializing...");
    Pix *pixs;

    QImage img(imagepath);
    img.setDotsPerMeterX(11811.025); // magic value :D = 300 dpi
    img.setDotsPerMeterY(11811.025);
    QTransform transform;
    img = img.transformed(transform.rotate(info.second));
    // if scaled up, the image will take a lot of space and OCR becomes really slow
    //img = img.scaled(img.width() / 2, img.height() / 2, Qt::KeepAspectRatio);
    img.save(imagepath, "jpg", 100);

    char* path = imagepath.toLocal8Bit().data();
    pixs = pixRead(path);

    info.first = QString("Preprocessing the image...");
    pixs = preprocess(pixs, 200, 200, 0, 0, 0.09);

    writeToDisk(pixs);

    if(api->Init(NULL, settings->getLanguageCode().toLocal8Bit().data())) {
        qDebug() << "fail";
    }
    api->SetPageSegMode(tesseract::PSM_AUTO);
    api->SetImage(pixs);
    api->SetSourceResolution(300);

    char *outText;
    info.first = QString("Running OCR...");
    api->Recognize(monitor);
    outText = api->GetUTF8Text();

    info.first = QString("Postprocessing...");
    pixDestroy(&pixs);

    QString text = clean(outText, api);
    api->Clear();
    return text;
}
<|endoftext|>