-
Notifications
You must be signed in to change notification settings - Fork 66
/
index.js
238 lines (203 loc) · 6.75 KB
/
index.js
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import axios from 'axios'
import vtkURLExtract from 'vtk.js/Sources/Common/Core/URLExtract'
import { readImageArrayBuffer } from 'itk-wasm'
import fetchBinaryContent from './IO/fetchBinaryContent'
import fetchJsonContent from './IO/fetchJsonContent'
import { processFiles } from './IO/processFiles'
import UserInterface from './UserInterface'
import createFileDragAndDrop from './UserInterface/createFileDragAndDrop'
import style from './UserInterface/ItkVtkViewer.module.css'
import toMultiscaleSpatialImage from './IO/toMultiscaleSpatialImage'
import { ConglomerateMultiscaleSpatialImage } from './IO/ConglomerateMultiscaleSpatialImage'
import { isZarr } from './IO/ZarrMultiscaleSpatialImage'
import ImJoyPluginAPI from './ImJoyPluginAPI.js'
import imJoyCodecs from './imJoyCodecs.js'
import packageJson from '../package.json'
const { version } = packageJson
let doNotInitViewers = false
export { version }
export { ImJoyPluginAPI }
export { imJoyCodecs }
export { default as createViewer } from './createViewer'
import * as utils from './utils.js'
export { utils }
// The `UserInterface` is considered an internal implementation detail
// and its interface and behavior may change without changes to the major version.
export { UserInterface }
/** Returns a Promise that revolves with the Viewer created the files. */
export function createViewerFromLocalFiles(container) {
doNotInitViewers = true
return createFileDragAndDrop(container, processFiles)
}
export async function createViewerFromFiles(el, files, use2D = false) {
return processFiles(el, { files: files, use2D })
}
async function makeImage({ image, progressCallback, isLabelImage = false }) {
if (!image) return null
const imageUrlObj = new URL(image, document.location)
if (isZarr(image)) {
return toMultiscaleSpatialImage(imageUrlObj, isLabelImage)
}
const result = await readImageArrayBuffer(
null,
await fetchBinaryContent(imageUrlObj, progressCallback),
imageUrlObj.pathname.split('/').pop()
)
result.webWorker.terminate()
return toMultiscaleSpatialImage(result.image, isLabelImage)
}
async function parseImageArg(image, progressCallback) {
if (!image) return null
const images = await Promise.all(
image.split(',').map(image => makeImage({ image, progressCallback }))
)
return images.length > 1
? new ConglomerateMultiscaleSpatialImage(images)
: images[0]
}
export async function createViewerFromUrl(
el,
{
files = [],
image,
labelImage,
fixedImage,
config,
labelImageNames = null,
rotate = true,
use2D = false,
...rest
}
) {
UserInterface.emptyContainer(el)
const progressCallback = UserInterface.createLoadingProgress(el)
let fetchedImage
const fileObjects = []
for (const url of files) {
const urlObj = new URL(url, document.location)
if (isZarr(url)) {
fetchedImage = await toMultiscaleSpatialImage(urlObj)
} else {
const arrayBuffer = await fetchBinaryContent(urlObj, progressCallback)
fileObjects.push(
new File([new Blob([arrayBuffer])], urlObj.pathname.split('/').pop())
)
}
}
// No image in files? Check image arg.
fetchedImage = fetchedImage ?? (await parseImageArg(image, progressCallback))
const labelImageObject = await makeImage({
image: labelImage,
progressCallback,
isLabelImage: true,
})
const fixedImageObject = await makeImage({
image: fixedImage,
progressCallback,
})
let viewerConfig = null
if (config) {
const response = await axios.get(config, {
responseType: 'json',
})
viewerConfig = response.data
}
let labelImageNameObject = null
if (labelImageNames) {
labelImageNameObject = await fetchJsonContent(labelImageNames)
}
return processFiles(el, {
files: fileObjects,
image: fetchedImage,
labelImage: labelImageObject,
fixedImage: fixedImageObject,
config: viewerConfig,
labelImageNames: labelImageNameObject,
rotate,
use2D,
...rest,
})
}
const parseBoolean = datasetValue =>
datasetValue !== undefined ? datasetValue.toLowerCase() === 'true' : undefined
export function initializeEmbeddedViewers() {
if (doNotInitViewers) {
return
}
const viewers = document.querySelectorAll('.itk-vtk-viewer')
let count = viewers.length
while (count--) {
const el = viewers[count]
if (!el.dataset.loaded) {
el.dataset.loaded = true
// Apply size to container
const [width, height] = (el.dataset.viewport || '500x500').split('x')
el.style.position = 'relative'
el.style.width = Number.isFinite(Number(width)) ? `${width}px` : width
el.style.height = Number.isFinite(Number(height)) ? `${height}px` : height
const files = el.dataset.url.split(',')
createViewerFromUrl(el, {
files,
use2D: parseBoolean(el.dataset.use2d),
rotate: parseBoolean(el.dataset.rotate),
}).then(viewer => {
// Background color handling
if (el.dataset.backgroundColor) {
const color = el.dataset.backgroundColor
const bgColor = [
color.slice(0, 2),
color.slice(2, 4),
color.slice(4, 6),
].map(v => parseInt(v, 16) / 255)
viewer.setBackgroundColor(bgColor)
}
viewer.setUICollapsed(true)
viewer.render()
el.dataset.viewer = viewer
})
}
}
}
function createCompareOptions(userParams) {
if (userParams.compare) {
const options = Object.fromEntries(
['pattern', 'swapImageOrder', 'checkerboard', 'imageMix']
.map(key => [key, userParams[key]])
.filter(([, value]) => value)
)
options.method = userParams.compare
return options
}
return undefined
}
export function processURLParameters(container, addOnParameters = {}) {
const userParams = Object.assign(
{},
vtkURLExtract.extractURLParameters(),
addOnParameters
)
if (userParams.gradientOpacity && isNaN(userParams.gradientOpacity))
throw new Error('gradientOpacity URL paramter is not a number')
const myContainer = UserInterface.getRootContainer(container)
if (userParams.fullscreen) {
myContainer.classList.add(style.fullscreenContainer)
}
const files = userParams.fileToLoad?.split(',') ?? []
if (files.length || userParams.image || userParams.labelImage) {
return createViewerFromUrl(myContainer, {
files,
image: userParams.image,
labelImage: userParams.labelImage,
config: userParams.config,
labelImageNames: userParams.labelImageNames,
rotate: userParams.rotate ?? true,
use2D: !!userParams.use2D,
gradientOpacity: userParams.gradientOpacity,
fixedImage: userParams.fixedImage,
compare: createCompareOptions(userParams),
})
}
return null
}
// Ensure processing of embedded viewers
setTimeout(initializeEmbeddedViewers, 100)