public
Last active

Stitch Zoomify tiles into a single image (Ruby + ImageMagick).

  • Download Gist
README.markdown
Markdown

Dezoomify

Ruby + ImageMagick script to stitch Zoomify Viewer tiles into a single image at the maximum zoom level.

Usage

Give the URL of a page that contains the Zoomify Flash viewer, e.g.:

./dezoomify.rb 'http://www.christies.com/lotfinder/ZoomImage.aspx?image=/LotFinderImages/D52792/D5279274'

The file would end up in /tmp/zoomified-0.0.jpg on Mac/*UX and C:\WINDOWS\Temp\zoomified-0.0.jpg on Windows. (The script has yet to be confirmed to work on Windows.)

You can give multiple URLs as separate arguments:

./dezoomify.rb 'http://collections.frick.org/CUS.18.zoomobject._580$7286*1484740' 'http://collections.frick.org/CUS.18.zoomobject._1106$8983*1484996' 'http://collections.frick.org/CUS.18.zoomobject._1105$7286*1516394'

The files would end up in /tmp/zoomified-0.0.jpg, /tmp/zoomified-1.0.jpg and /tmp/zoomified-2.0.jpg or the equivalent Windows directory.

The first number in the output filename reflects the ordinal of the provided URL. The second number is used if a single URL contains multiple Zoomify viewers.

Note that filenames do not increment from run to run, so beware of overwriting previously dezoomified files.

If you're on OS X, each file will be revealed in the Finder as download and stitching is completed.

Credits and license

Copyright (c) 2009 Henrik Nyh

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.

dezoomify.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
#!/usr/bin/env ruby
# Dezoomify. See README.markdown.
# By Henrik Nyh <http://henrik.nyh.se> 2009-02-06 under the MIT License.
 
require 'cgi'
require 'open-uri'
require 'rubygems'
require 'nokogiri'
 
module Kernel
def windows?
RbConfig::CONFIG['host_os'].match(/mswin|windows|mingw/i)
end
end
 
if windows?
# Case-sensitive. Use forward slashes, or double-escape backslashes.
TEMP_DIRECTORY = "C:/WINDOWS/Temp"
else
TEMP_DIRECTORY = "/tmp"
end
 
ARGV.each_with_index do |page_url, page_url_index|
if page_url.include?("zoomifyImagePath")
puts "#{page_url_index}. Extracting path from URL"
source = page_url
else
puts "#{page_url_index}. Visiting #{page_url}"
source = open(page_url).read
end
 
paths = source.scan(/zoomifyImagePath=([^"'&]+)/).flatten.map {|path| path.gsub(' ', '%20') }.uniq
 
paths.each_with_index do |path, path_index|
path = CGI.unescape(path)
full_path = URI.join(page_url, path+'/')
puts " #{page_url_index}.#{path_index} Found image path #{full_path}"
 
 
# <IMAGE_PROPERTIES WIDTH="1737" HEIGHT="2404" NUMTILES="99" NUMIMAGES="1" VERSION="1.8" TILESIZE="256"/>
xml_url = URI.join(full_path.to_s, 'ImageProperties.xml')
doc = Nokogiri::XML(open(xml_url))
props = doc.at('IMAGE_PROPERTIES')
 
width = props[:WIDTH].to_i
height = props[:HEIGHT].to_i
tilesize = props[:TILESIZE].to_f
 
tiles_wide = (width/tilesize).ceil
tiles_high = (height/tilesize).ceil
 
# Determine max zoom level.
# Also determine tile_counts per zoom level, used to determine tile group.
# With thanks to http://trac.openlayers.org/attachment/ticket/1285/zoomify.patch.
zoom = 0
w = width
h = height
tile_counts = []
while w > tilesize || h > tilesize
zoom += 1
 
t_wide = (w / tilesize).ceil
t_high = (h / tilesize).ceil
tile_counts.unshift t_wide*t_high
 
w = (w / 2.0).floor
h = (h / 2.0).floor
end
tile_counts.unshift 1 # Zoom level 0 has a single tile.
tile_count_before_level = tile_counts[0..-2].inject(0) {|sum, num| sum + num }
 
files_by_row = []
tiles_high.times do |y|
row = []
tiles_wide.times do |x|
filename = '%s-%s-%s.jpg' % [zoom, x, y]
local_filepath = "#{TEMP_DIRECTORY}/zoomify-#{filename}"
row << local_filepath
 
tile_group = ((x + y * tiles_wide + tile_count_before_level) / tilesize).floor
 
tile_url = URI.join(full_path.to_s, "TileGroup#{tile_group}/#{filename}")
url = URI.join(tile_url.to_s, filename)
puts " Getting #{url}..."
File.open(local_filepath, 'wb') {|f| f.print url.read }
end
files_by_row << row
end
 
 
# `montage` is ImageMagick.
# We first stitch together the tiles of each row, then stitch all rows.
# Stitching the full image all at once can get extremely inefficient for large images.
 
puts " Stitching #{tiles_wide} x #{tiles_high} = #{tiles_wide*tiles_high} tiles..."
 
row_files = []
files_by_row.each_with_index do |row, index|
filename = "#{TEMP_DIRECTORY}/zoomify-row-#{index}.jpg"
`montage #{row.join(' ')} -geometry +0+0 -tile #{tiles_wide}x1 #{filename}`
row_files << filename
end
 
filename = "#{TEMP_DIRECTORY}/zoomified-#{page_url_index}.#{path_index}.jpg"
 
`montage #{row_files.join(' ')} -geometry +0+0 -tile 1x#{tiles_high} #{filename}`
 
unless windows? || `which xattr`.empty?
# Set "Downloaded from" Finder metadata, like Safari does.
system('xattr', '-w', 'com.apple.metadata:kMDItemWhereFroms', page_url, filename)
end
 
puts " Done: #{filename}"
 
# Reveal in Finder if on OS X.
unless windows?
`which osascript && osascript -e 'tell app "Finder"' -e 'reveal POSIX file "#{filename}"' -e 'activate' -e 'end'`
end
 
end
 
end

arechampions:

It's probably possible on Windows if you install Cygwin with ImageMagick and Ruby. Afraid I can't give more detailed instructions than that.

Henrik, thanks for great script.

But what string should I use for a map like this ?
http://www.natgeomaps.com/ti_104_zoomify.html?zoomifyImagePath=assets/files/zoomify/ti00000104/ti00000104_3_img&zoomifyNavigatorVisible=false

I am trying to figure it out but without any result

Raff13: That page creates the Zoomify Flash embed code with JavaScript. I just updated dezoomify to support that. So with the current version of dezoomify, this should work:

ruby dezoomify.rb "http://www.natgeomaps.com/ti_104_zoomify.html?zoomifyImagePath=assets/files/zoomify/ti00000104/ti00000104_3_img&amp;zoomifyNavigatorVisible=false"

Henrik, it works very well now. And it works on Windows. One needs only Ruby and Imagemagick plus installing nokogiri gem. Then it's OK.

Raff13, how do you install nokogiri gem on your pc?

Help I really really need to extract images from a zoomify ste but know squat about programing, I did see a web page that you entered a url containing the zoomify'ed image but all it shows is a brief red line. Is there a simple command line or a web site i can enter a url string into that will extract the image at max res, I can supply the url if anyone can help please....please....gs

hi, I installed nokogiri, but it's not downloading any images. I tried the command line showed in the Usage section aswell as the one henrik posted on June 1st 2010. in both cases gives error on line 66, and it also says "0.0 Found image" which I understand it means that it can't find any image.

@mai9 What is the error message exactly? The "0.0" just means it attempted to download from the first URL given (starts counting from 0), and the first image on that page if there are many.

thanks for your fast reply henrik.
here's the screenshot: http://tinypic.com/r/2zevthh/5
the url I am trying to use is: http://www.christies.com/lotfinder/ZoomImage.aspx?image=/LotFinderImages/D52029/D5202920

I hope this information helps.

The script wasn't adapted for Windows. I made some quick changes -
hopefully that fixes it, but I haven't tried it on Windows, so I might
have missed something. Let me know.

On Sun, Dec 11, 2011 at 21:32, mai9
reply@reply.github.com
wrote:

thanks for your fast reply henrik.
here's the screenshot: http://tinypic.com/r/2zevthh/5
the url I am trying to use is: http://www.christies.com/lotfinder/ZoomImage.aspx?image=/LotFinderImages/D52029/D5202920

I hope this information helps.


Reply to this email directly or view it on GitHub:
https://gist.github.com/59636

thank you, that worked wonders :)

Hello, I'm having trouble with this one:
http://maps.bpl.org/zoomify.php?baseUrl=http://maps.bpl.org/&viewer=modern&id=06_01_002652

The ZoomifyImagePath is javascript

Also, here is the path of the tiles:
http://maps.bpl.org/pub/zoom/06/01/06_01_002652/

Hello, your script did not work with this image, I believe it extracted the wrong URL:

http://link.library.utoronto.ca/hollar/digobject.cfm?Idno=Hollar_k_0201&size=zoom&query=Hollar_k_0201&type=browse

Here is the result:

% ./dezoomify.rb 'http://link.library.utoronto.ca/hollar/digobject.cfm?Idno=Hollar_k_0201&size=zoom&query=Hollar_k_0201&type=browse'
0. Visiting http://link.library.utoronto.ca/hollar/digobject.cfm?Idno=Hollar_k_0201&size=zoom&query=Hollar_k_0201&type=browse
  0.0 Found image path http://link.library.utoronto.ca/hollar/http%3A%2F%2Fwww%2Elibrary%2Eutoronto%2Eca%2Fhollar%2Fzoomify%2FHollar_k_0201%2FHollar_k_0201_0001%2F/
/usr/lib/ruby/1.9.1/open-uri.rb:346:in `open_http': 404 Not Found (OpenURI::HTTPError)
        from /usr/lib/ruby/1.9.1/open-uri.rb:775:in `buffer_open'
        from /usr/lib/ruby/1.9.1/open-uri.rb:203:in `block in open_loop'
        from /usr/lib/ruby/1.9.1/open-uri.rb:201:in `catch'
        from /usr/lib/ruby/1.9.1/open-uri.rb:201:in `open_loop'
        from /usr/lib/ruby/1.9.1/open-uri.rb:146:in `open_uri'
        from /usr/lib/ruby/1.9.1/open-uri.rb:677:in `open'
        from /usr/lib/ruby/1.9.1/open-uri.rb:29:in `open'
        from ./dezoomify.rb:35:in `block (2 levels) in <main>'
        from ./dezoomify.rb:28:in `each'
        from ./dezoomify.rb:28:in `each_with_index'
        from ./dezoomify.rb:28:in `block in <main>'
        from ./dezoomify.rb:22:in `each'
        from ./dezoomify.rb:22:in `each_with_index'
        from ./dezoomify.rb:22:in `<main>'

@kugland I think I fixed it – try again with the current version of the script!

Now it worked perfectly! But it still gives this warning, “./dezoomify.rb:12: Use RbConfig instead of obsolete and deprecated Config.”, which I removed by simply replacing “Config” with “RbConfig” in the aforementioned line.

Fixed that one too, now; thanks!

On Mon, Apr 9, 2012 at 18:45, André von Kugland
reply@reply.github.com
wrote:

Now it worked perfectly! But it still gives this warning, “./dezoomify.rb:12: Use RbConfig instead of obsolete and deprecated Config.”, which I removed by simply replacing “Config” with “RbConfig” in the aforementioned line.


Reply to this email directly or view it on GitHub:
https://gist.github.com/59636

I am having trouble with the following image. Can you help? Note, this zoomify image displays fine in IE/Chrome/Safari. I had trouble viewing with firefox (not sure why).

http://220.227.252.236/ehmr/Ibrahimpatanam%20and%20Manchal.aspx

$ ./dezoomify.rb 'http://220.227.252.236/ehmr/Ibrahimpatanam%20and%20Manchal.aspx'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in gem_original_require': no such file to load -- nokogiri (LoadError)
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in
require'
from ./dezoomify.rb:8

@nicetry158 I think maybe OS X used to include the nokogiri Ruby gem but doesn't anymore.

Something like sudo gem install nokogiri (it will ask for your password) may work to install it. If it seems to successfully install, dezoomify will hopefully work.

Andrea G mailed me to suggest this change to handle timeouts. I don't have the time right now to update the script, but I'll paste what was mailed to me:

retries = 10
begin
  Timeout::timeout(5) do
    File.open(local_filepath, 'wb') {|f| f.print url.read }
  end
rescue Timeout::Error
  if retries > 0
    print "Timeout - Retrying... "
    retries -= 1
    retry
  else
    puts "ERROR: Not responding after 10 retries!  Giving up!"
    exit
  end
end

This patch will allow this script to stitch together the tiles from a zoom level below the highest, in the event the highest zoom has a ridiculous amount of tiles.

Setting zoom_levels_to_skip to 1 will download and stitch the second highest zoom tiles, setting it to 2 will download and stitch 2 from the highest zoomed tiles, etc. Leaving it at 0 will obviously leave the default functionality - the highest zoom level.

49,50c49,52
<     tiles_wide = (width/tilesize).ceil
<     tiles_high = (height/tilesize).ceil
---
>     zoom_levels_to_skip = 0
> 
>     tiles_wide = (width/tilesize/(2**zoom_levels_to_skip)).ceil
>     tiles_high = (height/tilesize/(2**zoom_levels_to_skip)).ceil
69a72
>     tile_counts = tile_counts[0..-(zoom_levels_to_skip + 1)]
76c79
<         filename = '%s-%s-%s.jpg' % [zoom, x, y]
---
>         filename = '%s-%s-%s.jpg' % [zoom - zoom_levels_to_skip, x, y]

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.