Last active
November 20, 2020 06:30
-
-
Save bdunnette/9073504 to your computer and use it in GitHub Desktop.
Process Aperio SVS files into web tiles (with Leaflet-based viewer)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Copyright (c) 2014 Regents of the University of Minnesota | |
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
# This script requires both libvips-tools and openslide-tools to be installed | |
# Note that the version of libopenjpeg2 in Ubuntu 13.10 and later is broken - see https://github.com/openslide/openslide/issues/130 | |
# Fix for now is to install earlier version of libopenjpeg2 from Debian, available here: http://packages.ubuntu.com/saucy/libopenjpeg2 | |
# Once a working version of libopenjpeg2 is installed, prevent it from being automatically upgraded by running: sudo apt-mark hold libopenjpeg2 | |
# Initialize our own variables: | |
offline=0 | |
upload=0 | |
verbose=0 | |
while getopts "ouv" opt; do | |
case "$opt" in | |
o) offline=1 | |
;; | |
u) upload=1 | |
;; | |
v) verbose=1 | |
;; | |
esac | |
done | |
echo "offline=$offline, upload=$upload, verbose=$verbose, Leftovers: $@" | |
trackingcode="UA-XXXXXXXXX-X" | |
imagehost="web-basic.oit.umn.edu" | |
js_and_css_offline="<link rel=\"stylesheet\" href=\"assets/leaflet.css\"/><script src=\"assets/jquery.min.js\"></script><script src=\"assets/leaflet.js\"></script>" | |
js_and_css="<link rel=\"stylesheet\" href=\"//cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.css\"/><script src=\"//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.min.js\"></script><script src=\"//cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.js\"></script>" | |
tracking_script="<script>(function(e,t,n,r,i,s,o){e[\"GoogleAnalyticsObject\"]=i;e[i]=e[i]||function(){(e[i].q=e[i].q||[]).push(arguments)},e[i].l=1*new Date;s=t.createElement(n),o=t.getElementsByTagName(n)[0];s.async=1;s.src=r;o.parentNode.insertBefore(s,o)})(window,document,\"script\",\"//www.google-analytics.com/analytics.js\",\"ga\");ga(\"create\",\"$trackingcode\",\"umn.edu\");ga(\"send\",\"pageview\")</script>" | |
if [ "$offline" -gt 0 ] | |
then | |
mkdir assets | |
wget -O assets/jquery.min.js https://code.jquery.com/jquery-1.11.1.min.js | |
wget -O assets/leaflet.js http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js | |
wget -O assets/leaflet.css http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css | |
fi | |
#Read case/slide titles from titles.txt (one case/title per line) | |
titles=() | |
titlefile="titles.txt" | |
while read line | |
do | |
titles+=("$line") | |
done <"$titlefile" | |
#Write header to index.html, using first command-line argument as title (if given) | |
if [ "$1" != "" ] | |
then | |
pagetitle="$1" | |
else | |
pagetitle="Whole Slide Images" | |
fi | |
pagehead="<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>$pagetitle</title><style>body {font-family: sans-serif;}ol {counter-reset: item; list-style-type: none;} ol li:before {font-weight: bold; content: 'Case ' counter(item, decimal) ': '; counter-increment: item;}.slide-preview{width:100px;vertical-align:middle;}</style></head><body><h2>$pagetitle</h2><ol>" | |
echo $pagehead > index.html | |
if [ "$offline" -gt 0 ] | |
then | |
echo $pagehead > index-offline.html | |
fi | |
#Process SVS files | |
index=0 | |
for s in *.svs | |
do | |
i="${s%.*}" | |
echo "Processing $s" | |
echo "Title: ${titles[$index]}" | |
slidetitle="${titles[$index]}" | |
#If there's no matching title in titles.txt, use the slide ID as the slide's title | |
if [ "$slidetitle" = "" ] | |
then | |
slidetitle="$s" | |
fi | |
#Remove existing image directory if it exists | |
if [ -d $i ] | |
then | |
rm -R $i | |
fi | |
#Get slide height and width to help us "flatten" image | |
slideprops=`openslide-show-properties $s` | |
width=`echo "$slideprops" |grep 'openslide.level\[0\].width'|cut -d':' -f2|tr -d ' '|sed "s/'//g"` | |
height=`echo "$slideprops" |grep 'openslide.level\[0\].height'|cut -d':' -f2|tr -d ' '|sed "s/'//g"` | |
echo "$width px wide, $height px high" | |
page_head="<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>$slidetitle</title><meta name=\"viewport\" content=\"width=device-width\">" | |
page_body="</head> | |
<body style=\"margin:0;\"> | |
<div id=\"map\"></div> | |
<script> | |
\$(document).ready( function(){ | |
\$('#map').css({height: \$(window).height() + 'px'}); | |
var map=new L.map('map'); | |
var slideUrl='$i/{z}/{y}/{x}.jpg' | |
slide=L.tileLayer(slideUrl,{minZoom: 0, maxZoom: 8, noWrap: true, errorTile: '$i/blank.png', attribution: 'Images © 2014 Regents of the University of Minnesota'}); | |
var southWest=map.unproject([0, $width], map.getMaxZoom()); | |
var northEast=map.unproject([$height, 0], map.getMaxZoom()); | |
map.addLayer(slide); | |
map.setView(new L.LatLng(0, 0), 3); | |
function onMapMove(e){console.log('Center:' + map.getCenter() + ' Zoom:' + map.getZoom());}map.on('moveend', onMapMove);}); | |
\$(window).resize(function(){\$('#map').css({height: \$(window).height() + 'px'});}); | |
</script>" | |
page_foot="</body></html>" | |
pagefile="$i.html" | |
echo "$page_head $js_and_css $page_body $tracking_script $page_foot" > $pagefile | |
echo "<li><a href=\"$pagefile\"><img src=\"$i/0/0/0.jpg\" class=\"slide-preview\">$slidetitle</a></li>" >> index.html | |
if [ "$offline" -gt 0 ] | |
then | |
pagefile_offline="$i-offline.html" | |
echo "$page_head $js_and_css_offline $page_body $page_foot" > $pagefile_offline | |
echo "<li><a href=\"$pagefile_offline\"><img src=\"$i/0/0/0.jpg\" class=\"slide-preview\">$slidetitle</a></li>" >> index-offline.html | |
fi | |
time vips dzsave $s $i --overlap 0 --layout google --centre | |
if [ "$offline" -gt 0 ] | |
then | |
echo "Creating $i.zip for offline viewing" | |
time zip -qr $i $i $pagefile_offline assets | |
fi | |
tf="$i.tar" | |
echo "Creating $tf" | |
time tar -cf $tf $i $pagefile | |
if [ "$upload" -gt 0 ] | |
then | |
echo "Uploading $tf to $imagehost" | |
scp -q index.html $i.html $tf $imagehost: & | |
if [ "$offline" -gt 0 ] | |
then | |
echo "Uploading $i.zip to $imagehost" | |
scp -q $i.zip $imagehost: & | |
fi | |
fi | |
((index++)) | |
done | |
#Finish writing index.html and upload to server | |
echo "</ol>$tracking_script</body></html>" >> index.html | |
if [ "$offline" -gt 0 ] | |
then | |
echo "</ol></body></html>" >> index-offline.html | |
fi | |
if [ "$upload" -gt 0 ] | |
then | |
scp index.html $imagehost: | |
fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 year old man with hoarseness; lesion on the vocal cord | |
54 year old woman, non-smoker, presented with enlarged neck node | |
72 year old man, with history of BMT for mantle cell lymphoma, now with PET positive sinonasal lesion | |
63 year old man, left sinonasal mass | |
51 year old man, with a sub-mandibular mass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment