Skip to content

Commit

Permalink
Major overhaul to ESM imagebox3. Removed environment-specific behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
PrafulB committed Feb 17, 2024
1 parent 7709ab9 commit 081905d
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 105 deletions.
File renamed without changes.
2 changes: 1 addition & 1 deletion demo/scriptTag/index.js → demo/esm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ imgBox.loadTile = async ({fileURL, tileX, tileY, tileWidth, tileHeight, tileSize
tileWidth,
tileHeight,
tileSize
})).blob())
}, true)).blob())
tileElement.onload = () => {
URL.revokeObjectURL(tileElement.src)
}
Expand Down
145 changes: 42 additions & 103 deletions imagebox3.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,8 @@
import { fromBlob, fromUrl, Pool, getDecoder } from "https://cdn.jsdelivr.net/npm/geotiff@2.1.2/+esm"

const imagebox3 = (() => {

const ENVIRONMENT_IS_WEB = typeof window === "object" && self instanceof Window,
ENVIRONMENT_IS_NODE = !ENVIRONMENT_IS_WEB && typeof process === "object" ,
ENVIRONMENT_IS_WEB_WORKER = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && typeof WorkerGlobalScope === "function" && self instanceof WorkerGlobalScope,
ENVIRONMENT_IS_SERVICE_WORKER = ENVIRONMENT_IS_WEB_WORKER && typeof ServiceWorkerGlobalScope === "function" && self instanceof ServiceWorkerGlobalScope

let workerPool = undefined

let utils = {
defineTileServerURL: () => {
// Load tile server base path from search params passed in service worker registration.
const urlSearchParams = new URLSearchParams(self.location.search)
if (urlSearchParams.has("tileServerURL")) {
return urlSearchParams.get("tileServerURL")
} else if (urlSearchParams.has("tileServerPathSuffix")) {
return `${self.location.origin}/${urlSearchParams.get("tileServerPathSuffix")}`
}
}
}


if (ENVIRONMENT_IS_SERVICE_WORKER) {
// Service worker fetch handling.
self.oninstall = () => {
self.skipWaiting()
}

self.onactivate = () => {
self.clients.claim()
}

self.tileServerBasePath = utils.defineTileServerURL()

self.addEventListener("fetch", (e) => {

if (e.request.url.startsWith(self.tileServerBasePath)) {

let regex = new RegExp(self.tileServerBasePath + "\/(?<identifier>.[^/]*)\/")
const { identifier } = regex.exec(e.request.url).groups

if (e.request.url.endsWith("/info.json")) {
e.respondWith(imagebox3.getImageInfo(decodeURIComponent(identifier)))
}

else if (e.request.url.includes("/full/")) {
regex = /full\/(?<thumbnailWidthToRender>[0-9]+?),[0-9]*?\/(?<thumbnailRotation>[0-9]+?)\//
const thumnbnailParams = regex.exec(e.request.url).groups
e.respondWith(imagebox3.getImageThumbnail(decodeURIComponent(identifier), thumnbnailParams))
}

else if (e.request.url.endsWith("/default.jpg")) {
regex = /\/(?<tileX>[0-9]+?),(?<tileY>[0-9]+?),(?<tileWidth>[0-9]+?),(?<tileHeight>[0-9]+?)\/(?<tileSize>[0-9]+?),[0-9]*?\/(?<tileRotation>[0-9]+?)\//
const tileParams = regex.exec(e.request.url).groups
e.respondWith(imagebox3.getImageTile(decodeURIComponent(identifier), tileParams))
}
}

})
} else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WEB_WORKER) {
// workerPool = new Pool(Math.floor(navigator.hardwareConcurrency/2))
} else if (ENVIRONMENT_IS_NODE) {
// TODO: Add node.js support
}

return {
pool: workerPool
}

})();

(function ($){

let tiff = {} // Variable to cache GeoTIFF instance per image for reuse.
const imageInfoContext = "http://iiif.io/api/image/2/context.json"

Expand All @@ -83,8 +13,7 @@ const imagebox3 = (() => {

let supportedDecoders = {};
fetch(decodersJSON_URL).then(resp => resp.json()).then(decoders => {
supportedDecoders = decoders
console.log(supportedDecoders)
supportedDecoders = decoders
})

const utils = {
Expand Down Expand Up @@ -249,9 +178,8 @@ const imagebox3 = (() => {
return
}

const getImageThumbnail = async (imageID, tileParams, pool=false) => {
const getImageThumbnail = async (imageID, tileParams, threadPoolSize=0) => {


const parsedTileParams = utils.parseTileParams(tileParams)
let { thumbnailWidthToRender, thumbnailHeightToRender } = parsedTileParams

Expand All @@ -267,8 +195,9 @@ const imagebox3 = (() => {
}

const thumbnailImage = await tiff[imageKey].pyramid.getImage(1)
if (pool) {
await createPool(thumbnailImage)

if (threadPoolSize > 0) {
await createPool(thumbnailImage, threadPoolSize)
}

if (!thumbnailHeightToRender) {
Expand All @@ -281,15 +210,15 @@ const imagebox3 = (() => {
let data = await thumbnailImage.readRasters({
width: thumbnailWidthToRender,
height: thumbnailHeightToRender,
pool: $.workerPool
pool: workerPool
})

const imageResponse = await utils.convertToImageBlob(data, thumbnailWidthToRender, thumbnailHeightToRender)
return imageResponse

}

const getImageTile = async (imageID, tileParams, pool=false) => {
const getImageTile = async (imageID, tileParams, threadPoolSize=0) => {
// Get individual tiles from the appropriate image in the pyramid.

const parsedTileParams = utils.parseTileParams(tileParams)
Expand All @@ -312,8 +241,8 @@ const imagebox3 = (() => {
const optimalImageHeight = optimalImageInTiff.getHeight()
const tileHeightToRender = Math.floor( tileHeight * tileSize / tileWidth)

if (pool) {
await createPool(optimalImageInTiff)
if (threadPoolSize > 0) {
await createPool(optimalImageInTiff, threadPoolSize)
}

const { maxWidth, maxHeight } = tiff[imageKey].pyramid
Expand All @@ -332,54 +261,64 @@ const imagebox3 = (() => {
tileInImageRightCoord,
tileInImageBottomCoord,
],
pool: $.workerPool
pool: workerPool
})

const imageResponse = await utils.convertToImageBlob(data, tileSize, tileHeightToRender)
return imageResponse
}

const createPool = async (tiffImage) => {
if (typeof(Worker) !== 'undefined') {
const createPool = async (tiffImage, numThreads=0) => {
if (typeof(Worker) !== 'undefined' && Number.isInteger(numThreads) && numThreads > 0) {
// Condition to check if this is a service worker-like environment. Service workers cannot create workers,
// plus the GeoTIFF version has to be downgraded to avoid any dynamic imports.
// As a result, thread creation and non-standard image decoding does not work inside service workers. You would typically
// only use service workers to support OpenSeadragon anyway, in which case you'd be better off using something like
// https://github.com/episphere/GeoTIFFTileSource-JPEG2k .

const imageCompression = tiffImage?.fileDirectory.Compression
const geotiffSupportsCompression = typeof(getDecoder(tiffImage.fileDirectory)) === 'function'
const decoderForCompression = supportedDecoders?.[imageCompression]

let createWorker = undefined
if (decoderForCompression) {
createWorker = () => new Worker( URL.createObjectURL( new Blob([`
importScripts("${baseURL}/decoders/${decoderForCompression}")
`])));
let geotiffSupportsCompression, createWorker
try {
geotiffSupportsCompression = await getDecoder(tiffImage.fileDirectory)
} catch (e) {
if (e.message.includes("Unknown compression method")) {
const decoderForCompression = supportedDecoders?.[imageCompression]
if (decoderForCompression) {
createWorker = () => new Worker( URL.createObjectURL( new Blob([`
importScripts("${baseURL}/decoders/${decoderForCompression}")
`])));
}
} else {
throw new Error(`Unsupported compression method: ${imageCompression}. Cannot process this image.`)
}
}

if (!$.workerPool) {
$.workerPool = new Pool(Math.min(Math.floor(navigator.hardwareConcurrency/2), 1), createWorker)
$.workerPool.supportedCompression = imageCompression
} else if (!geotiffSupportsCompression && $.workerPool.supportedCompression !== imageCompression) {
if (!workerPool) {
workerPool = new Pool(Math.min(Math.floor(navigator.hardwareConcurrency/2), numThreads), createWorker)
workerPool.supportedCompression = imageCompression
} else if (!geotiffSupportsCompression && workerPool.supportedCompression !== imageCompression) {
destroyPool()
$.workerPool = new Pool(Math.min(Math.floor(navigator.hardwareConcurrency/2), 1), createWorker)
$.workerPool.supportedCompression = imageCompression
workerPool = new Pool(Math.min(Math.floor(navigator.hardwareConcurrency/2), numThreads), createWorker)
workerPool.supportedCompression = imageCompression
}

await new Promise(res => setTimeout(res, 500)) // Setting up the worker pool is an asynchronous task, give it time to complete before moving on.
}
}

const destroyPool = () => {
$.workerPool?.destroy()
$.workerPool = undefined
workerPool?.destroy()
workerPool = undefined
}

[getImageInfo, getImageThumbnail, getImageTile, createPool, destroyPool].forEach(method => {
$[method.name] = method
})
return {
getImageInfo,
getImageThumbnail,
getImageTile,
createPool,
destroyPool
}

})(imagebox3)
})()

export default imagebox3
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ <h3>Demos:</h2>
<ul>
<li><a href="demo/serviceWorker/index.html">Complete WSI Viewer</a></li>
<br>
<li><a href="demo/scriptTag/index.html">Single Tile Loading</a></li>
<li><a href="demo/esm/index.html">Single Tile Loading</a></li>
</ul>
</div>
</div>
Expand Down

0 comments on commit 081905d

Please sign in to comment.