<!DOCTYPE html>
<html lang='en'>
  <head>
    <link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>
    <link rel='stylesheet' type='text/css' href='assets/css/no-ui-slider.css'>
    <link rel='stylesheet' type='text/css' href='assets/css/style.css'>
    <title>Visualizing Image Fields</title>
    <meta charset='utf-8'/>
    <meta name='author' content='Douglas Duhaime'>
  </head>
  <body>
    <!-- version: VERSION_NUMBER -->
    <script type='x-shader/x-vertex' id='vertex-shader'>
    /**
    * The vertex shader's main() function must define `gl_Position`,
    * which describes the position of each vertex in screen coordinates.
    *
    * To do so, we can use the following variables defined by Three.js:
    *   uniform mat4 projectionMatrix - maps camera space into screen space
    *   uniform mat4 modelViewMatrix - combines:
    *     model matrix: maps a point's local coordinate space into world space
    *     view matrix: maps world space into camera space
    *
    * `attributes` can vary from vertex to vertex and are defined as arrays
    *   with length equal to the number of vertices. Each index in the array
    *   is an attribute for the corresponding vertex. Each attribute must
    *   contain n_vertices * n_components, where n_components is the length
    *   of the given datatype (e.g. for a vec2, n_components = 2; for a float,
    *   n_components = 1)
    * `uniforms` are constant across all vertices
    * `varyings` are values passed from the vertex to the fragment shader
    *
    * For the full list of uniforms defined by three, see:
    *   https://threejs.org/docs/#api/renderers/webgl/WebGLProgram
    **/

    #version 100
    #define SHADER_NAME instancedVertex
    #define SELECTING

    // set float precision
    precision mediump float;

    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;
    uniform vec3 cameraPosition;
    uniform float scale;
    uniform float scaleTarget;
    uniform float transitionPercent;
    uniform float scaleTransitionPercent;
    uniform float borderWidth;
    uniform float delay;

    attribute vec3 pos0;          // x y z position offsets for an instance
    attribute vec3 pos1;          // x y z position to which we're transitioning
    attribute vec3 color;         // unique color for cell; used for raycasting
    attribute float width;        // px width of cell in lod texture
    attribute float height;       // px height of cell in lod texture
    attribute vec2 offset;        // px offset in tex from left, top
    attribute float opacity;      // opacity value for cell
    attribute float selected;     // 1 if the cell is selected else 0
    attribute float textureIndex; // index of an instance's texture among all textures

    varying vec3 vColor;          // cell color
    varying vec2 vOffset;         // px of cell offset left, top in texture
    varying float vWidth;         // px width of cell in lod texture
    varying float vHeight;        // px height of cell in lod texture
    varying float vOpacity;       // cell opacity
    varying float vSelected;      // 1 if this cell is selected else 0
    varying float vTextureIndex;  // cell texture idx (varyings can't be int)

    void main() {
      // pass varyings to fragment shader
      vTextureIndex = textureIndex;
      vColor = color;
      vWidth = width;
      vHeight = height;
      vOffset = offset;
      vOpacity = opacity;
      vSelected = selected;

      // determine how far this point has proceeded in its transition
      float delayX = (pos1.x + 1.0) / 2.0;
      float delayY = (pos0.y + 1.0) / 2.0;
      // pos0 has domains -1:1, so the delay sum above is <= 2.0
      float d = ((delayX + delayY) / 2.0) * delay;

      float percent = clamp(transitionPercent - d, 0.0, 1.0);

      // set point position as a mixture between the position + target
      vec3 pos = mix(pos0, pos1, smoothstep(0., 1., percent));
      vec4 world = modelViewMatrix * vec4(pos, 1.0);
      gl_Position = projectionMatrix * world;

      // use the unscaled point position and camera position to set point size
      float s = mix(scale, scaleTarget, percent);
      float pointSize = s / -world.z;

      if (selected > 0.5) {
        gl_PointSize = pointSize + (pointSize * borderWidth);
      } else {
        gl_PointSize = pointSize;
      }
    }
    </script>

    <script type='x-shader/x-fragment' id='fragment-shader'>
    /**
    * The fragment shader's main() function must define `gl_FragColor`,
    * which describes the pixel color of each pixel on the screen.
    *
    * To do so, we can use uniforms passed into the shader and varyings
    * passed from the vertex shader.
    *
    * Attempting to read a varying not generated by the vertex shader will
    * throw a warning but won't prevent shader compiling.
    **/

    #version 100
    #define SHADER_NAME instancedFragment
    #define SELECTING

    precision mediump float;

    uniform float useColor;
    uniform float cellAtlasPxPerSide;
    uniform float lodAtlasPxPerSide;
    uniform float cellPxHeight;
    uniform float lodPxHeight;
    uniform float borderWidth;
    uniform vec3 borderColor;

    varying vec3 vColor;         // cell's color (for gpu picking)
    varying vec2 vOffset;        // cell's offset in px from left, top of tex
    varying float vWidth;        // cell's width in lod tex in px
    varying float vHeight;       // cell's height in lod tex in px
    varying float vOpacity;      // cell's opacity
    varying float vSelected;     // cell's presence (>0.5) or absence (<0.5) from selection
    varying float vTextureIndex; // cell's texture index

    #ifndef SELECTING
      uniform sampler2D textures[N_TEXTURES]; // array of sampler2Ds
      uniform sampler2D lodTexture; // single sampler2D
    #endif

    void main() {
      // is this cell a LOD cell?
      bool isLod = vTextureIndex < -0.5;

      // get the width of the border on each side of the cell
      float borderW = borderWidth / 2.0;

      // find the width and height of this cell in its texture (in px)
      float w = isLod ? vWidth : vWidth/vHeight*cellPxHeight;
      float h = isLod ? vHeight : cellPxHeight;

      // find the length of the cell in its longest dimension (in px)
      float big = max(w, h);

      // set boolean indicating whether to center cells
      bool center = true;

      // find min, max vals of 0:1 scaled x axis to be textured
      float x0 = center ? (big-w)/2.0/big : 0.0;
      float x1 = center ? (w/big)+x0 : w / big;

      // find min, max vals of 0:1 scaled y axis to be textured
      float y0 = center ? (big-h)/2.0/big : 0.0;
      float y1 = center ? (h/big)+y0 : h / big;

      // clip the cell to center the texture on the x axis
      if (h > w && (gl_PointCoord.x < x0 || gl_PointCoord.x > x1)) discard;

      // if this cell's image is landscape clip bottom margin
      else if (w > h && gl_PointCoord.y < y0 || gl_PointCoord.y > y1) discard;

      // if this shader is using vColor attributes skip texture processing
      if (int(useColor) == 1) gl_FragColor = vec4(vColor, 1.0);

      // this cell should be textured
      else {

        // check if the current pointcoord position is within the border
        if (vSelected > 0.5 &&
           (gl_PointCoord.x < x0 + borderW ||
            gl_PointCoord.x > x1 - borderW ||
            gl_PointCoord.y < y0 + borderW ||
            gl_PointCoord.y > y1 - borderW)) {
          gl_FragColor = vec4(borderColor, 1.0);

        } else {

          // get the x and y positions to sample for this cell's texture
          float x = center ? gl_PointCoord.x - x0 : gl_PointCoord.x;
          float y = center ? gl_PointCoord.y - y0 : gl_PointCoord.y;

          // scale the x and y positions to the size of the texture
          vec2 uv = vOffset + vec2(x, y) * big;

          // set variables consumed by fragment tree below
          vec2 scaledUv = isLod ? uv/lodAtlasPxPerSide : uv/cellAtlasPxPerSide;
          int textureIndex = isLod ? -1 : int(vTextureIndex);

          // target to be replaced by texture tree
          TEXTURE_LOOKUP_TREE

          gl_FragColor = mix(gl_FragColor, vec4(0.0), 1.0-vOpacity);
          gl_FragColor.a = 1.0;

          if (vSelected > 0.5) gl_FragColor = mix(gl_FragColor, vec4(borderColor, 1.0), 0.15);
        }
      }
    }
    </script>

    <!-- Selection lasso -->
    <script type='x-shader/x-vertex' id='dashed-vertex-shader'>
    precision mediump float;

    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;
    uniform vec3 cameraPosition;

    attribute vec3 position;
    attribute float length;

    varying float vLength;

    void main() {
      vLength = length;
      vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
      gl_Position = projectionMatrix * mvPosition;
    }
    </script>

    <script type='x-shader/x-fragment' id='dashed-fragment-shader'>
    precision mediump float;

    uniform vec3 cameraPosition;
    uniform bool render;
    uniform float time;

    varying float vLength;

    void main() {
      if (!render) discard;
      float dashSize = cameraPosition.z / 100.0;
      float gapSize = dashSize * 0.4;
      if (mod(vLength + time, dashSize + gapSize) > dashSize) discard;
      gl_FragColor = vec4(0.9, 0.9, 0.9, 1.0);
    }
    </script>

    <!-- Text sprites -->
    <script type='x-shader/x-vertex' id='text-vertex-shader'>
    precision mediump float;

    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;
    uniform vec3 cameraPosition;
    uniform float scale;

    attribute vec2 offset;
    attribute vec3 position;

    varying vec2 vOffset;

    void main() {
      vec4 world = modelViewMatrix * vec4(position, 1.0);
      gl_Position = projectionMatrix * world;
      gl_PointSize = scale / -world.z;
      vOffset = offset;
    }
    </script>

    <script type='x-shader/x-fragment' id='text-fragment-shader'>
    precision mediump float;

    uniform sampler2D texture;
    uniform float render;
    uniform float point;
    uniform float canvas;

    varying vec2 vOffset;

    void main() {
      vec2 uv = vec2(vOffset.x/point + gl_PointCoord.x, vOffset.y/point + gl_PointCoord.y);
      uv *= point/canvas;
      gl_FragColor = texture2D(texture, uv);
      if (gl_FragColor.r < 0.1 || render < 0.5) discard;
    }
    </script>

    <!-- Worker -->
    <script type='script/worker' id='worker'>
    self.onmessage = function(event) {
      self.workerIndex = event.data.workerIndex;
      if (event.data.message == 'load') {
        self.url = event.data.url;
        self.get(self.url, self.onSuccess, self.onErr)
      }
    };

    self.onSuccess = function(data) {
      // window.caches is a global store of web worker data
      caches.open(self.url).then(function(cache) {
        return cache.add(data);
      })

      self.postMessage({
        status: 'complete',
        workerIndex: self.workerIndex,
        url: self.url,
      })
    }

    self.onErr = function() {
      self.postMessage({
        status: 'error',
        workerIndex: self.workerIndex,
      })
    }

    self.onProgress = function(e) {
      self.postMessage({
        status: 'progress',
        workerIndex: self.workerIndex,
        loadPercent: e.loaded/e.total,
      })
    }

    self.get = function(url, success, err) {
      var xmlhttp = new XMLHttpRequest();
      xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == XMLHttpRequest.DONE) {
          xmlhttp.status === 200
            ? success(xmlhttp.responseText)
            : err(xmlhttp);
        };
      };
      xmlhttp.onprogress = self.onProgress;
      xmlhttp.open('GET', url, true);
      xmlhttp.send();
    }
    </script>

    <!-- Header -->
    <header id='header' class='no-highlight'>
      <a href='https://dhlab.yale.edu' target='_blank'>
        <img id='logo' src='assets/images/dhlab-logo.svg' alt='DHLab logo' />
      </a>
      <div id='app-name'>PixPlot</div>
      <div class='tagline'>Image Fields in a Local Collection</div>
      <div id='header-controls'>
        <div id='image-search-container'>
          <input id='image-search' type='text' placeholder='Search for an image'></input>
          <img id='image-search-icon' src='assets/images/icons/search-icon.svg'>
          <div id='typeahead-container'></div>
        </div>
        <div id='range-slider'>
          <label>Point Size</label>
          <input id='pointsize-range-input' min='0' max='0.03' value='0' step='0.00001' type='range' />
        </div>
        <div id='icons'>
          <img id='layout-alphabetic' src='assets/images/icons/az-layout.svg' />
          <img id='layout-umap' src='assets/images/icons/scatter-layout.svg' />
          <img id='layout-grid' src='assets/images/icons/grid-layout.svg' />
          <img id='layout-date' src='assets/images/icons/date-layout.svg' />
          <img id='layout-categorical' src='assets/images/icons/categorical-layout.svg' />
        </div>
        <div id='filters'></div>
        <div id='date-slider'></div>
      </div>
    </header>
    <div id='tooltip'></div>
    <!-- Loader -->
    <div id='loader-scene'>
      <p class='welcome'>This page visualizes a large image collection within an interactive WebGL scene. Each image was processed with an <a href='https://arxiv.org/abs/1409.4842' target='_blank'>Inception</a> Convolutional Neural Network, trained on <a href='http://image-net.org/challenges/LSVRC/2012/' target='_blank'>ImageNet 2012</a>, and projected into a two-dimensional manifold with the <a href='https://github.com/lmcinnes/umap' target='_blank'>UMAP</a> algorithm such that similar images appear proximate to one another.</p>
      <div class='loader-container'>
        <div class='loader-icon'>
          <div class='blocks'>
            <div class='block'></div>
            <div class='block'></div>
            <div class='block'></div>
            <div class='block'></div>
            <div class='block'></div>
            <div class='block'></div>
            <div class='block'></div>
            <div class='block'></div>
            <div class='block'></div>
          </div>
        </div>
        <div id='loader-text'>
          <span id='progress'>0%</span>
        </div>
      </div>
      <button id='enter-button'>Enter</button>
    </div>
    <!-- Canvas -->
    <div id='canvas-container'>
      <div id='jitter-container' class='no-highlight'>
        <input type='checkbox' id='jitter-input'>
        <label>Jitter</label>
      </div>
      <div id='canvas-target'>
        <canvas id='pixplot-canvas' />
      </div>
    </div>
    <!-- Hotspot Navigation -->
    <nav>
      <div id='nav-inner' class='no-highlight'>
        <h2>Hotspots</h2>
        <div id='hotspots'></div>
        <script type='text/html' id='hotspot-template'>
          <% _.forEach(hotspots, function(hotspot, idx) { %>
            <div class='hotspot' id='hotspot-<%= idx %>'
                draggable='<%= isLocalhost %>'>
              <% if (isLocalhost) { %>
                <div class='hotspot-action remove-hotspot-x'>✕</div>
                <div class='hotspot-action drag-hotspot'>⠿</div>
                <img class='hotspot-action refresh-hotspot' src='assets/images/icons/refresh.svg'>
              <% } %>
              <img class='hotspot-image' src='data/thumbs/<%= hotspot.img %>'>
              <div class='hotspot-bar-container'>
                <div class='hotspot-bar-inner'></div>
              </div>
              <% if (isLocalhost) { %>
                <div class='hotspot-label' contenteditable='true' ><%= hotspot.label %></div>
              <% } else { %>
                <div class='hotspot-label'><%= hotspot.label %></div>
              <% } %>
            </div>
          <% }); %>
        </script>
      </div>
    </nav>
    <div id='hotspot-actions'>
      <div id='save-hotspots' class='button hotspot-button'>Save Hotspots</div>
      <div id='create-hotspot' class='no-highlight button hotspot-button'>Create Hotspot</div>
    </div>
    <!-- Image Picker: single image selection -->
    <script type='text/html' id='selected-image-template'>
      <div class='modal-content'>
        <div class='modal-x'>✕</div>
        <div class='modal-top'>
          <div class='modal-top-content' id='selected-image-parent'>
            <div class='icons'>
              <a target='_blank' id='eye-icon' href='<%- meta.permalink || "#" %>'>
                <img src='assets/images/icons/eye-icon.png' alt='eye icon' />
              </a>
              <a id='download-icon' href='#'>
                <img src='assets/images/icons/download-icon.png' alt='download icon' />
              </a>
            </div>
            <% if (multiImage) { %>
              <img id='caret-left' class='image-caret' src='assets/images/icons/chevron.png' onclick='modal.showPreviousCell()'>
              <img id='caret-right' class='image-caret' src='assets/images/icons/chevron.png' onclick='modal.showNextCell()'>
            <% } %>
          </div>
        </div>
        <div class='modal-bottom' id='selected-image-meta'>
          <div class='modal-bottom-content'>
            <div class='modal-text-left'>
              <div id='image-title'><%- meta.filename ? decodeURIComponent(meta.filename) : decodeURIComponent(meta.src) %></div>
              <span id='image-text'><%- meta.description ? decodeURIComponent(meta.description) : '' %></span>
              <span id='image-date'><%- meta.date ? '(' + meta.date + ')' : '' %></span>
            </div>
            <div class='modal-text-right'>
              <div id='meta-category-container'>
                <div class='meta-category'><%- meta.category %></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </script>
    <div id='selected-image-target' class='no-highlight'></div>
    <!-- Image Selection: multi-image selection -->
    <div id='selection-icons' class='no-highlight'>
      <img id='pan' src='assets/images/icons/pan.svg' class='active' />
      <img id='select' src='assets/images/icons/lasso.png' class='' />
      <div id='select-tooltip'>
        <div id='select-tooltip-heading'>Selection Mode</div>
        <p>Lasso to select. Shift + drag to edit selection.</p>
        <div id='select-tooltip-button'>Got it</div>
      </div>
    </div>
    <div id='view-selected-container'>
      <div id='selected-images-count' class='no-highlight'>
        <div id='selected-images-x'>✕</div>
        <div id='count-target'>0</div>
        <div>images selected</div>
      </div>
      <div id='view-selected' class='no-highlight button'>View Selected</div>
    </div>
    <div id='selected-images-modal'>
      <div class='modal-content'>
        <div class='modal-top'>
          <script type='text/html' id='selected-images-template'>
            <div class='modal-top-content'>
              <h2>Selected Images</h2>
              <p>You have selected <b id='n-images-selected'><%- images.length %></b> images</p>
              <div id='selected-images-grid'>
                <% _.forEach(images, function(image, idx) { %>
                  <div class='selected-image'>
                    <div class='toggle-selection'>✕</div>
                    <div data-index='<%- idx %>' data-image='<%- image %>' class='background-image' style='background-image: url("data/thumbs/<%- image %>");'></div>
                  </div>
                <% }); %>
              </div>
            </div>
          </script>
          <div id='selected-images-target'></div>
        </div>
        <div class='modal-bottom'>
          <div class='modal-bottom-content'>
            <div class='modal-text-left'>
              <b>Help Guide</b>
              <p>Click an image above for higher resolution, or click the button to the right to download a JSON file identifying selected images.</p>
            </div>
            <div class='modal-text-right'>
              <input id='download-filename' type='text' placeholder='filename'>
              <a href='#' id='download-link'>
                <button class='active'>Download Metadata</button>
              </a>
              <div id='download-as'>
                <span>Download as:</span>
                <button class='filetype active' id='csv'>CSV</button>
                <button class='filetype' id='json'>JSON</button>
                <button class='filetype' id='zip'>ZIP</button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- Webgl / Noscript Warnings -->
    <div id='webgl-not-available'>
      <div class='browser-message'>Sorry, your browser is not able to load a WebGL scene currently. Please check your browser settings and try again.</div>
    </div>
    <noscript>
      <span class='browser-message'>Sorry, your browser is not able to load a WebGL scene currently. Please enable JavaScript and try again.</span>
    </noscript>
    <script type='text/javascript' src='assets/vendor/dist/three.min.js'></script>
    <script type='text/javascript' src='assets/vendor/dist/lodash.min.js'></script>
    <script type='text/javascript' src='assets/js/object-assign-polyfill.js'></script>
    <script type='text/javascript' src='assets/vendor/dist/tweenlite.min.js'></script>
    <script type='text/javascript' src='assets/vendor/dist/trackball-controls.min.js'></script>
    <script type='text/javascript' src='assets/vendor/dist/stats.min.js'></script>
    <script type='text/javascript' src='assets/vendor/dist/gunzip.min.js'></script>
    <script type='text/javascript' src='assets/vendor/dist/papaparse.min.js'></script>
    <script type='text/javascript' src='assets/vendor/dist/no-ui-slider.min.js'></script>
    <script type='text/javascript' src='assets/vendor/dist/jszip.min.js'></script>
    <script type='text/javascript' src='assets/js/tsne.js'></script>
    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
      ga('create', 'UA-101732875-1', 'auto');
      ga('send', 'pageview');
    </script>
  </body>
</html>