Skip to content

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Stitch Zoomify tiles into a single image (Ruby + ImageMagick).

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.

#!/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
@henrik
Owner

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.

@Raff13

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

@henrik
Owner

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"
@Raff13

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.

@Filipvl

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

@mi3gto

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

@mai9

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.

@henrik
Owner

@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.

@mai9

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.

@henrik
Owner
@mai9

thank you, that worked wonders :)

@billflu

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/

@kugland

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>'
@henrik
Owner

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

@kugland

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.

@henrik
Owner
@nicetry158

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

@henrik
Owner

@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.

@henrik
Owner

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
@cainmi

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]
@putt1ck

Installing on openSUSE Factory: install ruby-devel, then gem install nokogiri can complete.

Works very well, thx :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.