view Applications/StoneWebViewer/WebApplication/index.html @ 2174:2410a171ebfb

refactoring using DicomWebDataset and OrthancNativeDataset
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 22 Oct 2024 21:52:34 +0200
parents 952674a812c2
children
line wrap: on
line source

<!doctype html>
<html class="wv-html">
  <head>
    <title>Stone Web Viewer</title>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
    
    <link rel="stylesheet" href="css/all.css">  <!-- Font Awesome -->
    <link rel="stylesheet" href="css/bootstrap.css">
    <link rel="stylesheet" href="open-sans.css">
    <link rel="stylesheet" href="app.css">
    <link rel="stylesheet" href="app-fixes.css">
  </head>
  
  <body class="wv-body">
    <div id="wv">

      <div class="wvInfoScreen" v-show="modalNotDiagnostic" style="display: none">
        <div class="wvInfoPopup">
          <div class="wvInfoPopupLogo">
            <a href="https://www.orthanc-server.com/" target="_blank">
              <img style="width: 340px;" src="img/orthanc.png"/>
            </a>
          </div>
          <div class="wvInfoPopupText">
            <h3>Intended use</h3>
            <p>
              The Stone Web Viewer is intended for<br> patients
              reviewing their images,<br> for research and for quality
              assurance.
            </p>
            <h3>Versions</h3>
            <p>
              Stone Web viewer: {{ stoneWebViewerVersion }} <br/>
              <span v-if="orthancSystem.Version">Orthanc: {{ orthancSystem.Version }} <br/></span>
              Emscripten compiler: {{ emscriptenVersion }}
            </p>
          </div>
          <div class="wvInfoPopupForm">
            <div v-if="globalConfiguration.ShowInfoPanelAtStartup == 'User'">
              <br>
              <label>Show this information at startup
                <input type="checkbox" style="margin-left: 20px" v-model="settingNotDiagnostic">
              </label>
              <br>
            </div>
            <br>
            <div style="text-align: center;">
              <button class="wvInfoPopupCloseButton" @click="modalNotDiagnostic = false">
                Close
              </button>
            </div>
          </div>
        </div>
      </div>
      
      <div class="wvInfoScreen" v-show="modalPreferences" style="display: none">
        <div class="wvInfoPopup">
          <div class="wvInfoPopupLogo">
            <a href="https://www.orthanc-server.com/" target="_blank">
              <img style="width: 340px;" src="img/orthanc.png"/>
            </a>
          </div>
          <div class="wvInfoPopupText">
            <h3>Intended use</h3>
            <p>
              The Stone Web Viewer is intended for<br> patients
              reviewing their images,<br> for research and for quality
              assurance.
            </p>
            <h3>Versions</h3>
            <p>
              Stone Web viewer: {{ stoneWebViewerVersion }} <br/>
              <span v-if="orthancSystem.Version">Orthanc: {{ orthancSystem.Version }} <br/></span>
              Emscripten compiler: {{ emscriptenVersion }}
            </p>
            <h3>User preferences</h3>
          </div>
          <div class="wvInfoPopupForm">
            <div v-if="globalConfiguration.ShowInfoPanelAtStartup == 'User'">
              <label>Warn about the intended use at startup
                <input type="checkbox" style="margin-left: 20px" v-model="settingNotDiagnostic">
              </label>
              <br>
            </div>
            <label>Enable linear interpolation
              <input type="checkbox" style="margin-left: 20px" v-model="settingLinearInterpolation">
            </label>
            <br>
            <label>Use software rendering (slower, will reload the viewer)
              <input type="checkbox" style="margin-left: 20px" v-model="settingSoftwareRendering">
            </label>
            <br><br>
            <div style="text-align: center;">
              <button class="wvInfoPopupCloseButton" @click="ApplyPreferences()">
                Apply
              </button>
            </div>
          </div>
        </div>
      </div>
      
      <div class="wvLoadingScreen" v-show="!ready && !modalNotDiagnostic && !modalPreferences" style="display: none">
        <span class="wvLoadingSpinner">
          <div class="bounce1"></div>
          <div class="bounce2"></div>
          <div class="bounce3"></div>
        </span>
      </div>

      <div class="fluid-height fluid-width" v-show="ready && !modalNotDiagnostic && !modalPreferences"
           style="display: none">

        <div class="wvWarning wvPrintExclude" v-show="modalWarning">
          <div class="wvWarning-content clearfix">
            <span class="wvWarning-text">
              <h2 class="mb10"><i class="fa fa-exclamation-triangle wvWarning-icon mr5"></i>Warning!</h2>
              <p class="mn mb10" style="color:#000">
                You browser is not supported. You might expect
                inconsistent behaviours and must not use the viewer to
                produce a diagnostic.
              </p>
            </span> 
          </div>
          <div class="text-right mb10 mr10">
            <button class="btn btn-primary" @click="modalWarning=false">OK</button>
          </div>
        </div>


        <div class="wvLayoutLeft wvLayoutLeft--closed" v-show="!leftVisible">
          <div class="wvLayoutLeft__actions--outside" style="z-index:10">
            <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center"
                    @click="leftVisible = true">
              <i class="fa fa-angle-double-right"></i>
            </button>
          </div>
        </div>


        <div class="wvLayoutLeft wvPrintExclude" v-show="leftVisible"
             v-bind:class="{ 'wvLayoutLeft--small': leftMode == 'small' }" 
             >
          <div class="wvLayoutLeft__actions" style="z-index:10">
            <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center"
                    @click="leftVisible = false">
              <i class="fa fa-angle-double-left"></i>
            </button>
          </div>
          <div class="wvLayoutLeft__content">
            <div class="wvLayoutLeft__contentTop">
              <div class="float__left dropdown" style="max-width: calc(100% - 4.5rem); height:4.5rem !important" v-show="leftMode != 'small'">
                <button type="button" class="wvButton--border" data-toggle="dropdown">
                  {{ getSelectedStudies }}
                  <span class="caret"></span>
                </button>
                <ul class="dropdown-menu checkbox-menu allow-focus">
                  <li v-for="study in studies"
                      v-bind:class="{ active: study.selected }" 
                      @click="study.selected = !study.selected">
                    <a>
                      {{ study.tags[STUDY_DESCRIPTION] }}
                      <small v-if="study.tags[STUDY_DATE].length > 0">
                        [{{ FormatDate(study.tags[STUDY_DATE]) }}]
                      </small>
                      <span v-if="study.selected">&nbsp;<i class="fa fa-check"></i></span>
                    </a> 
                  </li>
                </ul>
              </div>

              <div class="float__right wvButton" v-if="leftMode == 'grid'" @click="leftMode = 'full'">
                <i class="fa fa-th-list"></i>
              </div>
              <div class="float__right wvButton" v-if="leftMode == 'full'" @click="leftMode = 'small'">
                <i class="fa fa-ellipsis-v"></i>
              </div>
              <div class="float__right wvButton" v-if="leftMode == 'small'" @click="leftMode = 'grid'">
                <i class="fa fa-th"></i>
              </div>

              <p v-if="globalConfiguration.ShowNotForDiagnosticUsageDisclaimer" class="clear disclaimer mbn">
                For patients, researchers and quality assurance. Not for diagnostic usage.
              </p>
            </div>        
            <div class="wvLayoutLeft__contentMiddle">

              <div v-for="study in studies">
                <div v-if="study.selected">
                  <div v-bind:class="'wvStudyIsland--' + study.color">
                    <div v-bind:class="'wvStudyIsland__header--' + study.color">
                      <!-- Actions -->
                      <div class="wvStudyIsland__actions"
                           v-bind:class="{ 'wvStudyIsland__actions--oneCol': leftMode == 'small' }">
                        <a class="wvButton"
                           v-show="globalConfiguration.DownloadStudyEnabled && 'OrthancApiRoot' in globalConfiguration">
                          <!-- download --> 
                          <i class="fa fa-download" v-show="!creatingArchive"
                             data-toggle="tooltip" data-title="Download the study"
                             @click="DownloadStudy(study.tags[STUDY_INSTANCE_UID], $event)"></i>
                          <i class="fas fa-sync fa-spin" v-show="creatingArchive"
                             data-toggle="tooltip" data-title="A ZIP archive is being created by Orthanc..."></i>
                        </a>
                      </div>
                      
                      <!-- Title -->
                      {{ study.tags[STUDY_DESCRIPTION] }}
                      <br/>
                      <small>{{ FormatDate(study.tags[STUDY_DATE]) }}</small>
                    </div>

                    <div class="wvStudyIsland__main">
                      <ul class="wvSerieslist">

                        <!-- Series without multiple multiframe instances -->
                        <span v-for="seriesIndex in study.series">
                          <li class="wvSerieslist__seriesItem"
                              v-bind:class="{ highlighted : GetActiveSeriesInstanceUid().includes(series[seriesIndex].tags[SERIES_INSTANCE_UID]), 'wvSerieslist__seriesItem--list' : leftMode != 'grid', 'wvSerieslist__seriesItem--grid' : leftMode == 'grid' }"
                              v-on:dragstart="SeriesDragStart($event, seriesIndex)"
                              v-on:click="ClickSeries(seriesIndex)"
                              v-if="series[seriesIndex].virtualSeries === null">
                            <div class="wvSerieslist__picture" style="z-index:0"
                                 draggable="true"
                                 v-if="series[seriesIndex].type != stone.ThumbnailType.UNKNOWN"
                                 >
                              <div v-if="series[seriesIndex].type == stone.ThumbnailType.LOADING">
                                <img src="img/loading.gif"
                                     style="vertical-align:baseline"
                                     width="65px" height="65px"
                                     />
                              </div>

                              <i v-if="series[seriesIndex].type == stone.ThumbnailType.PDF"
                                 class="wvSerieslist__placeholderIcon fa fa-file-pdf"
                                 v-bind:title="leftMode == 'full' ? null : series[seriesIndex].tags[SERIES_DESCRIPTION]"></i>

                              <i v-if="series[seriesIndex].type == stone.ThumbnailType.VIDEO"
                                 class="wvSerieslist__placeholderIcon fa fa-video"
                                 v-bind:title="leftMode == 'full' ? null : series[seriesIndex].tags[SERIES_DESCRIPTION]"></i>
                              
                              <div v-if="[stone.ThumbnailType.IMAGE, stone.ThumbnailType.NO_PREVIEW].includes(series[seriesIndex].type)"
                                   class="wvSerieslist__placeholderIcon"
                                   v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags[MODALITY] + '] ' + series[seriesIndex].tags[SERIES_DESCRIPTION]">
                                <i v-if="series[seriesIndex].type == stone.ThumbnailType.NO_PREVIEW"
                                   class="fa fa-eye-slash"></i>

                                <img v-if="series[seriesIndex].type == stone.ThumbnailType.IMAGE"
                                     v-bind:src="series[seriesIndex].thumbnail"
                                     style="vertical-align:baseline"
                                     width="65px" height="65px"
                                     v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags[MODALITY] + '] ' + series[seriesIndex].tags[SERIES_DESCRIPTION]"
                                     />
                                
                                <div v-bind:class="'wvSerieslist__badge--' + study.color"
                                     v-if="series[seriesIndex].numberOfFrames != 0 || series[seriesIndex].tags[SERIES_NUMBER] !== undefined">
                                     <span v-if="series[seriesIndex].tags[SERIES_NUMBER] !== undefined">#{{ series[seriesIndex].tags[SERIES_NUMBER] }}</span>
                                     <span v-if="series[seriesIndex].numberOfFrames != 0 && series[seriesIndex].tags[SERIES_NUMBER] !== undefined"> - </span>
                                     <span v-if="series[seriesIndex].numberOfFrames != 0">{{ series[seriesIndex].numberOfFrames }}</span>
                                </div>
                              </div>
                            </div>

                            <div v-if="leftMode == 'full'" class="wvSerieslist__information"
                                 draggable="true"
                                 v-on:dragstart="SeriesDragStart($event, seriesIndex)"
                                 v-on:click="ClickSeries(seriesIndex)">
                              <p class="wvSerieslist__label">
                                [{{ series[seriesIndex].tags[MODALITY] }}]
                                {{ series[seriesIndex].tags[SERIES_DESCRIPTION] }}
                              </p>
                            </div>
                          </li>


                          <!-- Series with multiple multiframe instances (CINE) -->
                          <li class="wvSerieslist__seriesItem"
                              v-bind:class="{ highlighted : GetActiveVirtualSeries().includes(virtualSeries.ID), 'wvSerieslist__seriesItem--list' : leftMode != 'grid', 'wvSerieslist__seriesItem--grid' : leftMode == 'grid' }"
                              v-for="virtualSeries in series[seriesIndex].virtualSeries"
                              v-on:dragstart="VirtualSeriesDragStart($event, seriesIndex, virtualSeries.ID)"
                              v-on:click="ClickVirtualSeries(seriesIndex, virtualSeries.ID)">
                            <div class="wvSerieslist__picture" style="z-index:0"
                                 draggable="true">
                              <img v-if="series[seriesIndex].type == stone.ThumbnailType.IMAGE"
                                   v-bind:src="virtualSeries.ID in virtualSeriesThumbnails ? virtualSeriesThumbnails[virtualSeries.ID] : series[seriesIndex].thumbnail"
                                   style="vertical-align:baseline"
                                   width="65px" height="65px"
                                   v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags[MODALITY] + '] ' + series[seriesIndex].tags[SERIES_DESCRIPTION]"
                                   />
                              
                              <div v-bind:class="'wvSerieslist__badge--' + study.color">
                                {{ virtualSeries.NumberOfFrames }}
                              </div>
                            </div>

                            <div v-if="leftMode == 'full'" class="wvSerieslist__information"
                                 draggable="true"
                                 v-on:dragstart="VirtualSeriesDragStart($event, seriesIndex, virtualSeries.ID)"
                                 v-on:click="ClickVirtualSeries(seriesIndex, virtualSeries.ID)">
                              <p class="wvSerieslist__label">
                                [{{ series[seriesIndex].tags[MODALITY] }}]
                                {{ series[seriesIndex].tags[SERIES_DESCRIPTION] }}
                              </p>
                            </div>
                          </li>
                          
                        </span>
                      </ul>
                    </div>
                  </div>
                </div>
              </div>

            </div>        
            <div class="wvLayoutLeft__contentBottom">
              <div style="width:100%;padding:10px;text-align:center;"
                   v-if="globalConfiguration.InstitutionLogo != ''">
                <img style="max-width:100%" v-bind:src="globalConfiguration.InstitutionLogo" />
              </div>
            </div>        
          </div>
        </div>
        <div class="wvLayout__main"
             v-bind:class="{ 'wvLayout__main--smallleftpadding': leftVisible && leftMode == 'small', 'wvLayout__main--leftpadding': leftVisible && leftMode != 'small' }" 
             >

          <div class="wvToolbar wvToolbar--top wvPrintExclude" style="left: 0px; text-align:left">
            <a href="https://www.orthanc-server.com/" target="_blank">
              <img src="img/orthanc.png" style="max-height: 100%; padding: 8px" />
            </a>
          </div>
            
          <div class="wvToolbar wvToolbar--top wvPrintExclude">
            <div class="ng-scope inline-object">
              <div class="tbGroup">
                <div class="tbGroup__toggl">
                  <button class="wvButton"
                          v-bind:class="{ 'wvButton--underline' : !viewportLayoutButtonsVisible, 'wvButton--border' : viewportLayoutButtonsVisible }"
                          data-toggle="tooltip" data-title="Change layout"
                          @click="viewportLayoutButtonsVisible = !viewportLayoutButtonsVisible;mouseActionsVisible=false;annotationActionsVisible=false;HideAllTooltips()">
                    <i class="fa fa-th"></i>
                  </button>
                </div>
                
                <div class="tbGroup__buttons--bottom" v-show="viewportLayoutButtonsVisible">
                  <div class="inline-object">
                    <button class="wvButton" @click="SetViewportLayout('1x1')">
                      <img src="img/grid1x1.png" style="width:1em;height:1em" />
                    </button>
                  </div>
                  <div class="inline-object">
                    <button class="wvButton" @click="SetViewportLayout('2x1')">
                      <img src="img/grid2x1.png" style="width:1em;height:1em" />
                    </button>
                  </div>
                  <div class="inline-object">
                    <button class="wvButton" @click="SetViewportLayout('1x2')">
                      <img src="img/grid1x2.png" style="width:1em;height:1em" />
                    </button>
                  </div>
                  <div class="inline-object">
                    <button class="wvButton" @click="SetViewportLayout('2x2')">
                      <img src="img/grid2x2.png" style="width:1em;height:1em" />
                    </button>
                  </div>
                </div>
              </div>
            </div>


            <div class="ng-scope inline-object">
              <div class="tbGroup">
                <div class="tbGroup__toggl">
                  <button class="wvButton"
                          v-bind:class="{ 'wvButton--underline' : !mouseActionsVisible, 'wvButton--border' : mouseActionsVisible }"
                          data-toggle="tooltip" data-title="Mouse actions"
                          @click="viewportLayoutButtonsVisible=false;mouseActionsVisible = !mouseActionsVisible;annotationActionsVisible=false;HideAllTooltips()">
                    <i class="fa fa-mouse-pointer"></i>
                  </button>
                </div>
                
                <div class="tbGroup__buttons--bottom" v-show="mouseActionsVisible">
                  <div class="inline-object" v-if="globalConfiguration.CombinedToolEnabled">
                    <button class="wvButton--underline"
                            data-toggle="tooltip" data-title="Combined tool"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_COMBINED }"
                            @click="SetCombinedToolActions()">
                      <i class="far fa-hand-point-up"></i>
                    </button>
                  </div>
                  <div class="inline-object">
                    <button class="wvButton--underline"
                            data-toggle="tooltip" data-title="Zoom"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_ZOOM }"
                            @click="SetMouseButtonActions(MOUSE_TOOL_ZOOM, stone.WebViewerAction.ZOOM, stone.WebViewerAction.ZOOM, stone.WebViewerAction.ZOOM)">
                      <i class="fas fa-search"></i>
                    </button>
                  </div>
                  <div class="inline-object">
                    <button class="wvButton--underline"
                            data-toggle="tooltip" data-title="Pan"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_PAN }"
                            @click="SetMouseButtonActions(MOUSE_TOOL_PAN, stone.WebViewerAction.PAN, stone.WebViewerAction.PAN, stone.WebViewerAction.PAN)">
                      <i class="fas fa-arrows-alt"></i>
                    </button>
                  </div>
                  <div class="inline-object">
                    <button class="wvButton--underline"
                            data-toggle="tooltip" data-title="3D cross-hair"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_CROSSHAIR }"
                            @click="SetMouseButtonActions(MOUSE_TOOL_CROSSHAIR, stone.WebViewerAction.CROSSHAIR, stone.WebViewerAction.PAN, stone.WebViewerAction.ZOOM)">
                      <i class="fas fa-crosshairs"></i>
                    </button>
                  </div>
                </div>
              </div>
            </div>



            <!--div class="ng-scope inline-object">
              <button class="wvButton--underline text-center active">
                <i class="fa fa-hand-pointer-o"></i>
              </button>
            </div>

            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center">
                <i class="fa fa-search"></i>
              </button>
            </div>

            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center">
                <i class="fa fa-arrows"></i>
              </button>
            </div-->

            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Invert contrast"
                      v-on:click="InvertContrast()">
                <i class="fa fa-adjust"></i>
              </button>
            </div>
            
            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Change windowing"
                      id="windowing-popover"
                      v-bind:class="{ 'active' : showWindowing }"
                      v-on:click="ToggleWindowing()">
                <i class="fa fa-sun"></i>
              </button>
            </div>

            <div id="windowing-content" v-show="showWindowing"
                 class="popover wvToolbar__windowingPresetConfigPopover"
                 style="position: absolute; display: block"
                 >
              <div class="arrow"></div>
              <h3 class="popover-title">Change windowing</h3>
              <div class="popover-content">
                
                <!--p class="wvToolbar__windowingPresetConfigNotice">
                    Click on the button to toggle the windowing tool or apply a preset to the selected viewport.
                    </p-->

                <ul class="wvToolbar__windowingPresetList">
                  <li v-for="preset in windowingPresets"
                      class="wvToolbar__windowingPresetListItem">
                    <a href="#" v-on:click="SetWindowing(preset.center, preset.width)">
                      {{ preset.name }} <small>({{ preset.info }})</small>
                    </a>
                  </li>
                  <li class="wvToolbar__windowingPresetListItem">
                    <a href="#" v-on:click="StretchWindowing()">
                      Stretch to whole range
                    </a>
                  </li>
                  <li v-for="preset in globalConfiguration.WindowingPresets"
                      class="wvToolbar__windowingPresetListItem">
                    <a href="#" v-on:click="SetWindowing(preset.WindowCenter, preset.WindowWidth)">
                      {{ preset.Name }}
                      <small>
                        (C {{preset.WindowCenter}}, W {{preset.WindowWidth}})
                      </small>
                    </a>
                  </li>
                </ul>
              </div>
            </div>

            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Magnifying glass"
                      v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_MAGNIFYING_GLASS }"
                      v-on:click="SetMouseButtonActions(MOUSE_TOOL_MAGNIFYING_GLASS, stone.WebViewerAction.MAGNIFYING_GLASS, stone.WebViewerAction.PAN, stone.WebViewerAction.ZOOM)">
                <i class="fas fa-search-plus"></i>
              </button>
            </div>

            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Rotate to the left"
                      v-on:click="RotateLeft()">
                <i class="fas fa-undo"></i>
              </button>
            </div>

            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Rotate to the right"
                      v-on:click="RotateRight()">
                <i class="fas fa-undo fa-flip-horizontal"></i>
              </button>
            </div>
            
            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Flip horizontally"
                      v-on:click="FlipX()">
                <i class="fas fa-exchange-alt"></i>
              </button>
            </div>

            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Flip vertically"
                      v-on:click="FlipY()">
                <i class="fas fa-exchange-alt fa-rotate-90"></i>
              </button>
            </div>
            

            <div class="ng-scope inline-object">
              <div class="tbGroup">
                <div class="tbGroup__toggl">
                  <button class="wvButton"
                          v-bind:class="{ 'wvButton--underline' : !annotationActionsVisible, 'wvButton--border' : annotationActionsVisible }"
                          data-toggle="tooltip" data-title="Annotations"
                          @click="viewportLayoutButtonsVisible=false;mouseActionsVisible=false;annotationActionsVisible = !annotationActionsVisible;HideAllTooltips()">
                    <i class="fas fa-pencil-ruler"></i>
                  </button>
                </div>
                
                <div class="tbGroup__buttons--bottom" v-show="annotationActionsVisible">
                  <div class="ng-scope inline-object">
                    <button class="wvButton--underline text-center"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_CREATE_LENGTH }"
                            v-on:click="SetLeftMouseButtonAction(MOUSE_TOOL_CREATE_LENGTH, stone.WebViewerAction.CREATE_LENGTH)"
                            data-toggle="tooltip" data-title="Measure length">
                      <i class="fas fa-ruler"></i>
                    </button>
                  </div>

                  <div class="ng-scope inline-object">
                    <button class="wvButton--underline text-center"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_CREATE_ANGLE }"
                            v-on:click="SetLeftMouseButtonAction(MOUSE_TOOL_CREATE_ANGLE, stone.WebViewerAction.CREATE_ANGLE)"
                            data-toggle="tooltip" data-title="Measure angle">
                      <i class="fas fa-drafting-compass"></i>
                    </button>
                  </div>

                  <div class="ng-scope inline-object">
                    <button class="wvButton--underline text-center"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_CREATE_CIRCLE }"
                            v-on:click="SetLeftMouseButtonAction(MOUSE_TOOL_CREATE_CIRCLE, stone.WebViewerAction.CREATE_CIRCLE)"
                            data-toggle="tooltip" data-title="Measure circle">
                      <i class="far fa-circle"></i>
                    </button>
                  </div>

                  <div class="ng-scope inline-object">
                    <button class="wvButton--underline text-center"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_CREATE_PIXEL_PROBE }"
                            v-on:click="SetLeftMouseButtonAction(MOUSE_TOOL_CREATE_PIXEL_PROBE, stone.WebViewerAction.CREATE_PIXEL_PROBE)"
                            data-toggle="tooltip" data-title="Pixel probe">
                      <i class="fas fa-microscope"></i>
                    </button>
                  </div>

                  <div class="ng-scope inline-object">
                    <button class="wvButton--underline text-center"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_CREATE_RECTANGLE_PROBE }"
                            v-on:click="SetLeftMouseButtonAction(MOUSE_TOOL_CREATE_RECTANGLE_PROBE, stone.WebViewerAction.CREATE_RECTANGLE_PROBE)"
                            data-toggle="tooltip" data-title="Rectangle probe">
                      <i class="fas fa-plus-square"></i>
                    </button>
                  </div>

                  <div class="ng-scope inline-object">
                    <button class="wvButton--underline text-center"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_CREATE_ELLIPSE_PROBE }"
                            v-on:click="SetLeftMouseButtonAction(MOUSE_TOOL_CREATE_ELLIPSE_PROBE, stone.WebViewerAction.CREATE_ELLIPSE_PROBE)"
                            data-toggle="tooltip" data-title="Ellipse probe">
                      <i class="fas fa-plus-circle"></i>
                    </button>
                  </div>

                  <div class="ng-scope inline-object">
                    <button class="wvButton--underline text-center"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_CREATE_TEXT_ANNOTATION }"
                            v-on:click="SetLeftMouseButtonAction(MOUSE_TOOL_CREATE_TEXT_ANNOTATION, stone.WebViewerAction.CREATE_TEXT_ANNOTATION)"
                            data-toggle="tooltip" data-title="Add text annotation">
                      <i class="fas fa-comment-dots"></i>
                    </button>
                  </div>

                  <div class="ng-scope inline-object">
                    <button class="wvButton--underline text-center"
                            v-bind:class="{ 'active' : mouseTool == MOUSE_TOOL_REMOVE_MEASURE }"
                            v-on:click="SetLeftMouseButtonAction(MOUSE_TOOL_REMOVE_MEASURE, stone.WebViewerAction.REMOVE_MEASURE)"
                            data-toggle="tooltip" data-title="Delete annotation">
                      <i class="fas fa-trash"></i>
                    </button>
                  </div>
                </div>
              </div>
            </div>


            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Synchronized browsing"
                      v-bind:class="{ 'active' : synchronizedBrowsing }"
                      v-on:click="synchronizedBrowsing = !synchronizedBrowsing">
                <i class="fa fa-link"></i>
              </button>
            </div>

            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Reference lines"
                      v-bind:class="{ 'active' : showReferenceLines }"
                      v-on:click="showReferenceLines = !showReferenceLines">
                <i class="fa fa-bars"></i>
              </button>
            </div>

            <div class="ng-scope inline-object">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Show image information"
                      v-bind:class="{ 'active' : showInfo }"
                      v-on:click="showInfo = !showInfo">
                <i class="fa fa-info-circle"></i>
              </button>
            </div>


            <div class="ng-scope inline-object" v-if="globalConfiguration.PrintEnabled">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Print"
                      onclick="window.print()">
                <i class="fas fa-print"></i>
              </button>
            </div>

            <div class="ng-scope inline-object" v-if="globalConfiguration.DownloadAsJpegEnabled">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="Download as JPEG"
                      v-on:click="DownloadJpeg()">
                <i class="fas fa-file-download"></i>
              </button>
            </div>

            <div class="ng-scope inline-object" v-if="globalConfiguration.ShowUserPreferencesButton">
              <button class="wvButton--underline text-center"
                      data-toggle="tooltip" data-title="User preferences"
                      v-on:click="modalPreferences = true">
                <i class="fa fa-user"></i>
              </button>
            </div>
          </div>

          
          <div class="wvLayout__splitpane--toolbarAtTop wvPrintFullPage">
            <div id="viewport" class="wvSplitpane">
              <viewport v-on:updated-series="SetViewportSeries(1, $event)"
                        v-on:selected-viewport="activeViewport=1"
                        v-show="viewport1Visible"
                        canvas-id="canvas1"
                        v-bind:content="viewport1Content"
                        v-bind:left="viewport1Left"
                        v-bind:top="viewport1Top"
                        v-bind:width="viewport1Width"
                        v-bind:height="viewport1Height"
                        v-bind:show-info="showInfo"
                        v-bind:global-configuration="globalConfiguration"
                        v-bind:active="activeViewport==1"></viewport>
              <viewport v-on:updated-series="SetViewportSeries(2, $event)"
                        v-on:selected-viewport="activeViewport=2"
                        v-show="viewport2Visible"
                        canvas-id="canvas2"
                        v-bind:content="viewport2Content"
                        v-bind:left="viewport2Left"
                        v-bind:top="viewport2Top"
                        v-bind:width="viewport2Width"
                        v-bind:height="viewport2Height"
                        v-bind:show-info="showInfo"
                        v-bind:global-configuration="globalConfiguration"
                        v-bind:active="activeViewport==2"></viewport>
              <viewport v-on:updated-series="SetViewportSeries(3, $event)"
                        v-on:selected-viewport="activeViewport=3"
                        v-show="viewport3Visible"
                        canvas-id="canvas3"
                        v-bind:content="viewport3Content"
                        v-bind:left="viewport3Left"
                        v-bind:top="viewport3Top"
                        v-bind:width="viewport3Width"
                        v-bind:height="viewport3Height"
                        v-bind:show-info="showInfo"
                        v-bind:global-configuration="globalConfiguration"
                        v-bind:active="activeViewport==3"></viewport>
              <viewport v-on:updated-series="SetViewportSeries(4, $event)"
                        v-on:selected-viewport="activeViewport=4"
                        v-show="viewport4Visible"
                        canvas-id="canvas4"
                        v-bind:content="viewport4Content"
                        v-bind:left="viewport4Left"
                        v-bind:top="viewport4Top"
                        v-bind:width="viewport4Width"
                        v-bind:height="viewport4Height"
                        v-bind:show-info="showInfo"
                        v-bind:global-configuration="globalConfiguration"
                        v-bind:active="activeViewport==4"></viewport>
            </div>
          </div>
        </div>
      </div>      
    </div>

    
    <script type="text/x-template" id="viewport-template">
      <div v-bind:id="canvasId + '-container'"
           v-bind:style="{ padding:'2px', 
                         position:'absolute', 
                         left: left, 
                         top: top,
                         width: width, 
                         height: height }">
        <div v-bind:class="{ 'wvSplitpane__cellBorder--selected' : active, 
                           'wvSplitpane__cellBorder' : content.series.color == '', 
                           'wvSplitpane__cellBorder--blue' : content.series.color == 'blue', 
                           'wvSplitpane__cellBorder--red' : content.series.color == 'red',
                           'wvSplitpane__cellBorder--green' : content.series.color == 'green', 
                           'wvSplitpane__cellBorder--yellow' : content.series.color == 'yellow', 
                           'wvSplitpane__cellBorder--violet' : content.series.color == 'violet'
                           }" 
             v-on:dragenter="$event.preventDefault()"
             v-on:dragover="$event.preventDefault()"
             v-on:drop="DragDrop($event)"
             style="width:100%;height:100%">
          <div class="wvSplitpane__cell" 
               v-on:click="MakeActive()">
            <div v-show="status == 'ready'"
                 style="position:absolute; left:0; top:0; width:100%; height:100%;">
              <!--div style="width: 100%; height: 100%; background-color: red"></div-->
              <canvas v-bind:id="canvasId" class="viewport-canvas"
                      style="position:absolute; left:0; top:0; width:100%; height:100%"
                      oncontextmenu="return false"></canvas>

              <div v-show="showInfo">
                <div v-if="numberOfFrames > 1" class="wvVerticalScrollbar"
                     v-on:mousedown="ClickVerticalScrollbar($event)">
                  <div class="wvVerticalScrollbarHighlight"
                       v-bind:style="{ top: (currentFrame / (numberOfFrames - 1) * 95.0) + '%' }">
                  </div>
                </div>
                
                <div class="wv-overlay">
                  <div v-if="'tags' in content.series" class="wv-overlay-topleft">
                    {{ content.series.tags[PATIENT_NAME] }}<br/>
                    {{ content.series.tags[PATIENT_ID] }}<br/>
                    {{ app.FormatDate(content.series.tags[PATIENT_BIRTH_DATE]) }} -
                    {{ content.series.tags[PATIENT_SEX] }}
                  </div>
                  <div v-if="'tags' in content.series" class="wv-overlay-topright"
                       v-bind:class="{ 'wvInfoRightMargin' : numberOfFrames > 1 }">
                    {{ content.series.tags[STUDY_DESCRIPTION] }}<br/>
                    <span v-if="contentDate !== undefined && contentDate != ''">{{ app.FormatDate(contentDate) }} <span v-show="contentTime != ''">{{ app.FormatTime(contentTime) }}</span><br/></span>
                    <span v-else="contentDate === undefined || contentDate == ''">{{ app.FormatDate(content.series.tags[STUDY_DATE]) }}<br/></span>
                    {{ content.series.tags[SERIES_NUMBER] }} | {{ content.series.tags[SERIES_DESCRIPTION] }}
                  </div>
                  <div class="wv-overlay-bottomleft wvPrintExclude" style="bottom: 0px">
                    <div v-show="instanceNumber != 0" style="padding-bottom: 1em">
                      Instance number: {{ instanceNumber }}
                    </div>
                    <span v-show="numberOfFrames != 0">
                      <div style="width: 12em;" v-show="cineControls">
                        <label>
                          Frame rate
                          <span class="wv-play-button-config-framerate-wrapper">
                            <input type="range" min="1" max="60" v-model="cineFramesPerSecond"
                                   class="wv-play-button-config-framerate">
                          </span>
                          {{ cineFramesPerSecond }} fps
                        </label>
                      </div>
                      <div class="btn-group btn-group-sm" role="group">                        
                        <button class="btn btn-primary" @click="stone.GoToFirstFrame(canvasId)">
                          <i class="fas fa-fast-backward"></i>
                        </button>
                        <button class="btn btn-primary" @click="DecrementFrame()">
                          <i class="fas fa-step-backward"></i>
                        </button>
                      </div>
                      <span data-toggle="tooltip" data-title="Current frame number">
                        &nbsp;&nbsp;
                        <input type="text" v-model="currentFrameFromUser" class="wvInputInstanceNumber"> / {{ numberOfFrames }}
                        &nbsp;&nbsp;
                      </span>
                      <div class="btn-group btn-group-sm" role="group">                        
                        <button class="btn btn-primary" @click="IncrementFrame()">
                          <i class="fas fa-step-forward"></i>
                        </button>
                        <button class="btn btn-primary" @click="stone.GoToLastFrame(canvasId)">
                          <i class="fas fa-fast-forward"></i>
                        </button>
                      </div>
                      <div class="btn-group btn-group-sm" role="group">                        
                        <button type="button" class="btn btn-primary" @click="CineBackward()">
                          <i class="fas fa-play fa-flip-horizontal"></i>
                        </button>
                        <button type="button" class="btn btn-primary" @click="CinePause()">
                          <i class="fas fa-pause"></i>
                        </button>
                        <button type="button" class="btn btn-primary" @click="CinePlay()">
                          <i class="fas fa-play"></i>
                        </button>
                      </div>
                    </span>
                  </div>
                  <div class="wv-overlay-bottomright wvPrintExclude" style="bottom: 0px"
                       v-bind:class="{ 'wvInfoRightMargin' : numberOfFrames > 1 }">
                    <div v-if="windowingWidth != 0">
                      ww/wc: {{ windowingWidth }} / {{ windowingCenter }}
                    </div>
                    <div style="padding-top: 0.5em">
                      <div v-show="quality == stone.DisplayedFrameQuality.NONE"
                           style="display:inline-block;background-color:red;width:1em;height:1em" />
                      <div v-show="quality == stone.DisplayedFrameQuality.LOW"
                           style="display:inline-block;background-color:orange;width:1em;height:1em" />
                      <div v-show="quality == stone.DisplayedFrameQuality.HIGH"
                           style="display:inline-block;background-color:green;width:1em;height:1em" />
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <div v-if="status == 'waiting'" class="wvPaneOverlay">
              [ drop a series here ]
            </div>

            <!-- Don't use "v-if" here, otherwise the tooltips of the PDF viewer are not initialized -->
            <div v-show="status == 'pdf'" >
              <pdf-viewer v-bind:prefix="canvasId + '-pdf'" ref="pdfViewer"></pdf-viewer>
            </div>
                
            <div v-if="status == 'video'" class="wvPaneOverlay">
              <div v-if="!('OrthancApiRoot' in globalConfiguration) || videoUri.length == 0">
                [ cannot play videos using DICOMweb yet ]
              </div>
              <div v-if="'OrthancApiRoot' in globalConfiguration && videoUri.length > 0">
                <video class="wvVideo" autoplay="" loop="" controls="" preload="auto" type="video/mp4"
                       v-bind:src="videoUri">
                </video>
              </div>
            </div>
                
            <div v-if="status == 'loading'" class="wvPaneOverlay">
              <span class="wvLoadingSpinner">
                <div class="bounce1"></div>
                <div class="bounce2"></div>
                <div class="bounce3"></div>
              </span>
            </div>
          </div>
        </div>
      </div>
    </script>


    <script type="text/x-template" id="pdf-viewer">
      <div style="position:absolute; left:0; top:0; width:100%; height:100%;">
        <!-- "line-height: 0px" to fit height: https://stackoverflow.com/a/12616341/881731 -->
        <div v-bind:id="prefix + '-container'"
             style="position: absolute; left: 0; top: 0; width:100%;height:100%;overflow:auto;line-height: 0px;">
          <canvas v-bind:id="prefix + '-canvas'"
                  style="position: absolute; top:0px; left:0px;"
                  oncontextmenu="return false"></canvas>
        </div>

        <div class="wv-overlay">
          <div class="wv-overlay-bottomleft wvPrintExclude">
            <button class="btn btn-primary" @click="Download()"
                    data-toggle="tooltip" data-title="Download">
              <i class="fa fa-download"></i>
            </button>
            <button class="btn btn-primary" @click="Print()"
                    data-toggle="tooltip" data-title="Print">
              <i class="fa fa-print"></i>
            </button>
            <button class="btn btn-primary" @click="FitWidth()"
                    data-toggle="tooltip" data-title="Fit page width">
              <i class="fas fa-text-width"></i>
            </button>
            <button class="btn btn-primary" @click="FitHeight()"
                    data-toggle="tooltip" data-title="Fit page height">
              <i class="fas fa-text-height"></i>
            </button>
            <button class="btn btn-primary" @click="ZoomIn()"
                    data-toggle="tooltip" data-title="Zoom in">
              <i class="fas fa-search-plus"></i>
            </button>
            <button class="btn btn-primary" @click="ZoomOut()"
                    data-toggle="tooltip" data-title="Zoom out">
              <i class="fas fa-search-minus"></i>
            </button>
            <button class="btn btn-primary" @click="PreviousPage()"
                    data-toggle="tooltip" data-title="Show previous page">
              <i class="fa fa-chevron-circle-left"></i>
            </button>
            &nbsp;&nbsp;{{currentPage}} / {{countPages}}&nbsp;&nbsp;
            <button class="btn btn-primary" @click="NextPage()"
                    data-toggle="tooltip" data-title="Show next page">
              <i class="fa fa-chevron-circle-right"></i>
            </button>
          </div>
        </div>
      </div>
    </script>
    <script src="js/jquery-3.7.1.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
    <script src="js/vue.min.js"></script>
    <script src="js/axios.min.js"></script>
    <script src="js/pdf.js"></script>
    
    <script src="stone.js"></script>
    <script src="pdf-viewer.js"></script>   <!-- Must be before inclusion of "app.js" -->
    <script src="app.js"></script>
    <script src="print.js"></script>
  </body>
</html>