text: # Copyright 2017 The TensorFlow Authors All Rights Reserved.
  #
  # 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.
  # ==============================================================================
  
  r"""Collect images from multiple simultaneous webcams.
  
  Usage:
  
  1. Define some environment variables that describe what you're collecting.
  dataset=your_dataset_name
  mode=train
  num_views=2
  viddir=/tmp/tcn/videos
  tmp_imagedir=/tmp/tcn/tmp_images
  debug_vids=1
  
  2. Run the script.
  export DISPLAY=:0.0 && \
  root=learning/brain/research/tcn && \
  bazel build -c opt --copt=-mavx tcn/webcam && \
  bazel-bin/tcn/webcam \
  --dataset $dataset \
  --mode $mode \
  --num_views $num_views \
  --tmp_imagedir $tmp_imagedir \
  --viddir $viddir \
  --debug_vids 1 \
  --logtostderr
  
  3. Hit Ctrl-C when done collecting, upon which the script will compile videos
  for each view and optionally a debug video concatenating multiple
  simultaneous views.
  """
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  
  import multiprocessing
  from multiprocessing import Process
  import os
  import subprocess
  import sys
  import time
  import cv2
  import matplotlib
  matplotlib.use('TkAgg')
  from matplotlib import animation  # pylint: disable=g-import-not-at-top
  import matplotlib.pyplot as plt
  import numpy as np
  from six.moves import input
  import tensorflow as tf
  tf.logging.set_verbosity(tf.logging.INFO)
  
  
  tf.flags.DEFINE_string('dataset', '', 'Name of the dataset we`re collecting.')
  tf.flags.DEFINE_string('mode', '',
                         'What type of data we`re collecting. E.g.:'
                         '`train`,`valid`,`test`, or `demo`')
  tf.flags.DEFINE_string('seqname', '',
                         'Name of this sequence. If empty, the script will use'
                         'the name seq_N+1 where seq_N is the latest'
                         'integer-named sequence in the videos directory.')
  tf.flags.DEFINE_integer('num_views', 2,
                          'Number of webcams.')
  tf.flags.DEFINE_string('tmp_imagedir', '/tmp/tcn/data',
                         'Temporary outdir to write images.')
  tf.flags.DEFINE_string('viddir', '/tmp/tcn/videos',
                         'Base directory to write debug videos.')
  tf.flags.DEFINE_boolean('debug_vids', True,
                          'Whether to generate debug vids with multiple'
                          'concatenated views.')
  tf.flags.DEFINE_string('debug_lhs_view', '0',
                         'Which viewpoint to use for the lhs video.')
  tf.flags.DEFINE_string('debug_rhs_view', '1',
                         'Which viewpoint to use for the rhs video.')
  tf.flags.DEFINE_integer('height', 1080, 'Raw input height.')
  tf.flags.DEFINE_integer('width', 1920, 'Raw input width.')
  tf.flags.DEFINE_string('webcam_ports', None,
                         'Comma-separated list of each webcam usb port.')
  FLAGS = tf.app.flags.FLAGS
  
  
  class ImageQueue(object):
    """An image queue holding each stream's most recent image.
  
    Basically implements a process-safe collections.deque(maxlen=1).
    """
  
    def __init__(self):
      self.lock = multiprocessing.Lock()
      self._queue = multiprocessing.Queue(maxsize=1)
  
    def append(self, data):
      with self.lock:
        if self._queue.full():
          # Pop the first element.
          _ = self._queue.get()
        self._queue.put(data)
  
    def get(self):
      with self.lock:
        return self._queue.get()
  
    def empty(self):
      return self._queue.empty()
  
    def close(self):
      return self._queue.close()
  
  
  class WebcamViewer(object):
    """A class which displays a live stream from the webcams."""
  
    def __init__(self, display_queues):
      """Create a WebcamViewer instance."""
      self.height = FLAGS.height
      self.width = FLAGS.width
      self.queues = display_queues
  
    def _get_next_images(self):
      """Gets the next image to display."""
      # Wait for one image per view.
      not_found = True
      while not_found:
        if True in [q.empty() for q in self.queues]:
          # At least one image queue is empty; wait.
          continue
        else:
          # Retrieve the images.
          latest = [q.get() for q in self.queues]
          combined = np.concatenate(latest, axis=1)
        not_found = False
      return combined
  
    def run(self):
      """Displays the Kcam live stream in a window.
  
      This function blocks until the window is closed.
      """
      fig, rgb_axis = plt.subplots()
  
      image_rows = self.height
      image_cols = self.width * FLAGS.num_views
      initial_image = np.zeros((image_rows, image_cols, 3))
      rgb_image = rgb_axis.imshow(initial_image, interpolation='nearest')
  
      def update_figure(frame_index):
        """Animation function for matplotlib FuncAnimation. Updates the image.
  
        Args:
          frame_index: The frame number.
        Returns:
          An iterable of matplotlib drawables to clear.
        """
        _ = frame_index
        images = self._get_next_images()
        images = images[..., [2, 1, 0]]
        rgb_image.set_array(images)
        return rgb_image,
  
      # We must keep a reference to this animation in order for it to work.
      unused_animation = animation.FuncAnimation(
          fig, update_figure, interval=50, blit=True)
      mng = plt.get_current_fig_manager()
      mng.resize(*mng.window.maxsize())
      plt.show()
  
  
  def reconcile(queues, write_queue):
    """Gets a list of concurrent images from each view queue.
  
    This waits for latest images to be available in all view queues,
    then continuously:
    - Creates a list of current images for each view.
    - Writes the list to a queue of image lists to write to disk.
    Args:
      queues: A list of `ImageQueues`, holding the latest image from each webcam.
      write_queue: A multiprocessing.Queue holding lists of concurrent images.
    """
    # Loop forever.
    while True:
      # Wait till all queues have an image.
      if True in [q.empty() for q in queues]:
        continue
      else:
        # Retrieve all views' images.
        latest = [q.get() for q in queues]
        # Copy the list of all concurrent images to the write queue.
        write_queue.put(latest)
  
  
  def persist(write_queue, view_dirs):
    """Pulls lists of concurrent images off a write queue, writes them to disk.
  
    Args:
      write_queue: A multiprocessing.Queue holding lists of concurrent images;
        one image per view.
      view_dirs: A list of strings, holding the output image directories for each
        view.
    """
    timestep = 0
    while True:
      # Wait till there is work in the queue.
      if write_queue.empty():
        continue
      # Get a list of concurrent images to write to disk.
      view_ims = write_queue.get()
      for view_idx, image in enumerate(view_ims):
        view_base = view_dirs[view_idx]
        # Assign all concurrent view images the same sequence timestep.
        fname = os.path.join(view_base, '%s.png' % str(timestep).zfill(10))
        cv2.imwrite(fname, image)
      # Move to the next timestep.
      timestep += 1
  
  
  def get_image(camera):
    """Captures a single image from the camera and returns it in PIL format."""
    data = camera.read()
    _, im = data
    return im
  
  
  def capture_webcam(camera, display_queue, reconcile_queue):
    """Captures images from simultaneous webcams, writes them to queues.
  
    Args:
      camera: A cv2.VideoCapture object representing an open webcam stream.
      display_queue: An ImageQueue.
      reconcile_queue: An ImageQueue.
    """
    # Take some ramp images to allow cams to adjust for brightness etc.
    for i in range(60):
      tf.logging.info('Taking ramp image %d.' % i)
      get_image(camera)
  
    cnt = 0
    start = time.time()
    while True:
      # Get images for all cameras.
      im = get_image(camera)
      # Replace the current image in the display and reconcile queues.
      display_queue.append(im)
      reconcile_queue.append(im)
      cnt += 1
      current = time.time()
      if cnt % 100 == 0:
        tf.logging.info('Collected %s of video, %d frames at ~%.2f fps.' % (
            timer(start, current), cnt, cnt/(current-start)))
  
  
  def timer(start, end):
    """Returns a formatted time elapsed."""
    hours, rem = divmod(end-start, 3600)
    minutes, seconds = divmod(rem, 60)
    return '{:0>2}:{:0>2}:{:05.2f}'.format(int(hours), int(minutes), seconds)
  
  
  def display_webcams(display_queues):
    """Builds an WebcamViewer to animate incoming images, runs it."""
    viewer = WebcamViewer(display_queues)
    viewer.run()
  
  
  def create_vids(view_dirs, seqname):
    """Creates one video per view per sequence."""
    vidbase = os.path.join(FLAGS.viddir, FLAGS.dataset, FLAGS.mode)
    if not os.path.exists(vidbase):
      os.makedirs(vidbase)
    vidpaths = []
    for idx, view_dir in enumerate(view_dirs):
      vidname = os.path.join(vidbase, '%s_view%d.mp4' % (seqname, idx))
      encode_vid_cmd = r'mencoder mf://%s/*.png \
      -mf fps=29:type=png \
      -ovc lavc -lavcopts vcodec=mpeg4:mbd=2:trell \
      -oac copy -o %s' % (view_dir, vidname)
      os.system(encode_vid_cmd)
      vidpaths.append(vidname)
  
    debugpath = None
    if FLAGS.debug_vids:
      lhs = vidpaths[FLAGS.debug_lhs_view]
      rhs = vidpaths[FLAGS.debug_rhs_view]
      debug_base = os.path.join('%s_debug' % FLAGS.viddir, FLAGS.dataset,
                                FLAGS.mode)
      if not os.path.exists(debug_base):
        os.makedirs(debug_base)
      debugpath = '%s/%s.mp4' % (debug_base, seqname)
      os.system(r"avconv \
        -i %s \
        -i %s \
        -filter_complex '[0:v]pad=iw*2:ih[int];[int][1:v]overlay=W/2:0[vid]' \
        -map [vid] \
        -c:v libx264 \
        -crf 23 \
        -preset veryfast \
        %s" % (lhs, rhs, debugpath))
  
    return vidpaths, debugpath
  
  
  def setup_paths():
    """Sets up the necessary paths to collect videos."""
    assert FLAGS.dataset
    assert FLAGS.mode
    assert FLAGS.num_views
  
    # Setup directory for final images used to create videos for this sequence.
    tmp_imagedir = os.path.join(FLAGS.tmp_imagedir, FLAGS.dataset, FLAGS.mode)
    if not os.path.exists(tmp_imagedir):
      os.makedirs(tmp_imagedir)
  
    # Create a base directory to hold all sequence videos if it doesn't exist.
    vidbase = os.path.join(FLAGS.viddir, FLAGS.dataset, FLAGS.mode)
    if not os.path.exists(vidbase):
      os.makedirs(vidbase)
  
    # Get one directory per concurrent view and a sequence name.
    view_dirs, seqname = get_view_dirs(vidbase, tmp_imagedir)
  
    # Get an output path to each view's video.
    vid_paths = []
    for idx, _ in enumerate(view_dirs):
      vid_path = os.path.join(vidbase, '%s_view%d.mp4' % (seqname, idx))
      vid_paths.append(vid_path)
  
    # Optionally build paths to debug_videos.
    debug_path = None
    if FLAGS.debug_vids:
      debug_base = os.path.join('%s_debug' % FLAGS.viddir, FLAGS.dataset,
                                FLAGS.mode)
      if not os.path.exists(debug_base):
        os.makedirs(debug_base)
      debug_path = '%s/%s.mp4' % (debug_base, seqname)
  
    return view_dirs, vid_paths, debug_path
  
  
  def get_view_dirs(vidbase, tmp_imagedir):
    """Creates and returns one view directory per webcam."""
    # Create and append a sequence name.
    if FLAGS.seqname:
      seqname = FLAGS.seqname
    else:
      # If there's no video directory, this is the first sequence.
      if not os.listdir(vidbase):
        seqname = '0'
      else:
        # Otherwise, get the latest sequence name and increment it.
        seq_names = [i.split('_')[0] for i in os.listdir(vidbase)]
        latest_seq = sorted(map(int, seq_names), reverse=True)[0]
        seqname = str(latest_seq+1)
      tf.logging.info('No seqname specified, using: %s' % seqname)
    view_dirs = [os.path.join(
        tmp_imagedir, '%s_view%d' % (seqname, v)) for v in range(FLAGS.num_views)]
    for d in view_dirs:
      if not os.path.exists(d):
        os.makedirs(d)
    return view_dirs, seqname
  
  
  def get_cameras():
    """Opens cameras using cv2, ensures they can take images."""
    # Try to get free webcam ports.
    if FLAGS.webcam_ports:
      ports = map(int, FLAGS.webcam_ports.split(','))
    else:
      ports = range(FLAGS.num_views)
    cameras = [cv2.VideoCapture(i) for i in ports]
  
    if not all([i.isOpened() for i in cameras]):
      try:
        # Try to find and kill hanging cv2 process_ids.
        output = subprocess.check_output(['lsof -t /dev/video*'], shell=True)
        tf.logging.info('Found hanging cv2 process_ids: \n')
        tf.logging.info(output)
        tf.logging.info('Killing hanging processes...')
        for process_id in output.split('\n')[:-1]:
          subprocess.call(['kill %s' % process_id], shell=True)
        time.sleep(3)
        # Recapture webcams.
        cameras = [cv2.VideoCapture(i) for i in ports]
      except subprocess.CalledProcessError:
        raise ValueError(
            'Cannot connect to cameras. Try running: \n'
            'ls -ltrh /dev/video* \n '
            'to see which ports your webcams are connected to. Then hand those '
            'ports as a comma-separated list to --webcam_ports, e.g. '
            '--webcam_ports 0,1')
  
    # Verify each camera is able to capture images.
    ims = map(get_image, cameras)
    assert False not in [i is not None for i in ims]
    return cameras
  
  
  def launch_images_to_videos(view_dirs, vid_paths, debug_path):
    """Launch job in separate process to convert images to videos."""
  
    f = 'learning/brain/research/tcn/dataset/images_to_videos.py'
    cmd = ['python %s ' % f]
    cmd += ['--view_dirs %s ' % ','.join(i for i in view_dirs)]
    cmd += ['--vid_paths %s ' % ','.join(i for i in vid_paths)]
    cmd += ['--debug_path %s ' % debug_path]
    cmd += ['--debug_lhs_view %s ' % FLAGS.debug_lhs_view]
    cmd += ['--debug_rhs_view %s ' % FLAGS.debug_rhs_view]
    cmd += [' & ']
    cmd = ''.join(i for i in cmd)
  
    # Call images_to_videos asynchronously.
    fnull = open(os.devnull, 'w')
    subprocess.Popen([cmd], stdout=fnull, stderr=subprocess.STDOUT, shell=True)
  
    for p in vid_paths:
      tf.logging.info('Writing final video to: %s' % p)
    if debug_path:
      tf.logging.info('Writing debug video to: %s' % debug_path)
  
  
  def main(_):
    # Initialize the camera capture objects.
    cameras = get_cameras()
    # Get one output directory per view.
    view_dirs, vid_paths, debug_path = setup_paths()
    try:
      # Wait for user input.
      try:
        tf.logging.info('About to write to:')
        for v in view_dirs:
          tf.logging.info(v)
        input('Press Enter to continue...')
      except SyntaxError:
        pass
  
      # Create a queue per view for displaying and saving images.
      display_queues = [ImageQueue() for _ in range(FLAGS.num_views)]
      reconcile_queues = [ImageQueue() for _ in range(FLAGS.num_views)]
  
      # Create a queue for collecting all tuples of multi-view images to write to
      # disk.
      write_queue = multiprocessing.Queue()
  
      processes = []
      # Create a process to display collected images in real time.
      processes.append(Process(target=display_webcams, args=(display_queues,)))
      # Create a process to collect the latest simultaneous images from each view.
      processes.append(Process(
          target=reconcile, args=(reconcile_queues, write_queue,)))
      # Create a process to collect the latest simultaneous images from each view.
      processes.append(Process(
          target=persist, args=(write_queue, view_dirs,)))
  
      for (cam, dq, rq) in zip(cameras, display_queues, reconcile_queues):
        processes.append(Process(
            target=capture_webcam, args=(cam, dq, rq,)))
  
      for p in processes:
        p.start()
      for p in processes:
        p.join()
  
    except KeyboardInterrupt:
      # Close the queues.
      for q in display_queues + reconcile_queues:
        q.close()
      # Release the cameras.
      for cam in cameras:
        cam.release()
  
      # Launch images_to_videos script asynchronously.
      launch_images_to_videos(view_dirs, vid_paths, debug_path)
  
      try:
        sys.exit(0)
      except SystemExit:
        os._exit(0)  # pylint: disable=protected-access
  
  
  if __name__ == '__main__':
    tf.app.run()
  