diff --git a/README.md b/README.md new file mode 100644 index 00000000..d97fb9af --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Surround360 System + +Surround360 is a hardware and software system for capturing and rendering 3d (stereo) 360 videos and photos, suitable for viewing in VR. We divide the open source components of the system into three subdirectories: + +* /surround360_design -- hardware designs and assembly instructions +* /surround360_camera_ctl -- software for controlling the camera to capture raw data +* /surround360_render -- software for rendering the raw data that is captured into a format suitable for viewing in VR. + +## Join the Surround360 community + +* Website: https://facebook360.fb.com/facebook-surround-360/ + +See the CONTRIBUTING file in each subdirectory for how to help out. + +## License + +The hardware designs, camera control software, and rendering software are subject to different licenses. Please refer to the LICENSE file in each subdirectory. We also provide an additional patent grant. diff --git a/surround360_camera_ctl/.git_ignore b/surround360_camera_ctl/.git_ignore new file mode 100644 index 00000000..52c1cda4 --- /dev/null +++ b/surround360_camera_ctl/.git_ignore @@ -0,0 +1,7 @@ +Makefile +bin/ +lib/ +CMakeFiles/ +CMakeCache.txt +cmake_install.cmake +local/ diff --git a/surround360_camera_ctl/.hgignore b/surround360_camera_ctl/.hgignore new file mode 100644 index 00000000..c5626d3b --- /dev/null +++ b/surround360_camera_ctl/.hgignore @@ -0,0 +1,10 @@ +syntax: regexp +^Makefile +^bin/.*$ +^lib/.*$ +.*\.pyc$ +.*\.pyo$ +CMakeFiles/.*$ +^CMakeCache\.txt +^cmake_install\.cmake +^local/.*$ diff --git a/surround360_camera_ctl/CMakeLists.txt b/surround360_camera_ctl/CMakeLists.txt new file mode 100755 index 00000000..d7b83e02 --- /dev/null +++ b/surround360_camera_ctl/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.1) +project(Surround360CameraCtl CXX) +include_directories(/usr/include/flycapture) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/source) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/source/camera_control) + +IF( NOT DEFINED CMAKE_BUILD_TYPE ) + SET( ${CMAKE_BUILD_TYPE} Release ... FORCE ) +ENDIF() + +SET( PLATFORM_SPECIFIC_LIBS "-lpthread -lgflags") + +IF ( NOT ${CMAKE_BUILD_TYPE} MATCHES Debug ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -Ofast" ) +ENDIF () + +SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-write-strings" ) + +SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +### Add custom targets to show all source and header files in IDEs ### + +file( GLOB SRC "${CMAKE_CURRENT_SOURCE_DIR}/source/*" ) +add_custom_target( _source SOURCES ${SRC} ) + +### CameraControl ### + +add_executable( + CameraControl + source/camera_control/PointGrey.cpp + source/camera_control/CameraControl.cpp +) + +target_compile_features(CameraControl PRIVATE cxx_range_for) +target_link_libraries( + CameraControl + "-lflycapture${D}" + "-lusb-1.0" + "-lpthread" + "-lgflags" + "-lswscale" + "-lavutil" + "-lavcodec" + "-lavformat" + "-lavfilter" + ${PLATFORM_SPECIFIC_LIBS} +) + +### Producer consumer test ### +add_executable( + pc_test + source/camera_control/pc_test.cpp +) + +target_link_libraries( + pc_test + ${PLATFORM_SPECIFIC_LIBS} +) diff --git a/surround360_camera_ctl/CONTRIBUTING.md b/surround360_camera_ctl/CONTRIBUTING.md new file mode 100644 index 00000000..5572b3c4 --- /dev/null +++ b/surround360_camera_ctl/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing to Surround 360 + +We want to make contributing to this project as easy and transparent as +possible. + +## Our Development Process + +We maintain a repository for Surround 360 using Facebook's internal infrastructure, which is automatically synched with GitHub. + +## Pull Requests + +We actively welcome your pull requests. + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") + +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues + +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## Coding Style + +* 2 spaces for indentation rather than tabs +* 80 character line length (with discretion in cases where it makes code less readable). +* TODO + +## License + +The Surround 360 camera control code is licensed as described in LICENSE_camera_ctl.md under /surround360_camera_ctl. We also provide an additional patent grant. + diff --git a/surround360_camera_ctl/LICENSE_camera_ctl.md b/surround360_camera_ctl/LICENSE_camera_ctl.md new file mode 100644 index 00000000..78765c96 --- /dev/null +++ b/surround360_camera_ctl/LICENSE_camera_ctl.md @@ -0,0 +1,26 @@ +LICENSE AGREEMENT + +For Surround 360 Camera Control software + +Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + +Facebook, Inc. ("Facebook") owns all right, title and interest, including all +intellectual property and other proprietary rights, in and to the Nuclide +software (the "Software"). Subject to your compliance with these terms, you are +hereby granted a non-exclusive, worldwide, royalty-free copyright license to +(1) use and copy the Software; and (2) reproduce and distribute the Software as part +of your own software ("Your Software"), provided Your Software does not consist +solely of the Software; and (3) modify the Software for your own internal use. +Facebook reserves all rights not expressly granted to you in this license agreement. + +THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. +IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR EMPLOYEES +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. diff --git a/surround360_camera_ctl/README.md b/surround360_camera_ctl/README.md new file mode 100644 index 00000000..53d07026 --- /dev/null +++ b/surround360_camera_ctl/README.md @@ -0,0 +1,125 @@ +# Surround 360 Camera Control Software + +This part of the Surround 360 software controls the hardware (cameras) and captures the data they collect to a local file storage system (e.g., a RAID of SSD drives). + +## Requirements + +* The Surround 360 camera control software requires Ubuntu Linux 14.04 64-bit (required by the PointGrey camera SDK) + +* The essential dependencies of the Surround 360 camera control software are: + * FlyCapture SDK (from PointGrey), and its prereqs: + * libraw1394-11 + * libgtkmm-2.4-1c2a + * libglademm-2.4-1c2a + * libgtkglextmm-x11-1.2-dev + * libgtkglextmm-x11-1.2 + * libusb-1.0-0 + * libglademm-2.4-dev + * CMake + * gflags + * OpenCV 3.0+ + * ffmpeg + +## Installing Dependencies of the Surround 360 Camera Control Software + +* Download Point Grey FlyCapture SDK and Grasshopper 3 USB 3.0 (GS3-U3-41C6C-C) Firmware: + * Go to https://www.ptgrey.com/support/downloads + * You will need to register (free) to download software + * Product Families: Grasshopper3 + * Camera Models: GS3-U3-41C6C-C + * Operating Systems: Linux Ubuntu 14.04 + * Software -> download latest FlyCapture SDK - Linux Ubuntu (64-bit) + * (optional, see below) Firmware -> download GS3-U3-41C6 Firmware 2.23.3.0 + +* Install FlyCapture SDK (method 1): + Uncompress SDK file and follow instructions from README file + +* Install FlyCapture SDK (method 2): + * Install FlyCapture SDK dependencies: +
+    sudo apt-get install libraw1394-11 libgtkmm-2.4-1c2a libglademm-2.4-1c2a libgtkglextmm-x11-1.2-dev libgtkglextmm-x11-1.2 libusb-1.0-0 libglademm-2.4-dev
+
+ + * Open /etc/modules and append +
+    raw1394
+
+ + * Install FlyCapture2 deb files using the script provided: +
+    chmod +x install_flycapture.sh
+    sudo sh install_flycapture.sh
+
+ + * By default, Linux limits image capture to 2 MB. To capture images over 2 MB, + extend the USBFS limit on how many buffers can be locked into the driver. + This is done by updating the boot params in grub + + Open /etc/default/grub, and find and replace: +
+    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
+
+ with +
+    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash usbcore.usbfs_memory_mb=2000"
+
+ Then update grub: +
+    sudo update-grub
+
+ +* Install Grasshopper 3 USB 3.0 (GS3-U3-41C6C-C) Firmware (optional): + Cameras should come with latest firmware installed, but you can follow these + steps to upgrade it + WARNING: This can only be done in Windows, so you would need to download and + install the Windows version of FlyCapture SDK (see steps above) + * Open UpdatorGUI: + Located under Point Grey FlyCapture2 SDK -> Utilities -> UpdatorGUI3 + * Select the camera(s) to update, point to the uncompressed .ez2 file and select "Update" + * A power cycle of the cameras should not be required, as they are automatically rebooted after firmware update + +* Install CMake (method 2 - Linux only) +
+  sudo apt-get install software-properties-common
+  sudo add-apt-repository ppa:george-edison55/cmake-3.x
+  sudo apt-get update
+  sudo apt-get install cmake && sudo apt-get upgrade cmake
+
+ +* Install gflags: +
+  sudo apt-get install libgflags2 libgflags-dev
+
+ +* Install ffmpeg: + see https://trac.ffmpeg.org/wiki/CompilationGuide + +## Compiling the Surround 360 Camera Control Software + +After installing all of the dependencies as described above, run: +
+  cd /surround360/surround360_camera_ctl
+  cmake -DCMAKE_BUILD_TYPE=Release
+  make
+
+ +To test that compilation is successful, run: + +
+  ./bin/CameraControl -numcams 17 -raw -nbits 8 -shutter 20 -gain 0 -debug
+
+ +We recommend configuring CMake to compile in Release mode because the code will execute faster. However, you can also set it up for debug mode with: +
+  cmake -DCMAKE_BUILD_TYPE=Debug
+
+ +## Join the Surround 360 community + +* Website: https://facebook360.fb.com/facebook-surround-360/ + +See the CONTRIBUTING file for how to help out. + +## License + +The Surround 360 camera control code is licensed as described in LICENSE_camera_ctl.md under /surround360_camera_ctl. diff --git a/surround360_camera_ctl/configs/ffserver.conf b/surround360_camera_ctl/configs/ffserver.conf new file mode 100755 index 00000000..7afa781e --- /dev/null +++ b/surround360_camera_ctl/configs/ffserver.conf @@ -0,0 +1,34 @@ +HTTPPort 8090 +HTTPBindAddress 0.0.0.0 +MaxHTTPConnections 50 +MaxClients 50 +MaxBandwidth 4000 +CustomLog - + + +Format status +ACL allow localhost + + + +File /tmp/preview.ffm +FileMaxSize 1G +ACL allow 127.0.0.1 + + + +NoAudio +Feed preview.ffm +Format webm +VideoCodec libvpx-vp9 +VideoSize 512x512 +VideoFrameRate 1/30 +AVOptionVideo flags +global_header +AVOptionVideo cpu-used 0 +AVOptionVideo qmin 10 +AVOptionVideo qmax 42 +AVOptionVideo quality realtime +PreRoll 3 +StartSendOnKey +VideoBitRate 3000 + diff --git a/surround360_camera_ctl/scripts/setup.sh b/surround360_camera_ctl/scripts/setup.sh new file mode 100755 index 00000000..311dd48b --- /dev/null +++ b/surround360_camera_ctl/scripts/setup.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +mount -o noatime /dev/sda /media/snoraid +echo noop > /sys/block/sda/queue/scheduler +echo 0 > /sys/block/sda/queue/rotational +echo 1024 > /sys/block/sda/queue/nr_requests +for CPUFREQ in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do [ -f $CPUFREQ ] || continue; echo -n performance > $CPUFREQ; done diff --git a/surround360_camera_ctl/scripts/usbreset.sh b/surround360_camera_ctl/scripts/usbreset.sh new file mode 100755 index 00000000..4826239d --- /dev/null +++ b/surround360_camera_ctl/scripts/usbreset.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +if [[ $EUID != 0 ]] ; then + echo This must be run as root! + exit 1 +fi + +counter=0 + +for id in $(lspci | grep Renesas | cut -f 1 -d " ") +do + echo "Resetting $id..." + echo -n 0000:$id > /sys/bus/pci/drivers/xhci_hcd/unbind + echo -n 0000:$id > /sys/bus/pci/drivers/xhci_hcd/bind + counter=$((counter+1)) +done + +echo "$counter USB controllers have been reset!" diff --git a/surround360_camera_ctl/source/camera_control/Camera.hpp b/surround360_camera_ctl/source/camera_control/Camera.hpp new file mode 100755 index 00000000..d4789e7f --- /dev/null +++ b/surround360_camera_ctl/source/camera_control/Camera.hpp @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE_camera_ctl file in the root directory of this subproject. + */ +#pragma once + +#include +#include +#include + +namespace surround360 { + class Camera { + public: + virtual ~Camera() {} + virtual int attach() = 0; + virtual int detach() = 0; + virtual int init(bool isMaster) = 0; + virtual int startCapture() = 0; + virtual int stopCapture() = 0; + virtual void* getFrame() = 0; + virtual unsigned int getDroppedFramesCounter() const = 0; + virtual int powerCamera(bool onOff) = 0; + + protected: + Camera() {} + Camera(const Camera& c) = delete; + }; +} diff --git a/surround360_camera_ctl/source/camera_control/CameraControl.cpp b/surround360_camera_ctl/source/camera_control/CameraControl.cpp new file mode 100755 index 00000000..071ddc9d --- /dev/null +++ b/surround360_camera_ctl/source/camera_control/CameraControl.cpp @@ -0,0 +1,1521 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE_camera_ctl file in the root directory of this subproject. + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + +#include +#include +#include + +using namespace std; +using namespace surround360; +using namespace fc; + +static const int kNumPreviewCams = 4; +static const int kAlignment = 4096; +static const int kMaxDigits = 4; + +#define D(x) if (FLAGS_debug) {cout << x << endl;} + +DEFINE_bool(debug, false, "Enable printing of debugging statements."); +DEFINE_string(dir, to_string(time(nullptr)), "Directory to save data to."); +DEFINE_double(brightness, 10.449, "Set brightness value."); +DEFINE_double(exposure, 0.850, "Set exposure value."); +DEFINE_double(fps, 30.0, "Set frame rate."); +DEFINE_double(gain, 0.0, "Set gain."); +DEFINE_double(gamma, 1.250, "Set gamma."); +DEFINE_bool(list, false, "List detected cameras."); +DEFINE_bool(mono, false, "Capture grayscale frames."); +DEFINE_bool(nocapture, false, "Configure cameras and exit without capturing anything."); +DEFINE_int32(master, -1, "Master camera serial number."); +DEFINE_int32(numcams, -1, "Number of cameras in the rig."); +DEFINE_int32(nbits, 8, "Set bit depth. Allowed values: 8, 12 or 16."); +DEFINE_int32(nframes, 0, "Set number of frames to capture."); +DEFINE_bool(props, false, "Print camera property ranges, default values and exit."); +DEFINE_bool(raw, true, "Capture RAW frames."); +DEFINE_bool(restore, false, "Restore memory channels of all cameras."); +DEFINE_double(shutter, 20.0, "Set shutter speed."); +DEFINE_bool(stop, false, "Stop capturing."); +DEFINE_string(whitebalance, "450 796", "Set red, blue white balance values."); +DEFINE_bool(cli, false, "Enable CLI mode"); + +typedef pair SerialIndexPair; +typedef vector SerialIndexVector; +typedef SerialIndexVector::iterator SerialIndexIterator; + +// The output disk prefix name. There is one per consumer thread +// (CONSUMER_COUNT). +static const string kFramesDisk = "/media/snoraid"; + +// Compute a time difference in seconds +static long double timeDiff(timespec start, timespec end) { + const long double s = start.tv_sec + start.tv_nsec * 1.0e-9; + const long double e = end.tv_sec + end.tv_nsec * 1.0e-9; + return e - s; +} + +// Needs to be global so the signal handler can set this variable. +static bool keepRunning = true; +static bool startRecording = false; +static bool stopRecording = false; +static bool recording = false; + +// If we get a SIGINT (a ctrl-c) stop recording at the next frame +// boundary. +static void sigIntHandler(int signal) { + keepRunning = false; + stopRecording = true; +} + +static unsigned int previewCameras[kNumPreviewCams] = { 0, 4, 8, 12 }; + +// Watch out when using this function! It exits on error by default + +static void printAndSaveError( + const string& errorText, const string& destDir) { + // Print error + cerr << errorText << endl; + + ofstream errorLogFile; + ostringstream filename; + filename << destDir << "/error.log"; + + // Truncate error file. Apache will assume the contents of this file are + // from the last capture + errorLogFile.open(filename.str().c_str(), std::fstream::trunc); + + if (errorLogFile.is_open()) { + errorLogFile << errorText << endl; + errorLogFile.close(); + } else { + cerr << "Warning: Unable to open file (" + << filename.str() << ") for writing. " + << "Please check permissions" << endl; + } +} + +static const string getOptString(PointGreyCamera::CameraProperty propType) { + switch (propType) { + case PointGreyCamera::CameraProperty::BRIGHTNESS: return "brightness"; + case PointGreyCamera::CameraProperty::GAMMA: return "gamma"; + case PointGreyCamera::CameraProperty::SHUTTER: return "shutter"; + case PointGreyCamera::CameraProperty::GAIN: return "gain"; + case PointGreyCamera::CameraProperty::WHITE_BALANCE: return "whitebalance"; + default: + throw "Invalid option passed."; + } +} + +static const string getDefaultOptValue(PointGreyCamera::CameraProperty propType) { + switch (propType) { + case PointGreyCamera::CameraProperty::BRIGHTNESS: return "10.449"; + case PointGreyCamera::CameraProperty::GAMMA: return "1.250"; + case PointGreyCamera::CameraProperty::SHUTTER: return "20.0"; + case PointGreyCamera::CameraProperty::GAIN: return "0.0"; + case PointGreyCamera::CameraProperty::WHITE_BALANCE: return "450 796"; + default: + throw "Invalid option passed."; + } +} + +static string getPropRange( + PointGreyCameraPtr& pCam, + PointGreyCamera::CameraProperty prop) { + + // Get property + string propstr = pCam->getProperty(prop); + ostringstream oss; + + oss << "*PROP*;" + << getOptString(prop) << ";" + << propstr << ";" + << getDefaultOptValue(prop) << ";" + << "-" << getOptString(prop) + << endl; + + return oss.str(); +} + +static string getPropsRange(PointGreyCameraPtr& pCam) { + string output; + output += getPropRange(pCam, PointGreyCamera::CameraProperty::SHUTTER); + output += getPropRange(pCam, PointGreyCamera::CameraProperty::GAIN); + return output; +} + +static void printAndSaveCameraProperties(PointGreyCameraPtr& pCam) { + string camProps = getPropsRange(pCam); + + ofstream myfile; + myfile.open("camProps_raw"); + + if (myfile.is_open()) { + myfile << camProps; + myfile.close(); + } else { + D("ERROR: Unable to open file for writing. Please check permissions"); + exit(EXIT_FAILURE); + } + + D(camProps); +} + +static void disconnect( + PointGreyCameraPtr cameras[], + int nCameras) { + D("Disconnecting cameras..."); + + for (unsigned int i = 0; i < nCameras; i++) { + cameras[i]->detach(); + D("Camera " << i << " disconnected..."); + } +} + +static void stopCapturing( + PointGreyCameraPtr cameras[], + int nCameras) { + D("Stop capturing..."); + + for (unsigned int i = 0; i < nCameras; i++) { + cameras[i]->stopCapture(); + D("Camera " << i << " stopped..."); + } +} + +static int isPreviewCam(int k) { + for (int m = 0; m < kNumPreviewCams; ++m) { + if (previewCameras[m] == k) { + return m; + } + } + return -1; +} + +static int setPreviewCam(int k, int m) { + previewCameras[k] = m; +} + +void cameraProducer( + ConsumerBuffer *consumerBuffer, + ConsumerBuffer *previewBuffer, + PointGreyCameraPtr ppCameras[], + const unsigned int nCameras, + const unsigned int nImages, + const int pid, + const int iCamMaster, + const bool isDebug, + const bool isMono, + const bool isRaw, + const int pinStrobe, + const unsigned int nBits, + unsigned int* droppedFramesCount, + unsigned int* droppedFramesCur, + unsigned int* droppedFramesPrev, + stringstream *statsStream, + const string& destDir) { + + // Create file to write heartbeats + ostringstream filenameHeartbeat; + filenameHeartbeat << destDir << "/heartbeat.dat"; + int fd = open(filenameHeartbeat.str().c_str(), O_WRONLY | O_CREAT, 0777); + + // Setup the SIGINT handler + struct sigaction sa; + sa.sa_handler = &sigIntHandler; + + // Restart the system call, if at all possible + sa.sa_flags = SA_RESTART; + + // Block every signal during the handler + sigfillset(&sa.sa_mask); + + // Intercept SIGINT + if (sigaction(SIGINT, &sa, nullptr) == -1) { + printAndSaveError("Error: cannot handle SIGHUP", destDir); + } + + const int camerasPerProducer = nCameras / PRODUCER_COUNT; + const int cameraOffset = pid * camerasPerProducer; + const int lastCamera = min(cameraOffset + camerasPerProducer, int(nCameras)); + Image rawImage; + + int droppedFramesWindow[nCameras]; + for (int j = 0; j < nCameras; j++) { + droppedFramesWindow[j] = 0; + } + + // move outside of the region of CPUs where the kernel's MSI/MSI-X/IRQ handlers run + cpu_set_t threadCpuAffinity; + CPU_ZERO(&threadCpuAffinity); + CPU_SET(10 + pid, &threadCpuAffinity); + sched_setaffinity(0, sizeof(threadCpuAffinity), &threadCpuAffinity); + + sched_param sparam; + sparam.sched_priority = 99; // real-timed + sched_setscheduler(0, SCHED_RR, &sparam); + + timespec tStart, tEnd; + long double tDiff; + clock_gettime(CLOCK_REALTIME, &tStart); + timespec tLast = tStart; + + int frameNumber = 0; + int frameCount = 0; + const int intFps = int(FLAGS_fps); + + while (keepRunning) { + for (unsigned int i = cameraOffset; i < lastCamera; ++i) { + const int cid = i % CONSUMER_COUNT; // ping-pong between output threads + ++frameCount; + + if (i == 0 && startRecording) { + startRecording = false; + recording = true; + } + + if (i == 0 && stopRecording) { + stopRecording = false; + recording = false; + } + + // Retrieve an image from buffer + FramePacket* nextFrame = nullptr; + + try { + void *bytes = ppCameras[i]->getFrame(); + // loop invariant; if this fails, it means the program is not written correctly + assert(bytes != nullptr); + + if (recording) { + nextFrame = consumerBuffer[cid].getHead(); + nextFrame->frameNumber = frameNumber; + nextFrame->cameraNumber = i; + memcpy(nextFrame->imageBytes, bytes, FRAME_SIZE); + + // We're done with the head of the queue - move on... + consumerBuffer[cid].advanceHead(); + } + + FramePacket *previewFrame = nullptr; + + int prevIdx = isPreviewCam(i); + if (prevIdx > -1 && (frameNumber & 1)) { + previewFrame = previewBuffer[prevIdx].getHead(); + assert(previewFrame != nullptr); + previewFrame->imageBytes = (uint8_t*)bytes; + //memcpy(previewFrame->imageBytes, bytes, FRAME_SIZE); + previewFrame->frameNumber = frameNumber; + previewFrame->cameraNumber = i; + previewBuffer[prevIdx].advanceHead(); + } + } catch (...) { + cerr << "Error when grabbing a frame from the camera " << i << endl; + stopCapturing(ppCameras, nCameras); + ppCameras[iCamMaster]->toggleStrobeOut(pinStrobe, false); + disconnect(ppCameras, nCameras); + exit(EXIT_FAILURE); + } + + droppedFramesCount[i] += (droppedFramesCur[i] == 0) + ? 0 : ppCameras[i]->getDroppedFramesCounter() - droppedFramesCur[i] - 1; + droppedFramesCur[i] = ppCameras[i]->getDroppedFramesCounter(); + droppedFramesPrev[i] = droppedFramesCount[i]; + + ppCameras[i]->commitShutterSpeedUpdate(); + ppCameras[i]->commitGainUpdate(); + + if (frameNumber > 0 && (frameNumber % intFps) == 0 && i == 0) { + timespec tCurr; + clock_gettime(CLOCK_REALTIME, &tCurr); + int dpf = 0; + + for (int j = 0; j < nCameras; j++) { + dpf += droppedFramesCount[j] - droppedFramesWindow[j]; + droppedFramesWindow[j] = droppedFramesCount[j]; + } + + // Update heartbeat + // Multiply by 10 to keep precision. Will divide by 10 in website + int fps = 10.0 * intFps / timeDiff(tLast, tCurr); + + lseek(fd, 0, SEEK_SET); + const int secs = timeDiff(tStart, tCurr); + int bytes = write(fd, &fps, sizeof(fps)); + bytes = write(fd, &secs, sizeof(secs)); + bytes = write(fd, &dpf, sizeof(dpf)); + + if (frameNumber % (3 * intFps) == 0) { + if (FLAGS_cli) { + D("Press 'r' to start recording, 's' to stop recording, 'q' to quit."); + } + D("Elapsed time = " << timeDiff(tStart, tCurr) + << " FPS = " << fps / 10.0 + << " frame # = " << frameNumber + << " Frames captured = " << frameCount + << " Dropped frames = " << dpf + << " " << consumerBuffer[0].stateString() + << " " << consumerBuffer[1].stateString()); + } + tLast = tCurr; + } + } + ++frameNumber; + } + + for (int cid = 0; cid < CONSUMER_COUNT; ++cid) { + consumerBuffer[cid].done(); + } + + for (int cid = 0; cid < kNumPreviewCams; ++cid) { + previewBuffer[cid].done(); + } + + close(fd); + + clock_gettime(CLOCK_REALTIME, &tEnd); + tDiff = timeDiff(tStart, tEnd); + + int nImagesTotal = frameNumber * nCameras; + float frameSizeGB = (float) FRAME_SIZE / (1024 * 1024 * 1024); + float sizeGB = (float) nImagesTotal * frameSizeGB; + float speedTheory = (float) nCameras * 30 * 8 * frameSizeGB; + *statsStream << "--- Producer " << pid << "---" << endl; + *statsStream << "Data writen: " << sizeGB << " GB" << endl; + *statsStream << "Images count: " << nImagesTotal << endl; + *statsStream << "Images count/camera: " << nImagesTotal / nCameras << endl; + *statsStream << "Elapsed time: " << tDiff << " s" << endl; + *statsStream << "Producer speed: " << (8 * sizeGB / tDiff) << " Gb/s" << endl; + *statsStream << "Producer speed (theory): " << speedTheory << " Gb/s" << endl; +} + +static const char* filterGraphDesc( + "nullsrc=size=512x512 [bg];" + "[0:v] setpts=PTS-STARTPTS, scale=256x256 [A];" + "[1:v] setpts=PTS-STARTPTS, scale=256x256 [B];" + "[2:v] setpts=PTS-STARTPTS, scale=256x256 [C];" + "[3:v] setpts=PTS-STARTPTS, scale=256x256 [D];" + "[bg][A] overlay=shortest=1 [bgA];" + "[bgA][B] overlay=shortest=1:x=256 [bgAB];" + "[bgAB][C] overlay=shortest=1:y=256 [bgABC];" + "[bgABC][D] overlay=shortest=1:x=256:y=256"); + +static void preview(ConsumerBuffer* previewBuffer) { + AVFormatContext* formatCtx = nullptr; + AVOutputFormat* fmt = nullptr; + AVCodec* codec = nullptr; + AVCodecContext* codecCtx = nullptr; + AVFrame* frame[kNumPreviewCams]; + AVFrame* filtFrame = av_frame_alloc(); + AVStream* stream = nullptr; + SwsContext* scaleCtx = nullptr; + AVFilter* source[kNumPreviewCams] = { 0 }; + AVFilter* sink = nullptr; + AVFilterInOut* outputs[kNumPreviewCams]; + AVFilterInOut* output = nullptr; + AVFilterInOut* inputs = nullptr; + AVFilterContext* sinkCtx = nullptr; + AVFilterContext* srcCtx[kNumPreviewCams]; + AVFilterGraph* filterGraph = nullptr; + int ret, i; + const int kPreviewResWidth = 512; + const int kPreviewResHeight = 512; + const int kSubFrameResWidth = 256; + const int kSubFrameResHeight = 256; + const int kBitrate = 10000; + int64_t next_pts = 0; + char args[400]; + const char *kStreamUrl = "http://127.0.0.1:8090/preview.ffm"; + AVRational timeBase = (AVRational){ 1, static_cast(FLAGS_fps) }; + cpu_set_t threadCpuAffinity; + enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE }; + + av_register_all(); + avfilter_register_all(); + avformat_network_init(); + + // prepare stream and filtering pipeline + avformat_alloc_output_context2(&formatCtx, nullptr, "ffm", kStreamUrl); + if (formatCtx == nullptr) { + throw "could not allocate output format context"; + } + assert(formatCtx != nullptr); + + fmt = formatCtx->oformat; + fmt->video_codec = AV_CODEC_ID_VP8; + assert(fmt != nullptr); + + codec = avcodec_find_encoder(fmt->video_codec); + if (codec == nullptr) { + throw "could not find video encoder codec"; + } + assert(codec != nullptr); + + stream = avformat_new_stream(formatCtx, codec); + if (stream == nullptr) { + throw "could not allocate new stream"; + } + assert(stream != nullptr); + + stream->id = formatCtx->nb_streams - 1; + codecCtx = avcodec_alloc_context3(codec); + if (codecCtx == nullptr) { + throw "could not allocate codec context"; + } + assert(codecCtx != nullptr); + + codecCtx->codec_id = fmt->video_codec; + codecCtx->bit_rate = kBitrate; + codecCtx->width = kPreviewResWidth; + codecCtx->height = kPreviewResHeight; + stream->time_base = timeBase; + codecCtx->time_base = stream->time_base; + codecCtx->gop_size = 12; + codecCtx->pix_fmt = AV_PIX_FMT_YUV420P; + codecCtx->codec_type = AVMEDIA_TYPE_VIDEO; + codecCtx->qmin = 10; + codecCtx->qmax = 42; + codecCtx->framerate = (AVRational){ 1, static_cast(FLAGS_fps) }; + codecCtx->level = 2; + codecCtx->profile = 1; + codecCtx->thread_count = 4; + codecCtx->thread_type = FF_THREAD_FRAME; + + if (fmt->flags & AVFMT_GLOBALHEADER) + codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + ret = avcodec_open2(codecCtx, codec, nullptr); + if (ret < 0) { + throw "could not open video codec"; + } + assert(ret == 0); + + avcodec_parameters_from_context(stream->codecpar, codecCtx); + + ret = avcodec_copy_context(stream->codec, codecCtx); + + if (ret < 0) { + throw "could not copy stream parameters"; + } + assert(ret == 0); + + av_dump_format(formatCtx, 0, kStreamUrl, 1); + + if (!(fmt->flags & AVFMT_NOFILE)) { + ret = avio_open(&formatCtx->pb, kStreamUrl, AVIO_FLAG_WRITE); + if (ret < 0) { + throw "could not open format for streaming"; + } + assert(ret == 0); + } + + ret = avformat_write_header(formatCtx, nullptr); + if (ret < 0) { + throw "could not write header"; + } + assert(ret == 0); + + sink = avfilter_get_by_name("buffersink"); + if (sink == nullptr) { + throw "could not allocate buffersink filter"; + } + assert(sink != nullptr); + + for (int i = 0; i < kNumPreviewCams; ++i) { + source[i] = avfilter_get_by_name("buffer"); + if (source == nullptr) { + throw "could not allocate buffer source filter"; + } + assert(source[i] != nullptr); + } + + filterGraph = avfilter_graph_alloc(); + if (filterGraph == nullptr) { + throw "could not allocate filter graph"; + } + assert(filterGraph != nullptr); + + snprintf( + args, + sizeof(args), + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + kSubFrameResWidth, + kSubFrameResHeight, + AV_PIX_FMT_YUV420P, + timeBase.num, + timeBase.den, + codecCtx->sample_aspect_ratio.num, + codecCtx->sample_aspect_ratio.den); + + for (int i = 0; i < kNumPreviewCams; ++i) { + char name[kMaxDigits]; + snprintf(name, sizeof(name), "%d:v", i); + char *tmp = av_strdup(name); + if (tmp == nullptr) { + throw "could not allocate memory"; + } + assert(tmp != nullptr); + ret = avfilter_graph_create_filter( + &srcCtx[i], source[i], tmp, args, nullptr, filterGraph); + if (ret < 0) { + throw "could not create graph filter"; + } + assert(ret >= 0); + } + + ret = avfilter_graph_create_filter( + &sinkCtx, sink, "out", nullptr, nullptr, filterGraph); + if (ret < 0) { + throw "could not create graph filter"; + } + assert(ret == 0); + + ret = av_opt_set_int_list( + sinkCtx, + "pix_fmts", + pix_fmts, + AV_PIX_FMT_NONE, + AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + throw "failed to set sink options"; + } + assert(ret == 0); + + for (int i = 0; i < kNumPreviewCams; ++i) { + char name[kMaxDigits]; + snprintf(name, sizeof(name), "%d:v", i); + + outputs[i] = avfilter_inout_alloc(); + if (outputs[i] == nullptr) { + throw "could not allocate output inout node"; + } + assert(outputs[i] != nullptr); + + outputs[i]->name = av_strdup(name); + outputs[i]->filter_ctx = srcCtx[i]; + outputs[i]->pad_idx = 0; + outputs[i]->next = nullptr; + } + + for (int i = 0; i < kNumPreviewCams - 1; ++i) { + outputs[i]->next = outputs[i + 1]; + } + output = outputs[0]; + + inputs = avfilter_inout_alloc(); + if (inputs == nullptr) { + throw "could not allocate input inout node"; + } + assert(inputs != nullptr); + + inputs->name = av_strdup("out"); + inputs->filter_ctx = sinkCtx; + inputs->pad_idx = 0; + inputs->next = NULL; + + ret = avfilter_graph_parse_ptr( + filterGraph, filterGraphDesc, &inputs, &output, nullptr); + if (ret < 0) { + throw "failed to parse graph filter"; + } + assert(ret >= 0); + + ret = avfilter_graph_config(filterGraph, nullptr); + if (ret < 0) { + throw "failed to create filter complex"; + } + assert(ret == 0); + + scaleCtx = sws_getContext( + FRAME_W, + FRAME_H, + AV_PIX_FMT_BAYER_GBRG8, + kSubFrameResWidth, + kSubFrameResHeight, + AV_PIX_FMT_YUV420P, + SWS_FAST_BILINEAR, + nullptr, + nullptr, + nullptr); + if (scaleCtx == nullptr) { + throw "failed to create scaling context"; + } + assert(scaleCtx != nullptr); + + // allocate frames + for (i = 0; i < kNumPreviewCams; ++i) { + frame[i] = av_frame_alloc(); + if (frame[i] == nullptr) { + throw "could not allocate frame"; + } + assert(frame[i] != nullptr); + + frame[i]->format = AV_PIX_FMT_YUV420P; + frame[i]->width = kSubFrameResWidth; + frame[i]->height = kSubFrameResHeight; + ret = av_frame_get_buffer(frame[i], 32); + if (ret < 0) { + throw "could not allocate frame"; + } + assert(ret >= 0); + } + + FramePacket* nextFrame = nullptr; + + while (keepRunning) { + for (i = 0; i < kNumPreviewCams; ++i) { + int stride[4] = { FRAME_W, 0, 0, 0 }; + + nextFrame = previewBuffer[i].getTail(); + if (nextFrame == nullptr) { + keepRunning = false; + break; + } + + const unsigned char* const planes[4] = { + nextFrame->imageBytes, nullptr, nullptr, nullptr + }; + + frame[i]->pts = next_pts; + ret = sws_scale( + scaleCtx, + planes, + stride, + 0, + FRAME_H, + frame[i]->data, + frame[i]->linesize); + + if (ret < 0) { + throw "failed when scaling frame"; + } + assert(ret >= 0); + // frame is now down-scaled and converted from bayer to yuv420p + // we can advance the buffer + previewBuffer[i].advanceTail(); + + ret = av_buffersrc_add_frame_flags( + srcCtx[i], frame[i], AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) { + throw "could not push scaled frame into a buffer source"; + } + assert(ret == 0); + } + ++next_pts; + + while (keepRunning) { + AVPacket pkt = { 0 }; + int gotPacket = 0; + + ret = av_buffersink_get_frame(sinkCtx, filtFrame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } + if (ret < 0) { + throw "error when retrieving filtered frames"; + } + + av_init_packet(&pkt); + ret = avcodec_encode_video2(codecCtx, &pkt, filtFrame, &gotPacket); + if (ret < 0) { + throw "could not encode final frame"; + } + assert(ret >= 0); + + if (gotPacket) { + av_packet_rescale_ts(&pkt, codecCtx->time_base, stream->time_base); + pkt.stream_index = stream->index; + ret = av_interleaved_write_frame(formatCtx, &pkt); + if (ret < 0) { + throw "failed to write frame to the format output"; + } + assert(ret >= 0); + } + + av_frame_unref(filtFrame); + } + } +} + +void frameConsumer( + ConsumerBuffer consumerBuffer[], + const int cid, + const int nCameras, + const int nImages, + const string& dir, + const string& label, + stringstream* statsStream) { + + const string filenameFrames = dir + "/" + label + "_" + to_string(cid) + ".bin"; + int fd = open(filenameFrames.c_str(), + O_WRONLY | O_NONBLOCK | O_CREAT | O_DIRECT, 0644); + + if (fd < 0) { + printAndSaveError(strerror(errno), dir); + exit(EXIT_FAILURE); + } + + size_t fileSize = FRAME_SIZE * nCameras * size_t(nImages) / CONSUMER_COUNT; + posix_fadvise(fd, 0, fileSize, POSIX_FADV_DONTNEED); + posix_fadvise(fd, 0, fileSize, POSIX_FADV_SEQUENTIAL); + + int countImg = 0; + size_t bytesWritten = 0; + + // move outside of the region of CPUs where the kernel's MSI/MSI-X/IRQ handlers run + cpu_set_t threadCpuAffinity; + CPU_ZERO(&threadCpuAffinity); + CPU_SET(10 + cid + PRODUCER_COUNT, &threadCpuAffinity); + sched_setaffinity(0, sizeof(threadCpuAffinity), &threadCpuAffinity); + + // Using UNIX open/write to avoid I/O buffering + timespec tStart, tEnd; + long double tDiff; + clock_gettime(CLOCK_REALTIME, &tStart); + + FramePacket* nextFrame; + while ((nextFrame = consumerBuffer[cid].getTail()) != nullptr) { + int count = write(fd, nextFrame->imageBytes, FRAME_SIZE); + if (count < 0) { + printAndSaveError(strerror(errno), dir); + exit(EXIT_FAILURE); + } + bytesWritten += count; + countImg++; + consumerBuffer[cid].advanceTail(); + } + + fsync(fd); + close(fd); + clock_gettime(CLOCK_REALTIME, &tEnd); + + tDiff = timeDiff(tStart, tEnd); + + float sizeGB = float(bytesWritten) / float(1024 * 1024 * 1024); + *statsStream << "--- Consumer " << cid << "---" << endl; + *statsStream << "Data writen: " << sizeGB << " GB" << endl; + *statsStream << "Images count: " << countImg << endl; + *statsStream << "Elapsed time: " << tDiff << " s" << endl; + *statsStream << "Consumer speed: " + << (8 * sizeGB / tDiff) << " Gb/s" << endl; +} + +static bool hasEnoughDiskSpace(const string& path, const double requested) { + struct statvfs stats; + statvfs(path.c_str(), &stats); + const double avail = double(stats.f_bfree) * double(stats.f_frsize); + return avail > requested; +} + +static void checkCameraSpeeds() { + int err; + ssize_t ndevs = 0; + libusb_device** devices = nullptr; + const unsigned short kVendorID = 0x1e10; + const unsigned short kProductID = 0x3300; + + libusb_init(nullptr); + + ndevs = libusb_get_device_list(nullptr, &devices); + for (int k = 0; k < ndevs; k++) { + libusb_device_descriptor desc; + libusb_get_device_descriptor(devices[k], &desc); + + if ((desc.idVendor != kVendorID) && (desc.idProduct != kProductID)) { + continue; + } + + int speed = libusb_get_device_speed(devices[k]); + if (speed < LIBUSB_HIGH_SPEED_OPERATION) { + int err; + libusb_device_handle *handle = nullptr; + libusb_device_handle *parentHandle = nullptr; + libusb_device *parentDev = nullptr; + + cout << "USB device " << k + << " operating at wrong speed. Resetting camera..." << endl; + + parentDev = libusb_get_parent(devices[k]); + if ((err = libusb_open(parentDev, &parentHandle)) != 0) { + cerr << "Error grabbing the parent interface handle: " << err << endl; + continue; + } + + libusb_reset_device(parentHandle); + libusb_close(parentHandle); + } + } + + libusb_free_device_list(devices, 1); +} + +static void getCameraSerialNumbers(SerialIndexVector *v) { + unsigned int ncameras = 0; + BusManager busMgr; + PGRGuid guid; + CameraInfo camInfo; + + if (!v) { + return; + } + + ncameras = PointGreyCamera::findCameras(); + + for (int k = 0; k < ncameras; ++k) { + PointGreyCameraPtr c = PointGreyCamera::getCamera(k); + v->push_back(make_pair(k, c->getSerialNumber())); + } +} + +static void validateWhiteBalance(const string& valuestr) { + istringstream iss(valuestr); + istream_iterator end; + istream_iterator iter(iss); + int idx = 0; + int wb[2] = { 0 }; + + for (; iter != end; ++iter, ++idx) { + try { + wb[idx] = stoi(*iter); + } catch (...) { + cerr << "white balance arguments need to be a pair of integers." << endl; + } + } + if (idx != 2) { + throw "whitebalance needs a pair of integer values."; + } +} + +static void saveCMDArgs(const string& destDir, int argc, char* argv[]) { + ofstream cmdFile; + ostringstream filename; + filename << destDir << "/cmd.txt"; + cmdFile.open(filename.str().c_str()); + + if (cmdFile.is_open()) { + for (int i = 0; i <= argc; i++) { + cmdFile << argv[i] << " "; + } + cmdFile.close(); + } else { + cerr << "Warning: Unable to open file (cmd) for writing." + << " Please check permissions, and make sure all intermediate " + << "directories are created (" + << destDir << ")" + << endl; + } +} + +static void getPixelFormatFromBitDepth( + PixelFormat* pf, + unsigned int nBits) { + switch (nBits) { + case 8: + *pf = PIXEL_FORMAT_RAW8; + break; + case 12: + *pf = PIXEL_FORMAT_RAW12; + break; + case 16: + *pf = PIXEL_FORMAT_RAW16; + break; + default: + cerr << "Invalid bit depth! Falling back to RGB8" << endl; + break; + } +} + +static void saveDroppedFrames( + PointGreyCameraPtr& ppCam, + unsigned int droppedFrames, + const string& destDir) { + ofstream skippedFramesFile; + ostringstream filename; + filename << destDir << "/dropped_frames.txt"; + skippedFramesFile.open(filename.str().c_str(), std::fstream::app); + + if (skippedFramesFile.is_open()) { + skippedFramesFile << "Dropped frames in camera " << ppCam->getSerialNumber() + << ": " << droppedFrames << endl; + skippedFramesFile.close(); + } else { + cerr << "Warning: Unable to open file (dropped_frames) for writing. " + << "Please check permissions" + << endl; + } +} + +static int getControlQueue() { + const char* kQueuePath = "/usr/local/bin/CameraControl"; + const int kKeyId = 83; // ascii 'S' + key_t queueKey = ftok(kQueuePath, kKeyId); + int queueId = msgget(queueKey, 0666 | IPC_CREAT); + if (queueId == -1) { + throw "can't create control message queue"; + } + return queueId; +} + +static termios origTermSettings; + +static void restoreTerminalSettings() { + tcsetattr(STDIN_FILENO, 0, &origTermSettings); +} + +int main(int argc, char *argv[]) { + PointGreyCameraPtr *ppCameras; + Error error; + Image rawImage; + PixelFormat pf = PIXEL_FORMAT_RGB8; + timespec tStart, tEnd, tDiff; + termios tattr; + + const int pinStrobe = 2; // pin #3 (red wire) + const int pinTrigger = 3; // pin #4 (green wire) + // Time (ms) to wait for an image when calling RetrieveBuffer + const int timeoutBuffer = -1; + float sh; // Used to track if we do autoshutter (sh = 0) + SerialIndexVector* serialNumbers = new SerialIndexVector(); + + // Serial number of camera that will trigger the rest (master) + int camStrobeOutSN = -1; + int iCamMaster = -1; + int ret; + unsigned int nCameras = 0; + string finalPath; + struct stat dirStat; + const mode_t kPermissions = + S_IRUSR | + S_IWUSR | + S_IXUSR | + S_IRGRP | + S_IWGRP | + S_IXGRP | + S_IROTH | + S_IXOTH; + + google::SetUsageMessage("Control capturing of image data."); + google::ParseCommandLineFlags(&argc, &argv, false); + + if (argc == 1) { + cerr << "No arguments. See --help for more information." << endl; + return -1; + } + + if (FLAGS_cli) { + tcgetattr(STDIN_FILENO, &origTermSettings); + tattr = origTermSettings; + tattr.c_lflag &= ~(ECHO | ICANON); + tattr.c_cc[VMIN] = 1; + tattr.c_cc[VTIME] = 0; + tcsetattr(STDIN_FILENO, 0, &tattr); + + atexit(&restoreTerminalSettings); + } + + getCameraSerialNumbers(serialNumbers); + SerialIndexVector* sortedSerials = + new SerialIndexVector(serialNumbers->size()); + copy(serialNumbers->begin(), serialNumbers->end(), sortedSerials->begin()); + + sort(sortedSerials->begin(), sortedSerials->end(), + [](const SerialIndexPair& left, const SerialIndexPair& right) + { return left.second < right.second; }); + + // list cameras and quit + if (FLAGS_list) { + if (serialNumbers->empty()) { + cerr << "No cameras detected." << endl; + return -1; + } + + cout << "1: " << sortedSerials->at(0).second << " [master]" << endl; + + int i; + SerialIndexVector::iterator it; + + for (i = 2, it = ++sortedSerials->begin(); it != sortedSerials->end(); ++it, ++i) { + cout << i << ": " << (*it).second << " [slave]" << endl; + } + + delete sortedSerials; + return 0; + } + + const string label = FLAGS_dir; + string captureDir = kFramesDisk + "/" + FLAGS_dir; + + ret = stat(captureDir.c_str(), &dirStat); + if (ret == -1 && errno == ENOENT) { + ret = mkdir(captureDir.c_str(), kPermissions); + if (ret == -1) { + const string kErrString(strerror(errno)); + throw "Can't create destination directory: " + kErrString; + } + } else { + // destination directory exists. Don't overwrite. Append timestamp to the name + captureDir += "-" + to_string(time(nullptr)); + ret = mkdir(captureDir.c_str(), kPermissions); + if (ret == -1) { + const string kErrString(strerror(errno)); + throw "Can't create destination directory " + + captureDir + ": " + kErrString; + } + } + + saveCMDArgs(captureDir, argc, argv); + checkCameraSpeeds(); + + camStrobeOutSN = FLAGS_master; + validateWhiteBalance(FLAGS_whitebalance); + + // Check if we have enough disk space + if ((FLAGS_nframes > 0) + && !hasEnoughDiskSpace( + kFramesDisk, FRAME_SIZE * FLAGS_numcams * FLAGS_nframes)) { + cerr << "Not enough disk space to capture requested number of frames." + << endl << "You need at least " + << (FRAME_SIZE >> 20) * FLAGS_numcams * FLAGS_nframes + << " MB available on " << kFramesDisk << endl; + exit(EXIT_FAILURE); + } + + // Create the producer/consumer buffer object + ConsumerBuffer* consumerBuffer = new ConsumerBuffer[CONSUMER_COUNT]; + uint8_t* frameBytes[CONSUMER_COUNT]; + for (int k = 0; k < CONSUMER_COUNT; k++) { + int ret = posix_memalign( + (void **)&frameBytes[k], + kAlignment, + FRAME_SIZE * BUFFER_SIZE); + assert(ret == 0); + mlock(frameBytes[k], FRAME_SIZE * BUFFER_SIZE); + memset(frameBytes[k], 0, FRAME_SIZE * BUFFER_SIZE); + madvise(frameBytes, FRAME_SIZE * BUFFER_SIZE, MADV_SEQUENTIAL); + consumerBuffer[k].setBuffers(frameBytes[k], FRAME_SIZE); + } + + ConsumerBuffer* previewBuffer = new ConsumerBuffer[kNumPreviewCams]; + + D("Starting process..."); + BusManager busMgr; + + D("Getting number of cameras..."); + + nCameras = PointGreyCamera::findCameras(); + + D("Number of cameras detected: " << nCameras); + if (nCameras == 0 || (!FLAGS_props && FLAGS_numcams != nCameras)) { + cerr << "Error: detected number of cameras " + << "is different than passed in as argument." << endl; + exit(EXIT_FAILURE); + } + + // Check if we only want camera property info + if (FLAGS_props) { + PointGreyCameraPtr camera = PointGreyCamera::getCamera(0); + try { + camera->init(false); + } catch (...) { + printAndSaveError("Erorr polling for trigger ready!", captureDir); + camera->toggleStrobeOut(pinStrobe, false); + camera->detach(); + exit(EXIT_FAILURE); + } + } + + ppCameras = new PointGreyCameraPtr[nCameras]; + + if (FLAGS_restore) { + for (unsigned int i = 0; i < nCameras; i++) { + ppCameras[i] = PointGreyCamera::getCamera(i); + ppCameras[i]->reset(); + } + disconnect(ppCameras, nCameras); + exit(EXIT_SUCCESS); + } + + if (camStrobeOutSN == -1) { + camStrobeOutSN = sortedSerials->at(0).second; + printAndSaveError("Master serial number missing! Using " + + to_string(camStrobeOutSN), captureDir); + } + + if (FLAGS_stop) { + CameraInfo camInfo; + + for (unsigned int i = 0; i < nCameras; i++) { + PointGreyCameraPtr cam = PointGreyCamera::getCamera(i); + if (cam->getSerialNumber() == camStrobeOutSN) { + cam->toggleStrobeOut(pinStrobe, false); + } + } + + stopCapturing(ppCameras, nCameras); + disconnect(ppCameras, nCameras); + exit(EXIT_SUCCESS); + } + + sh = FLAGS_shutter; + + if (FLAGS_nframes == -1) { + printAndSaveError("Number of frames (n) missing!", captureDir); + exit(EXIT_FAILURE); + } + + // Check that we have all we need if video + if (FLAGS_nframes > 1) { + float fps = FLAGS_fps; + // We cannot have shutter time longer than 1/fps + if (sh > 1000 / fps) { + ostringstream oss; + + printAndSaveError( + "Shutter time (" + to_string(sh) + + "ms) cannot be longer than 1/fps (" + + to_string(1000.0 / fps) + ")!", captureDir); + exit(EXIT_FAILURE); + } + } + + if (FLAGS_raw) { + getPixelFormatFromBitDepth(&pf, FLAGS_nbits); + } else if (FLAGS_mono) { + pf = PIXEL_FORMAT_MONO8; + } + + CameraInfo camInfo; + + ////////// START OF SETTING CAMERA PARAMETERS ////////// + + // Connect to all detected cameras and start capturing (i.e. letting sensor get light != buffering) + for (unsigned int i = 0; i < nCameras; i++) { + D("Connecting camera " << i << "..."); + ppCameras[i] = PointGreyCamera::getCamera(i); + + // Power on the camera + D("Powering on camera " << i << "..."); + ppCameras[i]->powerCamera(true); + + D("Getting camera " << i << " info..."); + + // Print camera information + cout << ppCameras[i] << endl; + + // We will send a software trigger to one camera, which will be configured + // to send a strobe output to the rest of the cameras at the time of icoming + // trigger (since they are all in external trigger mode) + // free run) + bool isMaster = ppCameras[i]->getSerialNumber() == camStrobeOutSN; + if (isMaster) + iCamMaster = i; + + ppCameras[i]->setMaster(); + + // Shutter = 0 means auto shutter, which requires the camera to be in + // free-run mode + if (sh == 0.0f) + isMaster = true; + + try { + ppCameras[i]->init(isMaster); + } catch (...) { + printAndSaveError("Erorr polling for trigger ready!", captureDir); + ppCameras[iCamMaster]->toggleStrobeOut(pinStrobe, false); + disconnect(ppCameras, nCameras); + exit(EXIT_FAILURE); + } + } + + ////////// END OF SETTING CAMERA PARAMETERS ////////// + + if (FLAGS_nocapture) { + ppCameras[iCamMaster]->toggleStrobeOut(pinStrobe, false); + disconnect(ppCameras, nCameras); + cerr << "Done." << endl; + exit(EXIT_SUCCESS); + } + + if (iCamMaster == -1) { + printAndSaveError("No master camera!! Exiting...", captureDir); + ppCameras[iCamMaster]->toggleStrobeOut(pinStrobe, false); + disconnect(ppCameras, nCameras); + exit(EXIT_FAILURE); + } + + // Prepare arrays of dropped frame counts + unsigned int droppedFramesCount[nCameras]; + unsigned int droppedFramesCur[nCameras]; + unsigned int droppedFramesPrev[nCameras]; + + for (unsigned int i = 0; i < nCameras; i++) { + droppedFramesCount[i] = 0; + droppedFramesCur[i] = 0; + droppedFramesPrev[i] = 0; + } + + // Dump the camera names + ofstream cameraNamesFile( + string(captureDir + "/cameranames.txt")); + + for (unsigned int i = 0; i < nCameras; i++) { + cameraNamesFile << to_string(serialNumbers->at(i).second) << "\n"; + } + cameraNamesFile.close(); + + // Write stats to file + ofstream statsFile; + const string filenameStats = captureDir + "/stats.txt"; + statsFile.open(filenameStats.c_str(), std::fstream::app); + + + // Start capture on all slave cameras + for (unsigned int i = 0; i < nCameras; i++) { + if (i != iCamMaster) { + D("Starting slave camera " << i << " capture..."); + + // Start capturing images + PointGreyCamera::printError(ppCameras[i]->startCapture()); + } + } + + // Start capture on master camera + D("Starting master camera " << iCamMaster << " capture..."); + PointGreyCamera::printError(ppCameras[iCamMaster]->startCapture()); + + // If we do it before calling StartCapture cameras will start sending strobe + // pulses to before we want them to + D("Setting camera strobes..."); + + // Sometimes pin #1 is enabled for strobe out. Disable it. + for (unsigned int i = 0; i < nCameras; i++) { + ppCameras[i]->toggleStrobeOut(1, false); + } + + // Set master camera strobe. This will send the pulses that slaves are expecting + ppCameras[iCamMaster]->toggleStrobeOut(pinStrobe, true); + + if (sh == 0.0f) { + D("Sleeping 2 seconds to let auto shutter stabilize..."); + sleep(2); + } + + D("Grabbing " << FLAGS_nframes << " image(s) from each camera"); + + ////////// START OF FRAME CAPTURE ////////// + clock_gettime(CLOCK_REALTIME, &tStart); + + // create preview threads + thread* previewThread = new std::thread(preview, previewBuffer); + + // Create producer thread + stringstream* producerStatsString[PRODUCER_COUNT]; + thread* cameraProducerThread[PRODUCER_COUNT]; + + for (int pid = 0; pid < PRODUCER_COUNT; ++pid) { + producerStatsString[pid] = new stringstream; + cameraProducerThread[pid] = + new std::thread( + cameraProducer, + consumerBuffer, + previewBuffer, + ppCameras, + FLAGS_numcams, + FLAGS_nframes, + pid, + iCamMaster, + FLAGS_debug, + FLAGS_mono, + FLAGS_raw, + pinStrobe, + FLAGS_nbits, + &droppedFramesCount[pid], + &droppedFramesCur[pid], + &droppedFramesPrev[pid], + producerStatsString[pid], + captureDir); + } + + // Create consumer threads + thread* frameConsumerThread[CONSUMER_COUNT]; + stringstream* consumerStatsString[CONSUMER_COUNT]; + int fp[CONSUMER_COUNT]; + + for (int cid = 0; cid < CONSUMER_COUNT; ++cid) { + consumerStatsString[cid] = new stringstream; + + frameConsumerThread[cid] = + new std::thread( + frameConsumer, + consumerBuffer, + cid, + FLAGS_numcams, + FLAGS_nframes, + captureDir, + label, + consumerStatsString[cid]); + } + + int queueId = getControlQueue(); + + while (keepRunning) { + static const int kCmdSize = 60; + + struct cmd_msgbuf { + long mtype; + char cmd[kCmdSize]; + } msg; + + if (FLAGS_cli) { + char c; + cin >> c; + + if (c == 'r') { + startRecording = true; + stopRecording = false; + } else if (c == 's') { + stopRecording = true; + startRecording = false; + } else if (c == 'q') { + stopRecording = true; + startRecording = false; + keepRunning = false; + } + } else { + ssize_t size = msgrcv(queueId, &msg, sizeof(msg.cmd), 0, IPC_NOWAIT); + if (size > 0) { + if (strncmp(msg.cmd, "cam", min(strlen("cam"), sizeof(msg.cmd))) == 0) { + int k = -1; + int m = -1; + sscanf(msg.cmd, "cam %d %d", &k, &m); + cout << "command was: " << msg.cmd << endl; + if (k >= 0 && k < kNumPreviewCams) { + // set preview slot K to show camera M + setPreviewCam(k, sortedSerials->at(m).first); + } + } else if (strncmp(msg.cmd, "shutter", min(strlen("shutter"), sizeof(msg.cmd))) == 0) { + double shutter = 0.0; + sscanf(msg.cmd, "shutter %lf", &shutter); + // set new shutter value + for (int k = 0; k < FLAGS_numcams; ++k) { + ppCameras[k]->prepareShutterSpeedUpdate(shutter); + } + } else if (strncmp(msg.cmd, "gain", strlen("gain")) == 0) { + double gain = 0.0; + sscanf(msg.cmd, "gain %lf", &gain); + for (int k = 0; k < FLAGS_numcams; ++k) { + ppCameras[k]->prepareGainUpdate(gain); + } + } else if (strncmp(msg.cmd, "record", strlen("record")) == 0) { + startRecording = true; + stopRecording = false; + } else if (strncmp(msg.cmd, "stop", strlen("stop")) == 0) { + stopRecording = true; + startRecording = false; + } else if (strncmp(msg.cmd, "quit", strlen("quit")) == 0) { + stopRecording = true; + startRecording = false; + keepRunning = false; + } + } + } + } + + // Join frame producer threads + for (int pid = 0; pid < PRODUCER_COUNT; ++pid) { + cameraProducerThread[pid]->join(); + statsFile << producerStatsString[pid]->str(); + delete producerStatsString[pid]; + } + + D("Done"); + + // Join frame consumer threads + for (int cid = 0; cid < CONSUMER_COUNT; ++cid) { + frameConsumerThread[cid]->join(); + statsFile << consumerStatsString[cid]->str(); + delete consumerStatsString[cid]; + } + + clock_gettime(CLOCK_REALTIME, &tEnd); + + delete [] consumerBuffer; + delete serialNumbers; + ////////// END OF FRAME CAPTURE ////////// + + // Close files + statsFile.close(); + stopCapturing(ppCameras, nCameras); + + for (unsigned int i = 0; i < nCameras; i++) { + D("Camera " << ppCameras[i]->getSerialNumber() + << " dropped " << droppedFramesCount[i] << " frame(s)."); + saveDroppedFrames(ppCameras[i], droppedFramesCount[i], captureDir); + } + + // KP: Master camera will continue to send strobe pulses after stop capturing, + // so we manually disable it + ppCameras[iCamMaster]->toggleStrobeOut(pinStrobe, false); + disconnect(ppCameras, nCameras); + + D("Done capturing"); + exit(EXIT_SUCCESS); +} + +//////////////////////// END OF MAIN //////////////////////// diff --git a/surround360_camera_ctl/source/camera_control/CameraControl.hpp b/surround360_camera_ctl/source/camera_control/CameraControl.hpp new file mode 100755 index 00000000..15eb1478 --- /dev/null +++ b/surround360_camera_ctl/source/camera_control/CameraControl.hpp @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE_camera_ctl file in the root directory of this subproject. + */ + +#pragma once + +#include +#include +#include +#include + +#include "PointGrey.hpp" +#include "ProducerConsumer.h" + +#define PRODUCER_COUNT 1 +#define CONSUMER_COUNT 2 +#define FRAME_H 2048ULL +#define FRAME_W 2048ULL +#define FRAME_SIZE (FRAME_H * FRAME_W) +#define BUFFER_SIZE 1000ULL + +namespace surround360 { + namespace fc = FlyCapture2; + + struct FramePacket{ + int frameNumber; + int cameraNumber; + uint8_t* imageBytes; + }; + + // What we pass between the producer and consumer. + typedef ProducerConsumer ConsumerBuffer; + +} diff --git a/surround360_camera_ctl/source/camera_control/PointGrey.cpp b/surround360_camera_ctl/source/camera_control/PointGrey.cpp new file mode 100644 index 00000000..5793dda2 --- /dev/null +++ b/surround360_camera_ctl/source/camera_control/PointGrey.cpp @@ -0,0 +1,602 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE_camera_ctl file in the root directory of this subproject. + */ + +#include + +#include +#include +#include +#include + +using namespace std; +using namespace surround360; +using namespace fc; + +DECLARE_double(fps); +DECLARE_double(gain); +DECLARE_double(gamma); +DECLARE_double(exposure); +DECLARE_double(shutter); +DECLARE_double(brightness); + +PointGreyCamera::PointGreyCamera( + shared_ptr& camera, + fc::PGRGuid& guid) + : m_camera(camera), + isMaster(false), + m_guid(guid), + m_shutterSpeedUpdate(0.0), + m_shutterSpeed(FLAGS_shutter), + m_gainUpdate(0.0), + m_gain(FLAGS_gain) { +} + +BusManager& PointGreyCamera::getBusManager() { + static BusManager busMgr; + return busMgr; +} + +void PointGreyCamera::printError(int error, bool doExit) { + if (error != PGRERROR_OK) { + if (doExit) { + exit(EXIT_FAILURE); + } + } +} + +void PointGreyCamera::printError(Error error, bool doExit) { + printError(error.GetType(), doExit); +} + +unsigned int PointGreyCamera::findCameras() { + unsigned int numCameras = 0; + getBusManager().GetNumOfCameras(&numCameras); + return numCameras; +} + +shared_ptr PointGreyCamera::getCamera( + const unsigned int index) { + + shared_ptr camera(new fc::Camera()); + fc::PGRGuid guid; + fc::InterfaceType ifaceType; + getBusManager().GetCameraFromIndex(index, &guid); + PointGreyCameraPtr pgcam = shared_ptr(new PointGreyCamera(camera, guid)); + + pgcam->attach(); + + return pgcam; +} + +void* PointGreyCamera::getFrame() { + Image rawImage; + ImageMetadata metadata; + + Error error = m_camera->RetrieveBuffer(&rawImage); + if (error != PGRERROR_OK) { + throw "Error retrieving frame buffer."; + } + + metadata = rawImage.GetMetadata(); + droppedFramesCounter = metadata.embeddedFrameCounter; + + return rawImage.GetData(); +} + +int PointGreyCamera::toggleStrobeOut(int pin, bool onOff) { + StrobeControl strobeControl; + strobeControl.source = pin; + Error err = m_camera->GetStrobe(&strobeControl); + if (err != PGRERROR_OK) { + return err.GetType(); + } + + // Check if we need to change register + if (strobeControl.onOff == onOff) { + if (!onOff || (onOff && + strobeControl.polarity == 0 && + strobeControl.delay == 0.0f && + strobeControl.duration == 0.0f)) { + return 0; + } + } + + strobeControl.onOff = onOff; + + if (onOff) { + strobeControl.polarity = 0; // low (falling edge) signal + strobeControl.delay = 0.0f; // ms + strobeControl.duration = 0.0f; // ms. duration 0 = shutter time + } + + err = m_camera->SetStrobe(&strobeControl); + return err.GetType(); +} + +unsigned int PointGreyCamera::getDroppedFramesCounter() const { + return droppedFramesCounter; +} + +int PointGreyCamera::startCapture() { + fc::Error error = m_camera->StartCapture(); + if (error != PGRERROR_OK) { + cerr << "Error starting capture..." << endl; + cerr << "Error: " << error.GetType() << " " << error.GetDescription() << endl; + throw "Error starting capture on camera " + to_string(getSerialNumber()); + } + + return error.GetType(); +} + +int PointGreyCamera::stopCapture() { + fc::Error error = m_camera->StopCapture(); + if (error != PGRERROR_OK) { + throw "Error stopping capture on camera " + to_string(getSerialNumber()); + } + return error.GetType(); +} + +int PointGreyCamera::detach() { + fc::Error error = m_camera->Disconnect(); + return error.GetType(); +} + +int PointGreyCamera::attach() { + fc::Error error = m_camera->Connect(&m_guid); + return error.GetType(); +} + +int PointGreyCamera::setMaster() { + isMaster = true; + return 0; +} + +int PointGreyCamera::getSerialNumber() const { + CameraInfo camInfo; + m_camera->GetCameraInfo(&camInfo); + return camInfo.serialNumber; +} + +int PointGreyCamera::reset() { + unsigned int numMemChannels; + m_camera->GetMemoryChannelInfo(&numMemChannels); + + // Iterate to numMemChannels+1, since 0th channel is default + for (unsigned int k = 0; k < numMemChannels + 1; ++k) { + m_camera->RestoreFromMemoryChannel(k); + } + + return 0; +} + +string PointGreyCamera::getProperty(CameraProperty p) { + Property prop; + ostringstream oss; + + switch (p) { + case CameraProperty::BRIGHTNESS: + prop.type = fc::BRIGHTNESS; + break; + + case CameraProperty::GAIN: + prop.type = fc::GAIN; + break; + + case CameraProperty::GAMMA: + prop.type = fc::GAMMA; + break; + + case CameraProperty::SHUTTER: + prop.type = fc::SHUTTER; + break; + + case CameraProperty::WHITE_BALANCE: + prop.type = fc::WHITE_BALANCE; + break; + + default: + break; + } + + PropertyInfo propInfo(prop.type); + fc::Error error = m_camera->GetPropertyInfo(&propInfo); + if (error != PGRERROR_OK) { + throw "Error getting camera property."; + } + + if (propInfo.absValSupported) { + oss << setprecision(3) << fixed + << float(propInfo.min) << ";" << float(propInfo.max) << ";" + << propInfo.pUnitAbbr; + } else { + oss << float(propInfo.min) << ";" << float(propInfo.max); + } + return oss.str(); +} + +int PointGreyCamera::powerCamera(bool on) { + const unsigned int kCameraPower = 0x610; + const unsigned int kPowerVal = on ? 0x80000000 : 0x00000000; + const unsigned int kSleepMsPowerOff = 100; + unsigned int regVal = 0; + unsigned int retries = 10; + + // Check if we need to modify register + m_camera->ReadRegister(kCameraPower, ®Val); + if ((regVal & kPowerVal) != 0) { + return 0; + } + + printError(m_camera->WriteRegister(kCameraPower, kPowerVal)); + + // Wait for camera to complete power-up + do { + usleep(kSleepMsPowerOff * 1000); + + Error error = m_camera->ReadRegister(kCameraPower, ®Val); + if (error == PGRERROR_TIMEOUT) { + // ignore timeout errors, camera may not be responding to + // register reads during power-up + } + + printError(error); + + retries--; + } while ((regVal & kPowerVal) == 0 && retries > 0); + + return 0; +} + +bool PointGreyCamera::pollForTriggerReady() { + const unsigned int k_softwareTrigger = 0x62C; + Error error; + unsigned int regVal = 0; + + do { + error = m_camera->ReadRegister(k_softwareTrigger, ®Val); + if (error != PGRERROR_OK) { + printError(error); + return false; + } + } while ((regVal >> 31) != 0); + + return true; +} + +int PointGreyCamera::init(bool isMaster) { + const int pinStrobe = 2; // pin #3 (red wire) + const int pinTrigger = 3; // pin #4 (green wire) + + // Time (ms) to wait for an image when calling RetrieveBuffer + const int timeoutBuffer = -1; + fc::InterfaceType ifType; + + getBusManager().GetInterfaceTypeFromGuid(&m_guid, &ifType); + if (ifType != fc::INTERFACE_USB3) { + return -1; + } + + setPixelFormat(PIXEL_FORMAT_RAW8); + setCameraTrigger(isMaster); + + if (!isMaster) { + // Poll to ensure camera is ready + if (!pollForTriggerReady()) { + throw "Error polling for trigger ready!"; + } + } + + // Set camera properties + setCameraProps(); + + // Auto shutter + if (FLAGS_shutter == 0.0f) { + setCameraPropAbs(fc::SHUTTER, "0.000"); + } + + // embed frame counter into image metadata + embedImageInfo(); + + // Set grabbing mode and buffer retrieval timeout + setCameraGrabMode(timeoutBuffer); +} + +void PointGreyCamera::embedImageInfo() { + EmbeddedImageInfo embeddedInfo; + m_camera->GetEmbeddedImageInfo(&embeddedInfo); + embeddedInfo.frameCounter.onOff = true; + embeddedInfo.shutter.onOff = false; + embeddedInfo.gain.onOff = false; + m_camera->SetEmbeddedImageInfo(&embeddedInfo); + +} + +std::ostream& PointGreyCamera::printCameraInfo(std::ostream& stream) const { + CameraInfo camInfo; + + m_camera->GetCameraInfo(&camInfo); + + stream << "Serial number: " << getSerialNumber() << endl + << "Camera model: " << camInfo.modelName << endl + << "Camera vendor: " << camInfo.vendorName << endl + << "Sensor: " << camInfo.sensorInfo << endl + << "Resolution: " << camInfo.sensorResolution << endl + << "FW version: " << camInfo.firmwareVersion << endl + << "FW build time: " << camInfo.firmwareBuildTime << endl; + + return stream; +} + +void PointGreyCamera::setCameraTrigger(bool isMaster) { + TriggerMode triggerMode; + + m_camera->GetTriggerMode(&triggerMode); + + // Set external trigger params + // If single image all cameras set to external trigger + const bool isExternalTrigger = !isMaster; + + // Check if trigger is already set + if ((triggerMode.onOff == isExternalTrigger && isExternalTrigger == false) + || + (triggerMode.onOff == isExternalTrigger && + triggerMode.mode == 0 && + triggerMode.parameter == 0 && + triggerMode.source == 3)) { + return; + } + + triggerMode.onOff = isExternalTrigger; + + // If camera set to trigger on external pulse, it will receive the trigger + // via pin #4 (green wire) + if (isExternalTrigger) { + triggerMode.mode = 0; + triggerMode.parameter = 0; // only necessary if multi-shot trigger + triggerMode.source = 3; + } + + m_camera->SetTriggerMode(&triggerMode); +} + +bool PointGreyCamera::setCameraPropRel( + PropertyType propType, + unsigned int value) { + Error error; + + // Get property + Property prop; + prop.type = propType; + error = m_camera->GetProperty(&prop); + if (error != PGRERROR_OK) { + printError( error ); + return false; + } + + // Modify property + prop.autoManualMode = false; + prop.valueA = value; + + // Set property + error = m_camera->SetProperty(&prop); + if (error != PGRERROR_OK) { + printError( error ); + return false; + } + + return true; +} + +bool PointGreyCamera::setCameraPropAbs( + PropertyType propType, + const char* value) { + Error error; + const string val(value); + + // Get property + Property prop; + prop.type = propType; + error = m_camera->GetProperty(&prop); + if (error != PGRERROR_OK) { + printError( error ); + return false; + } + + PropertyInfo propInfo; + propInfo.type = propType; + error = m_camera->GetPropertyInfo(&propInfo); + + // Modify property + if (propType == fc::SHUTTER && val == "0.000") { + prop.autoManualMode = true; // sh = 0 enables auto mode + } else { + prop.autoManualMode = false; + } + if (propInfo.absValSupported) { + prop.absControl = true; + } + prop.onePush = false; + prop.onOff = true; + + if (propType == fc::FRAME_RATE && val == "0.000") { + prop.onOff = false; + } else { + if (propType == fc::WHITE_BALANCE) { + prop.onOff = false; + } else { + prop.absValue = stof(val); + } + } + + // Set property + error = m_camera->SetProperty(&prop); + if (error != PGRERROR_OK) { + printError(error); + return false; + } + + return true; +} + +void PointGreyCamera::prepareShutterSpeedUpdate(double shutter) { + if (shutter != m_shutterSpeed) { + m_shutterSpeedUpdate = shutter; + } +} + +void PointGreyCamera::commitShutterSpeedUpdate() { + if (m_shutterSpeedUpdate != 0.0) { + setCameraPropAbs(fc::SHUTTER, to_string(m_shutterSpeedUpdate).c_str()); + m_shutterSpeed = m_shutterSpeedUpdate; + m_shutterSpeedUpdate = 0.0; + } +} + +void PointGreyCamera::prepareGainUpdate(double gain) { + if (gain != m_gain) { + m_gainUpdate = gain; + } +} + +void PointGreyCamera::commitGainUpdate() { + if (m_gainUpdate != 0.0) { + setCameraPropAbs(fc::GAIN, to_string(m_gainUpdate).c_str()); + m_gain = m_gainUpdate; + m_gainUpdate = 0.0; + } +} + +bool PointGreyCamera::setCameraProps() { + if (!setCameraPropAbs( + fc::AUTO_EXPOSURE, + to_string(FLAGS_exposure).c_str())) { + return false; + } + + if (!setCameraPropAbs( + fc::BRIGHTNESS, + to_string(FLAGS_brightness).c_str())) { + return false; + } + + if (!setCameraPropAbs( + fc::GAMMA, + to_string(FLAGS_gamma).c_str())) { + return false; + } + + if (!setCameraPropAbs( + fc::FRAME_RATE, + to_string(FLAGS_fps).c_str())) { + return false; + } + + if (!setCameraPropAbs( + fc::SHUTTER, + to_string(FLAGS_shutter).c_str())) { + return false; + } + + if (!setCameraPropAbs( + fc::GAIN, + to_string(FLAGS_gain).c_str())) { + return false; + } + + return true; +} + +void PointGreyCamera::setCameraGrabMode(int timeoutBuffer) { + // Get the camera configuration + FC2Config config; + m_camera->GetConfiguration(&config); + + // Set the grab timeout (miliseconds) - TIMEOUT_INFINITE to wait indefinitely + config.grabTimeout = timeoutBuffer; + + // Set grab mode + // According to the libdc1394 standard, "setting the drop_frames member of the + // dc1394_cameracapture structure to a non-zero value causes the DMA capture + // functions to throw away all frames buffered in the DMA ring buffer except + // the latest one, which is returned to you. + // BUFFER_FRAMES uses the camera buffer to store some frames so we don't drop + // them + config.grabMode = BUFFER_FRAMES; + + // Setting a large number of buffers for each camera may cause the program to + // hang trying to allocate resources. Try less than 10 + config.numBuffers = 5; + + // Enbale high performance mode + config.highPerformanceRetrieveBuffer = true; + + // Set the camera configuration + m_camera->SetConfiguration(&config); +} + + +void PointGreyCamera::setPixelFormat(PixelFormat pf) { + Mode mode = MODE_7; + bool supported; + + // Query for available Format 7 modes + Format7Info fmt7Info; + fmt7Info.mode = mode; + + m_camera->GetFormat7Info(&fmt7Info, &supported); + + if (!supported) { + cerr << "Warning: MODE_7 not supported. Falling back to MODE_0" << endl; + mode = MODE_0; + fmt7Info.mode = mode; + printError(m_camera->GetFormat7Info(&fmt7Info, &supported)); + } + + Format7ImageSettings fmt7ImageSettings; + fmt7ImageSettings.mode = mode; + fmt7ImageSettings.offsetX = 0; + fmt7ImageSettings.offsetY = 0; + fmt7ImageSettings.width = fmt7Info.maxWidth; + fmt7ImageSettings.height = fmt7Info.maxHeight; + + fmt7ImageSettings.pixelFormat = pf; + + bool valid; + Format7PacketInfo fmt7PacketInfo; + + // Validate the settings + printError(m_camera->ValidateFormat7Settings( + &fmt7ImageSettings, + &valid, + &fmt7PacketInfo)); + + if (!valid) { + // Settings are not valid + cerr << "Format7 settings are not valid" << endl; + exit(EXIT_FAILURE); + } + + // Set the settings to the camera + printError(m_camera->SetFormat7Configuration( + &fmt7ImageSettings, + fmt7PacketInfo.maxBytesPerPacket)); +} + +PointGreyCamera::~PointGreyCamera() { + detach(); +} + +ostream& surround360::operator<<(ostream& stream, const PointGreyCamera& c) { + c.printCameraInfo(stream); + return stream; +} + +ostream& surround360::operator<<(ostream& stream, const PointGreyCameraPtr& cp) { + stream << *cp; + return stream; +} diff --git a/surround360_camera_ctl/source/camera_control/PointGrey.hpp b/surround360_camera_ctl/source/camera_control/PointGrey.hpp new file mode 100755 index 00000000..dc475f2c --- /dev/null +++ b/surround360_camera_ctl/source/camera_control/PointGrey.hpp @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE_camera_ctl file in the root directory of this subproject. + */ + +#pragma once + +#include + +#include +#include +#include + +#include + +namespace surround360 { + namespace fc = FlyCapture2; + + class PointGreyCamera; + typedef std::shared_ptr PointGreyCameraPtr; + + class PointGreyCamera : protected Camera { + public: + static const unsigned int FRAME_WIDTH = 2048; + static const unsigned int FRAME_HEIGHT = 2048; + static const unsigned int FRAME_SIZE = FRAME_WIDTH * FRAME_HEIGHT; + + static std::shared_ptr getCamera(const unsigned int index); + static unsigned int findCameras(); + + int attach(); + int detach(); + int init(bool isMaster = false); + int stopCapture(); + int startCapture(); + int setMaster(); + + void* getFrame(); + unsigned int getDroppedFramesCounter() const; + int getSerialNumber() const; + int reset(); + int powerCamera(bool onOff); + int toggleStrobeOut(int pin, bool onOff); + void prepareShutterSpeedUpdate(double shutter); + void commitShutterSpeedUpdate(); + void prepareGainUpdate(double gain); + void commitGainUpdate(); + + static void printError(int error, bool doExit = true); + static void printError(fc::Error error, bool doExit = true); + + enum CameraProperty { + BRIGHTNESS = 0, + GAIN, + GAMMA, + SHUTTER, + WHITE_BALANCE, + FRAME_RATE, + }; + + std::string getProperty(CameraProperty p); + + ~PointGreyCamera(); + + private: + std::shared_ptr m_camera; + bool isMaster; + unsigned int droppedFramesCounter; + fc::PGRGuid m_guid; + fc::InterfaceType m_ifaceType; + + double m_shutterSpeedUpdate; + double m_shutterSpeed; + double m_gainUpdate; + double m_gain; + + private: + static fc::BusManager& getBusManager(); + + PointGreyCamera(std::shared_ptr& camera, fc::PGRGuid& guid); + PointGreyCamera(PointGreyCamera& camera); + + void setCameraTrigger(bool isMaster); + + bool pollForTriggerReady(); + void setCameraGrabMode(int timeoutBuffer); + + void updateDroppedFrames( + fc::ImageMetadata meta, + unsigned int* droppedFrames, + unsigned int* droppedFramesCur, + unsigned int i); + + void setCameraPixelFormat(fc::PixelFormat pf); + + bool setCameraProps(); + + bool setCameraPropRel( + fc::PropertyType propType, + unsigned int value); + + bool setCameraPropAbs( + fc::PropertyType propType, + const char* value); + + + void printAndSaveCameraProperties(bool isRaw); + + std::ostream& printCameraInfo(std::ostream& stream) const; + + void saveCMDArgs(const std::string& destDir, int argc, char* argv[]); + + void saveDroppedFrames( + unsigned int droppedFrames, + const std::string& destDir); + + std::string getPropsRange(bool isRaw); + + float getPropAbsFromRel( + fc::Camera* pCam, + fc::PropertyType propType, + unsigned int value); + + std::string getPropRange(fc::PropertyType propType); + + int getPropRelFromAbs( + fc::PropertyType propType, + const char* value); + + void embedImageInfo(); + + void setPixelFormat(fc::PixelFormat pf); + + void getPixelFormatFromBitDepth(fc::PixelFormat* pf, unsigned int nBits); + + friend std::ostream& operator<<( + std::ostream& stream, + const PointGreyCamera& c); + + }; + + std::ostream& operator<<( + std::ostream& stream, + const PointGreyCamera& c); + + std::ostream& operator<<( + std::ostream& stream, + const PointGreyCameraPtr& c); +} diff --git a/surround360_camera_ctl/source/camera_control/ProducerConsumer.h b/surround360_camera_ctl/source/camera_control/ProducerConsumer.h new file mode 100644 index 00000000..35ddbe9c --- /dev/null +++ b/surround360_camera_ctl/source/camera_control/ProducerConsumer.h @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE_camera_ctl file in the root directory of this subproject. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace surround360 { +/// An in-place, thread-aware producer consumer messaging class +/// +/// This is an implementation of a thread aware producer/consumer +/// class that uses an in-place circular buffer. In-place means that +/// the circular buffer directly stores the items messaged from the +/// producer to the consumer. As such, care must be taken to insure +/// that both head/tail access and head/tail advancement are mutually +/// protected from overwrite and wrap around. We achieve this using +/// two condition variables that "signal" when the producer and +/// consumer can proceed. The circular buffer state is protected +/// using a single mutex. Strictly speaking only a head and tail +/// pointer are necessary and sufficient for proper operation of the +/// circular buffer, however a count is added to make the code easier +/// read and maintain. An additional "done" routine is provided so +/// that the producer can signal the consumer when the producer is +/// truly done producing items. +/// + template + class ProducerConsumer { + private : + T items[LENGTH]; + std::mutex m; + std::condition_variable dataAvailable; + std::condition_variable spaceAvailable; + int head; + int tail; + int count; + bool fini; + + public: + // Don't allow any assignment or copies + ProducerConsumer(ProducerConsumer const&) = delete; + ProducerConsumer& operator=(ProducerConsumer const&) = delete; + + /// The only constructor is the default constructor + /// + /// We do this because the size of the circular buffer used to + /// share data between the producer and consumer is statically + /// allocated. + ProducerConsumer() + : head(0), tail(0), count(0), fini(false) + { + memset(items, 0, LENGTH * sizeof(T)); + } + + void setBuffers(void *ptr, size_t size) + { + uint64_t vaddr = reinterpret_cast(ptr); + for (int k = 0; k < LENGTH; k++) { + items[k].imageBytes = reinterpret_cast(vaddr + k * size); + } + } + + /// Signal the consuemr that producer is done producing. + /// + /// Used by the producer to notify the consumer that when the queue + /// empty it can exit. + void done() { + std::unique_lock lk(m); + fini = true; + lk.unlock(); + dataAvailable.notify_one(); + } + + /// Access the head of the message queue. + /// + /// The routine will block if the queue is full. It used by the + /// producer to copy data into the queue. + /// + /// @return Returns a poimnter to the head of the queue. + T* getHead() { + std::unique_lock lk(m); + spaceAvailable.wait(lk, [this]{return this->count < LENGTH;}); + lk.unlock(); + return &items[head]; + } + + /// Sends the message the value onto the queue. + /// + /// Thread safe queue state update. + void advanceHead() { + std::unique_lock lk(m); + head = (head + 1) % LENGTH; + ++count; + lk.unlock(); + dataAvailable.notify_one(); + } + + /// Accesses data at the tail of the message queue. + /// + /// This routine will block until there is something available. The + /// normal usage is for the consumer to loop on the value of this + /// pointer until it null indicating that the queue is empty and the + /// producer is done producing. + /// + // @return Returns the pointer to the data or null if the queue is + // empty and the producer has called done(). + T* getTail() { + T* val = nullptr; + std::unique_lock lk(m); + dataAvailable.wait(lk, [this]{return this->count > 0 || fini;}); + if (this->count > 0) { + val = &items[tail]; + } + lk.unlock(); + return val; + } + + /// Advances the tail pointer indicating tail consumption. + /// + /// Used by the consumer to signal that it has infact + /// consumed/used/copied the last getTail. + void advanceTail() { + std::unique_lock lk(m); + if (this->count > 0) { + tail = (tail + 1) % LENGTH; + --count; + } + lk.unlock(); + spaceAvailable.notify_one(); + } + + // Used to debug the state of the circular buffer + std::string stateString() { + std::stringstream ss; + m.lock(); + ss << "[head=" << tail + << " tail=" << head + << " count=" << count + << " fini=" << fini + << "]"; + m.unlock(); + return ss.str(); + } + }; +} diff --git a/surround360_camera_ctl/source/camera_control/README.txt b/surround360_camera_ctl/source/camera_control/README.txt new file mode 100755 index 00000000..2cbba948 --- /dev/null +++ b/surround360_camera_ctl/source/camera_control/README.txt @@ -0,0 +1,48 @@ +** NOTE ** +Assuming we are located at ~/fbsource/fbcode/vr_camera_hw/bin + +** NOTE ** +CameraControl creates log files in /var/log/vrcam. If we need any of those for processing (e.g. cameranames.txt) we have to copy them afterwards to the desired location! +sudo cp /var/log/vrcam/captures//cameranames.txt /media/snoraid/_cameranames.txt + +** NOTE ** +Before running CameraControl, we ALWAYS need to force completion of disk writes (via sync) and free pagecache, dentries and inodes (via drop_caches): +sync; echo 3 | sudo /usr/bin/tee /proc/sys/vm/drop_caches; sudo ./CameraControl [options] + +== Print help == +./CameraControl --help + +== Print camera properties and value ranges == +./CameraControl --props --debug + +== Single image with default params (-d outputs debug statements) == +./CameraControl --nframes 1 --numcams 17 --debug + +== Single image with params == +sudo ./CameraControl --nframes 1 --brightness 13.5 --exposure 0.18 --gamma 2.3 --shutter 10.0 --gain 4.500 --whitebalance "300 500" --numcams 17 --debug + +== Single 8-bit RAW image == +sudo ./CameraControl --nframes 1 --raw --nbits 8 --brightness 10.449 --exposure 2.400 --shutter 100.0 --gain 7.000 --numcams 17 --debug + +== Video: 5 frames at 30 fps == +sudo ./CameraControl --debug --raw --nbits 8 --fps 30 --shutter 10 --gain 0 --nframes 5 --numcams 17 + +== High Dynamic Range: shutter in [3ms 10ms 20ms 30ms], gain in [0dB 2dB 5dB 7dB] == +sudo ./CameraControl --debug --raw --nbits 8 --fps 30 --brightness 1 --nframes 1 --hdr "3 10 20 30 0 2 5 7" --numcams 17 + +== Autoexposure (use as -fps the frame rate that will be used for video, so we don't go over 1/fps) == +No need to add "--gain 0". If camera auto gain is used it's because its auto shutter is maxed out, and that's what we care about + +sudo ./CameraControl --debug --brightness 1 --gain 0 --autoexposure --fps 30 --numcams 17 + +NOTE: If sh << 1/fps we may see aliasing. Try to keep the shutter close to 1/fps (may introduce motion blur, though). + +== Restore cameras == +sudo bin/CameraControl --debug --numcams 17 --restore + +== List all serial numbers for cameras + +sudo ./CameraControl --list + +== Common command for Point Grey's == +sync; echo 3 | sudo /usr/bin/tee /proc/sys/vm/drop_caches; sudo ./CameraControl -n 5 -raw -nbits 8 -br 1 -sh 20.0 -ga 0 -fps 30 -master 15405803 -numcams 17 -dir -d; sudo cp /var/log/vrcam/captures//cameranames.txt /media/snoraid/_cameranames.txt diff --git a/surround360_camera_ctl/source/camera_control/pc_test.cpp b/surround360_camera_ctl/source/camera_control/pc_test.cpp new file mode 100644 index 00000000..efad6c06 --- /dev/null +++ b/surround360_camera_ctl/source/camera_control/pc_test.cpp @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE_camera_ctl file in the root directory of this subproject. + */ + +#include +#include +#include +#include +#include "ProducerConsumer.h" + +#undef DEBUG_PUSH + +using namespace std; +using namespace surround360; + +#define BUFFER_SIZE 47 +#define CONSUMER_COUNT 5 +#define IO_SIZE (BUFFER_SIZE * CONSUMER_COUNT * 200) + +int output[IO_SIZE]; + +// Random time upto in 100's micro-second range This is to jitter the +// producer and consumer as if they have indeterminate timing. +inline useconds_t randomSleep(const int s) { + return usleep(random() & ((1 << s) - 1)); +} + + +// Consumer thread function +void consumer(ProducerConsumer* cb) { + // Keep popping values off until we're handed a null pointer + // indicating that there is no more data to consume. + int* ptr; + while ((ptr = cb->getTail()) != NULL) { + output[*ptr] = *ptr; + cb->advanceTail(); + randomSleep(10); + } +} + + +// Producer thread - should only be one of these for this test. +void producer(const int itemCount, + const int consumerCount, + ProducerConsumer* cb) { + + for (int i = 0; i < itemCount; ++i) { + const int cid = i % consumerCount; + int* ptr = cb[cid].getHead(); + *ptr = i; + cb[cid].advanceHead(); +#ifdef DEBUG_PUSH + cout << "pushed[" << cid << "] = " << i + << " " << cb[cid].stateString() << endl; +#endif + randomSleep(3); + } + + // Notify all the consumers when we're done + for (int cid = 0; cid < consumerCount; ++cid) { + cb[cid].done(); + } +} + + +int main(int argc, char* argv[]) { + // Set the output to "known" marker value + for (int i = 0; i < IO_SIZE; i++) { + output[i] = 0xdeadbeef; + } + + // Create all producer consumer queues + ProducerConsumer cb[CONSUMER_COUNT]; + + // Create and start the consumer threads + thread* consumerThreads[CONSUMER_COUNT]; + + for (int i = 0; i < CONSUMER_COUNT; ++i) { + consumerThreads[i] = new thread(consumer, &cb[i]); + } + + // Create the producer thread + thread producerThread(producer, IO_SIZE, CONSUMER_COUNT, cb); + + // Wait for the threads to finish + producerThread.join(); + for (int i = 0; i < CONSUMER_COUNT; ++i) { + consumerThreads[i]->join(); + } + + int status = EXIT_SUCCESS; + for (int i = 0; i < IO_SIZE; i++) { + if (i != output[i]) { + cerr << "Failed p-c test at " << i << " != " + << output[i] << " == 0x" << hex << output[i] << dec << endl; + status = EXIT_FAILURE; + } + } + exit(status); +} diff --git a/surround360_camera_ctl/source/www/camera_capture/README.txt b/surround360_camera_ctl/source/www/camera_capture/README.txt new file mode 100755 index 00000000..55151485 --- /dev/null +++ b/surround360_camera_ctl/source/www/camera_capture/README.txt @@ -0,0 +1,2 @@ +== Things to modify/consider when putting this code on a new computer/camera rig == +- capture_image.php: $num_cameras, $fps, master camera serial number \ No newline at end of file diff --git a/surround360_camera_ctl/source/www/camera_capture/camProps_raw b/surround360_camera_ctl/source/www/camera_capture/camProps_raw new file mode 100755 index 00000000..0cf1b92d --- /dev/null +++ b/surround360_camera_ctl/source/www/camera_capture/camProps_raw @@ -0,0 +1,2 @@ +*PROP*;shutter;0.016;33.258;ms;20.000;-shutter +*PROP*;gain;0.000;9.827;dB;0.000;-gain diff --git a/surround360_camera_ctl/source/www/camera_capture/camera_control.php b/surround360_camera_ctl/source/www/camera_capture/camera_control.php new file mode 100755 index 00000000..10b8ae81 --- /dev/null +++ b/surround360_camera_ctl/source/www/camera_capture/camera_control.php @@ -0,0 +1,68 @@ +'; + + foreach ($properties as $props) { + $name_id = $props[5]; + $input_params = 'min="'.$props[1].'" max="'.$props[2].'" value="'.$props[4].'" step="'.end($props).'"'; + echo ''; + echo ''.ucfirst($props[0]).''; + echo ''; + echo ''; + echo ''.$props[3].''; + echo ''; + echo ''; + } + echo ''; +?> diff --git a/surround360_camera_ctl/source/www/camera_capture/capture_image.php b/surround360_camera_ctl/source/www/camera_capture/capture_image.php new file mode 100755 index 00000000..11c314b1 --- /dev/null +++ b/surround360_camera_ctl/source/www/camera_capture/capture_image.php @@ -0,0 +1,48 @@ + $value) { + // Ignore parameters that do not start with '-' + if (strpos($cmd, '-') !== 0) { + continue; + } + if ($cmd === '-whitebalance1' || $cmd === '-whitebalance2') { + $cmd_wb[] = $value; + } else { + $cmd_options .= sprintf(' %s %s', $cmd, $value); + } + } + + if (count($cmd_wb) > 0) { + $cmd_options .= sprintf(' -whitebalance "%s %s"', $cmd_wb[0], $cmd_wb[1]); + } + + $command = '/usr/local/bin/CameraControl '.$cmd_options; + $pid = exec('nohup '.$command.' > /dev/null 2>&1 & echo $!; ', $output); + + // Return PID and output directory + echo json_encode(array($pid, $output_dir)); +?> diff --git a/surround360_camera_ctl/source/www/camera_capture/check_errors.php b/surround360_camera_ctl/source/www/camera_capture/check_errors.php new file mode 100755 index 00000000..59c61b38 --- /dev/null +++ b/surround360_camera_ctl/source/www/camera_capture/check_errors.php @@ -0,0 +1,15 @@ + diff --git a/surround360_camera_ctl/source/www/camera_capture/css/customized-ui.css b/surround360_camera_ctl/source/www/camera_capture/css/customized-ui.css new file mode 100755 index 00000000..5ff63430 --- /dev/null +++ b/surround360_camera_ctl/source/www/camera_capture/css/customized-ui.css @@ -0,0 +1,90 @@ +body { + background: #333333; + color: #555555; + padding: 0; + margin: 0; + font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; + font-weight: normal; + font-style: normal; + line-height: 1; + position: relative; + cursor: default; +} + +h1, h2, h3, h4, h5, h6 { + font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; + font-weight: 300; + font-style: normal; + color: #222222; + text-rendering: optimizeLegibility; + margin-top: 0.2rem; + margin-bottom: 0.5rem; + line-height: 1.4; +} + +h3 { + font-size: 1.375rem; +} + +h4 { + font-size: 1.525rem; + margin-top: 1.2rem; + display: inline-block; +} + +#wrapper { + background: #fff; +} + +.hero { + background: #3b5998; +} + +.row { + width: 100%; + margin-left: auto; + margin-right: auto; + margin-top: 0; + margin-bottom: 0; + max-width: 62.5rem; + *zoom: 1; +} + +.row:before, .row:after { + content: " "; + display: table; +} + +.row:after { + clear: both; +} + +.columns { + position: relative; + width: 100%; + float: left; +} + +button, .button { + cursor: pointer; + font-size: 0.9em; + margin: 0 0 1.25rem; + position: relative; + text-align: center; + display: inline-block; + padding: 5px 10px 5px 10px; + background-color: #6bb933; + color: white; + border-radius: 15px; +} + +table { + background: white; +} + +table tr th, table tr td { + padding: 0rem 0.625rem; + font-size: 0.875rem; + color: #222222; +} + diff --git a/surround360_camera_ctl/source/www/camera_capture/css/jquery-ui.css b/surround360_camera_ctl/source/www/camera_capture/css/jquery-ui.css new file mode 100755 index 00000000..4b955f04 --- /dev/null +++ b/surround360_camera_ctl/source/www/camera_capture/css/jquery-ui.css @@ -0,0 +1,1225 @@ +/*! jQuery UI - v1.11.4 - 2015-03-11 +* http://jqueryui.com +* Includes: core.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, draggable.css, menu.css, progressbar.css, resizable.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px +* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-clearfix { + min-height: 0; /* support: IE7 */ +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); /* support: IE8 */ +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; +} + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin: 2px 0 0 0; + padding: .5em .5em .5em .7em; + min-height: 0; /* support: IE7 */ + font-size: 100%; +} +.ui-accordion .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-icons .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-header .ui-accordion-header-icon { + position: absolute; + left: .5em; + top: 50%; + margin-top: -8px; +} +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; +} +.ui-autocomplete { + position: absolute; + top: 0; + left: 0; + cursor: default; +} +.ui-button { + display: inline-block; + position: relative; + padding: 0; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + overflow: visible; /* removes extra width in IE */ +} +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2.2em; +} +/* button elements seem to need a little more width */ +button.ui-button-icon-only { + width: 2.4em; +} +.ui-button-icons-only { + width: 3.4em; +} +button.ui-button-icons-only { + width: 3.7em; +} + +/* button text element */ +.ui-button .ui-button-text { + display: block; + line-height: normal; +} +.ui-button-text-only .ui-button-text { + padding: .4em 1em; +} +.ui-button-icon-only .ui-button-text, +.ui-button-icons-only .ui-button-text { + padding: .4em; + text-indent: -9999999px; +} +.ui-button-text-icon-primary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 1em .4em 2.1em; +} +.ui-button-text-icon-secondary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 2.1em .4em 1em; +} +.ui-button-text-icons .ui-button-text { + padding-left: 2.1em; + padding-right: 2.1em; +} +/* no icon support for input elements, provide padding by default */ +input.ui-button { + padding: .4em 1em; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon, +.ui-button-text-icon-primary .ui-icon, +.ui-button-text-icon-secondary .ui-icon, +.ui-button-text-icons .ui-icon, +.ui-button-icons-only .ui-icon { + position: absolute; + top: 50%; + margin-top: -8px; +} +.ui-button-icon-only .ui-icon { + left: 50%; + margin-left: -8px; +} +.ui-button-text-icon-primary .ui-button-icon-primary, +.ui-button-text-icons .ui-button-icon-primary, +.ui-button-icons-only .ui-button-icon-primary { + left: .5em; +} +.ui-button-text-icon-secondary .ui-button-icon-secondary, +.ui-button-text-icons .ui-button-icon-secondary, +.ui-button-icons-only .ui-button-icon-secondary { + right: .5em; +} + +/* button sets */ +.ui-buttonset { + margin-right: 7px; +} +.ui-buttonset .ui-button { + margin-left: 0; + margin-right: -.3em; +} + +/* workarounds */ +/* reset extra padding in Firefox, see h5bp.com/l */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +.ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} +.ui-datepicker .ui-datepicker-next { + right: 2px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 45%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} +.ui-dialog { + overflow: hidden; + position: absolute; + top: 0; + left: 0; + padding: .2em; + outline: 0; +} +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + position: relative; +} +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 0; + white-space: nowrap; + width: 90%; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 20px; + margin: -10px 0 0 0; + padding: 1px; + height: 20px; +} +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; +} +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin-top: .5em; + padding: .3em 1em .5em .4em; +} +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} +.ui-dialog .ui-resizable-se { + width: 12px; + height: 12px; + right: -5px; + bottom: -5px; + background-position: 16px 16px; +} +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} +.ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-menu { + list-style: none; + padding: 0; + margin: 0; + display: block; + outline: none; +} +.ui-menu .ui-menu { + position: absolute; +} +.ui-menu .ui-menu-item { + position: relative; + margin: 0; + padding: 3px 1em 3px .4em; + cursor: pointer; + min-height: 0; /* support: IE7 */ + /* support: IE10, see #8844 */ + list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); +} +.ui-menu .ui-menu-divider { + margin: 5px 0; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-state-focus, +.ui-menu .ui-state-active { + margin: -1px; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item { + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: 0; + bottom: 0; + left: .2em; + margin: auto 0; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + left: auto; + right: 0; +} +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} +.ui-progressbar .ui-progressbar-value { + margin: -1px; + height: 100%; +} +.ui-progressbar .ui-progressbar-overlay { + background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); + height: 100%; + filter: alpha(opacity=25); /* support: IE8 */ + opacity: 0.25; +} +.ui-progressbar-indeterminate .ui-progressbar-value { + background-image: none; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; + -ms-touch-action: none; + touch-action: none; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-selectable { + -ms-touch-action: none; + touch-action: none; +} +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + /* Support: IE7 */ + overflow-x: hidden; + padding-bottom: 1px; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-button { + display: inline-block; + overflow: hidden; + position: relative; + text-decoration: none; + cursor: pointer; +} +.ui-selectmenu-button span.ui-icon { + right: 0.5em; + left: auto; + margin-top: -8px; + position: absolute; + top: 50%; +} +.ui-selectmenu-button span.ui-selectmenu-text { + text-align: left; + padding: 0.4em 2.1em 0.4em 1em; + display: block; + line-height: 1.4; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; + -ms-touch-action: none; + touch-action: none; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* support: IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} +.ui-sortable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} +.ui-spinner-input { + border: none; + background: none; + color: inherit; + padding: 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 22px; +} +.ui-spinner-button { + width: 16px; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} +/* more specificity required here to override default borders */ +.ui-spinner a.ui-spinner-button { + border-top: none; + border-bottom: none; + border-right: none; +} +/* vertically center icon */ +.ui-spinner .ui-icon { + position: absolute; + margin-top: -8px; + top: 50%; + left: 0; +} +.ui-spinner-up { + top: 0; +} +.ui-spinner-down { + bottom: 0; +} + +/* TR overrides */ +.ui-spinner .ui-icon-triangle-1-s { + /* need to fix icons sprite */ + background-position: -65px -16px; +} +.ui-tabs { + position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ + padding: .2em; +} +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom-width: 0; + padding: 0; + white-space: nowrap; +} +.ui-tabs .ui-tabs-nav .ui-tabs-anchor { + float: left; + padding: .5em 1em; + text-decoration: none; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { + cursor: text; +} +.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { + cursor: pointer; +} +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + max-width: 300px; + -webkit-box-shadow: 0 0 5px #aaa; + box-shadow: 0 0 5px #aaa; +} +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif; + font-size: 1.1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif; + font-size: 1em; +} +.ui-widget-content { + border: 1px solid #dddddd; + background: #eeeeee url("images/ui-bg_highlight-soft_100_eeeeee_1x100.png") 50% top repeat-x; + color: #333333; +} +.ui-widget-content a { + color: #333333; +} +.ui-widget-header { + border: 1px solid #e78f08; + background: #f6a828 url("images/ui-bg_gloss-wave_35_f6a828_500x100.png") 50% 50% repeat-x; + color: #ffffff; + font-weight: bold; +} +.ui-widget-header a { + color: #ffffff; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default { + border: 1px solid #cccccc; + background: #f6f6f6 url("images/ui-bg_glass_100_f6f6f6_1x400.png") 50% 50% repeat-x; + font-weight: bold; + color: #1c94c4; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited { + color: #1c94c4; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus { + border: 1px solid #fbcb09; + background: #fdf5ce url("images/ui-bg_glass_100_fdf5ce_1x400.png") 50% 50% repeat-x; + font-weight: bold; + color: #c77405; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited, +.ui-state-focus a, +.ui-state-focus a:hover, +.ui-state-focus a:link, +.ui-state-focus a:visited { + color: #c77405; + text-decoration: none; +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active { + border: 1px solid #fbd850; + background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; + font-weight: bold; + color: #eb8f00; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #eb8f00; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #fed22f; + background: #ffe45c url("images/ui-bg_highlight-soft_75_ffe45c_1x100.png") 50% top repeat-x; + color: #363636; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #363636; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #b81900 url("images/ui-bg_diagonals-thick_18_b81900_40x40.png") 50% 50% repeat; + color: #ffffff; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #ffffff; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #ffffff; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); /* support: IE8 */ + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); /* support: IE8 */ + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-widget-header .ui-icon { + background-image: url("images/ui-icons_ffffff_256x240.png"); +} +.ui-state-default .ui-icon { + background-image: url("images/ui-icons_ef8c08_256x240.png"); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon { + background-image: url("images/ui-icons_ef8c08_256x240.png"); +} +.ui-state-active .ui-icon { + background-image: url("images/ui-icons_ef8c08_256x240.png"); +} +.ui-state-highlight .ui-icon { + background-image: url("images/ui-icons_228ef1_256x240.png"); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url("images/ui-icons_ffd27a_256x240.png"); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 4px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #666666 url("images/ui-bg_diagonals-thick_20_666666_40x40.png") 50% 50% repeat; + opacity: .5; + filter: Alpha(Opacity=50); /* support: IE8 */ +} +.ui-widget-shadow { + margin: -5px 0 0 -5px; + padding: 5px; + background: #000000 url("images/ui-bg_flat_10_000000_40x100.png") 50% 50% repeat-x; + opacity: .2; + filter: Alpha(Opacity=20); /* support: IE8 */ + border-radius: 5px; +} diff --git a/surround360_camera_ctl/source/www/camera_capture/img/fb_small_white.png b/surround360_camera_ctl/source/www/camera_capture/img/fb_small_white.png new file mode 100755 index 00000000..594d353e Binary files /dev/null and b/surround360_camera_ctl/source/www/camera_capture/img/fb_small_white.png differ diff --git a/surround360_camera_ctl/source/www/camera_capture/img/loading.gif b/surround360_camera_ctl/source/www/camera_capture/img/loading.gif new file mode 100755 index 00000000..74a0f82e Binary files /dev/null and b/surround360_camera_ctl/source/www/camera_capture/img/loading.gif differ diff --git a/surround360_camera_ctl/source/www/camera_capture/img/spinner.gif b/surround360_camera_ctl/source/www/camera_capture/img/spinner.gif new file mode 100755 index 00000000..3c2f7c05 Binary files /dev/null and b/surround360_camera_ctl/source/www/camera_capture/img/spinner.gif differ diff --git a/surround360_camera_ctl/source/www/camera_capture/index.php b/surround360_camera_ctl/source/www/camera_capture/index.php new file mode 100755 index 00000000..0effdd30 --- /dev/null +++ b/surround360_camera_ctl/source/www/camera_capture/index.php @@ -0,0 +1,395 @@ + + + + + Surround 360 + + + + + + + + + + + +
+
+
+
+

+
+ + Surround 360 +
+

+
+
+
+
+
+
+

Record Video

+ + + + + + +
+ + + + + +
Label:
+ +
+
+
+
+
+
+
+

Preview

+
+

Choose 4 cameras:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 0  1  2  3 
 4  5  6  7 
 8  9  10  11 
 12  13  14  15 
 16 
+
+ +
+
+
+
+
+

Latest Video Stats

+
+
+
+ + + + diff --git a/surround360_camera_ctl/source/www/camera_capture/js/jquery-2.1.4.min.js b/surround360_camera_ctl/source/www/camera_capture/js/jquery-2.1.4.min.js new file mode 100755 index 00000000..49990d6e --- /dev/null +++ b/surround360_camera_ctl/source/www/camera_capture/js/jquery-2.1.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("