Skip to content

Instantly share code, notes, and snippets.

@bdunnette
Last active November 20, 2020 06:30
Show Gist options
  • Save bdunnette/9073504 to your computer and use it in GitHub Desktop.
Save bdunnette/9073504 to your computer and use it in GitHub Desktop.
Process Aperio SVS files into web tiles (with Leaflet-based viewer)
#!/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 &copy; 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
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