Javascript 将来自耦合显微镜/光谱学的图像和数据缝合到Photoshop或R中的全景图像中
我有一组图像和X射线数据,这些数据来自耦合扫描电子显微镜和能量色散光谱学。我的问题是: 我像这样拍摄了岩石表面的横断面图(紫色框描绘了横断面区域): 我想要非常高的分辨率,所以我使用了7幅放大3000倍的图像,并用Photoshop中的photomerge脚本将它们缝合在一起。下面是一个单独图像的示例: 及其在照相图像横断面中的位置: 在这7个位置中的每个位置,我还收集了X射线数据,这些数据为检测到的每个元素生成元素图,并将其写入TIFF。我还想将每个元素贴图TIFF缝合在一起,以便将其覆盖在岩石的合并横断面图像上。这就是我想要的结果: 问题是元素贴图中没有足够的特征,无法将它们与photomerge缝合在一起。它基本上是二进制的——如果检测到元素,像素就是某种颜色(比如在我的示例图像中,红色代表铁,黄色代表硫),或者如果没有检测到元素,像素就是黑色。您可以看到大部分元素贴图都是黑色的 我现在有约20个横断面x 7个图像,每个x 10个元素产生约1400个图像,这些图像需要放在一起,因此需要自动化 我的想法是用photomerge将岩石图像拼接在一起。photomerge的输出是一个智能对象,其中每个图像都是一个层。然后,我将使用一个脚本来获取照片增强图像对象中7个图像的左上角坐标、宽度和高度。然后,我会将这些属性放置并分配给7幅图像的每个对应元素贴图,以生成“合并”元素贴图以覆盖在图像上。我自己也曾尝试过,但我不精通javascript,也无法理解PhotoshopAPIJavascript 将来自耦合显微镜/光谱学的图像和数据缝合到Photoshop或R中的全景图像中,javascript,r,raster,photoshop,extendscript,Javascript,R,Raster,Photoshop,Extendscript,我有一组图像和X射线数据,这些数据来自耦合扫描电子显微镜和能量色散光谱学。我的问题是: 我像这样拍摄了岩石表面的横断面图(紫色框描绘了横断面区域): 我想要非常高的分辨率,所以我使用了7幅放大3000倍的图像,并用Photoshop中的photomerge脚本将它们缝合在一起。下面是一个单独图像的示例: 及其在照相图像横断面中的位置: 在这7个位置中的每个位置,我还收集了X射线数据,这些数据为检测到的每个元素生成元素图,并将其写入TIFF。我还想将每个元素贴图TIFF缝合在一起,以便将其覆
我在Github上上传了一个示例数据集。从左至右为7个横断面位置:-2、-1、0、1、2、3、4。有岩石的图像和每个位置的元素数据的子目录。我不知道Photoshop或R,也不知道JavaScript:
const names = { // map from directory names to patterns (where "#" stands for position index) of names of images therein
"SEM_images" : "pos# image.tif",
"Al" : "Al Kα1 pos# map data.tif",
"Ba" : "Ba Lα1 pos# map data.tif",
"C" : "C Kα1_2 pos# map data.tif",
"Ca" : "Ca Kα1 pos# map data.tif",
"Fe" : "Fe Kα1 pos# map data.tif",
"Hg" : "Hg Lα1 pos# map data.tif",
"Ir" : "Ir Lα1 pos# map data.tif",
"K" : "K Kα1 pos# map data.tif",
"Mg" : "Mg Kα1_2 pos# map data.tif",
"Mn" : "Mn Kα1 pos# map data.tif",
"Na" : "Na Kα1_2 pos# map data.tif",
"O" : "O Kα1 pos# map data.tif",
"Os" : "Os Lα1 pos# map data.tif",
"P" : "P Kα1 pos# map data.tif",
"S" : "S Kα1 pos# map data.tif",
"Si" : "Si Kα1 pos# map data.tif",
"Ti" : "Ti Kα1 pos# map data.tif"
}
const SCALE = 1/10 // scale of output images
const OVERLAP = 1.0 // minimum *tested* (horizontal) overlap of images relative to their width
const H_BOX = 0.1 // height of comparison box relative to height of images
const W_BOX = 0.1 // width of comparison box relative to width of images
const ADJUSTMENT = 0 // (vertical) adjustment of comparison box [pixels]
/* Merge images given:
* dataset - dataset address as String
* directory - directory name for images as String
* pattern - pattern (where "#" stands for position index) of names of images in directory
* pos_min - minimum position index of images as Number
* pos_max - maximum position index of images as Number
*/
function Merge(dataset, directory, pos_min, pos_max) {
if (dataset[dataset.length - 1] != "/") dataset += "/"
const images = []
for (let pos = pos_min; pos <= pos_max; ++pos) (images[pos - pos_min] = new Image).src = dataset + directory + "/" + names[directory].replace("#", pos)
merge(images, dataset, pos_min, pos_max)
}
function Laplacian(imagedata) { // 5-point stencil approximation
const data = imagedata.data
const L = data.length/4
const grayscale = new Float32Array(L)
for (let i = 0; i < L; ++i) {
const I = 4*i
grayscale[i] = (data[I ] + data[I + 1] + data[I + 2])/3
}
const Laplacian = new Float32Array(L)
//const H = imagedata.height
const Hm1 = imagedata.height - 1
const W = imagedata.width
const Wm1 = W - 1
for (let r = 1; r < Hm1; ++r) {
const R = r*W
for (let c = 1; c < Wm1; ++c) {
const i = R + c
Laplacian[i] = grayscale[i - W] + grayscale[i + W] + grayscale[i - 1] + grayscale[i + 1] - 4*grayscale[i]
}
}
for (let c = 1; c < Wm1; ++c) {
//const i = c
Laplacian[c] = grayscale[c + W] + grayscale[c - 1] + grayscale[c + 1] - 4*grayscale[c]
}
for (let c = 1; c < Wm1; ++c) {
const i = Hm1*W + c
Laplacian[i] = grayscale[i - W] + grayscale[i - 1] + grayscale[i + 1] - 4*grayscale[i]
}
for (let r = 1; r < Hm1; ++r) {
const i = r*W
Laplacian[i] = grayscale[i - W] + grayscale[i + W] + grayscale[i + 1] - 4*grayscale[i]
}
for (let r = 1; r < Hm1; ++r) {
const i = r*W + Wm1
Laplacian[i] = grayscale[i - W] + grayscale[i + W] + grayscale[i - 1] - 4*grayscale[i]
}
{
const Lm1 = L - 1
const LmW = L - W
Laplacian[0 ] = grayscale[W ] + grayscale[1 ] - 4*grayscale[0 ]
Laplacian[W ] = grayscale[2*W ] + grayscale[Wm1 ] - 4*grayscale[W ]
Laplacian[LmW] = grayscale[LmW - W] + grayscale[LmW + 1] - 4*grayscale[LmW]
Laplacian[Lm1] = grayscale[Lm1 - W] + grayscale[Lm1 - 1] - 4*grayscale[Lm1]
}
return Laplacian
}
function merge(images, dataset, pos_min, pos_max) {
for (const image of images) if (!image.complete) {
setTimeout(merge, 1000, images, dataset, pos_min, pos_max) // wait 1000ms = 1s
return
}
let Row, Col
const Coords = [[Row = 0, Col = 0]]
let index = 0
let image = images[index]
const H = image.naturalHeight
const W = image.naturalWidth
if (W*H == 0) return []
const canvas = document.createElement("canvas")
canvas.height = H
canvas.width = W
const context = canvas.getContext('2d')
context.drawImage(image, 0, 0)
let prev = Laplacian(context.getImageData(0, 0, W, H))
const length = images.length
const h = Math.round(H_BOX*H)
const Hmh = H - h
const w = Math.round(W_BOX*W)
const o = Math.max(Math.round((1 - OVERLAP)*W), w)
const Wmw = W - w
const row_offset = Math.round(Hmh/2) + ADJUSTMENT
const offset = row_offset*W
for (++index; index < length; ++index) {
image = images[index]
if (image.naturalHeight != H || image.naturalWidth != W) alert("Dimension mismatch: " + image.src)
context.drawImage(image, 0, 0)
const curr = Laplacian(context.getImageData(0, 0, W, H))
let max = -1
let row, col
for (let r = 0; r < Hmh; ++r) {
const R = r*W
for (let c = o; c < Wmw; ++c) {
let m = 0
for (let i = 0; i < h; ++i) {
const I = i*W
const K = R + I + c
const k = offset + I
for (let j = 0; j < w; ++j) if (prev[K + j]*curr[k + j] > 0) ++m
}
if (m > max) {
max = m
row = r
col = c
}
}
}
Coords[index] = [(Row += row - row_offset)/H, (Col += col)/W]
prev = curr
}
Stitch(dataset, pos_min, pos_max, Coords)
}
function Stitch(dataset, pos_min, pos_max, Coords) {
if (dataset[dataset.length - 1] != "/") dataset += "/"
document.body.appendChild(document.createElement("h1")).innerText = `${dataset} :[${pos_min},${pos_max}] @${JSON.stringify(Coords)}`
const tasks = []
for (const directory in names) {
document.body.appendChild(document.createElement("h2")).innerText = directory
const images = []
for (let pos = pos_min; pos <= pos_max; ++pos) (images[pos - pos_min] = new Image).src = dataset + directory + "/" + names[directory].replace("#", pos)
const target = document.body.appendChild(document.createElement("img"))
target.height = 0
target.width = 0
tasks.push([images, target])
}
process(tasks, Coords)
}
const ROW = 0
const COL = 1
function stitch(images, Coords) {
let image
let index
for (index in images) {
image = images[index]
if (image.naturalHeight != 0 && image.naturalWidth != 0) break
}
const H = image.naturalHeight
const W = image.naturalWidth
const canvas = document.createElement("canvas")
let r_min = 0
let r_max = 0
let c_min = 0
let c_max = 0
for (coords of Coords) {
const r = coords[ROW]
const c = coords[COL]
if (r < r_min) r_min = r
if (r > r_max) r_max = r
if (c < c_min) c_min = c
if (c > c_max) c_max = c
}
canvas.height = (r_max - r_min + 1)*H
canvas.width = (c_max - c_min + 1)*W
const context = canvas.getContext('2d')
if (context == null) {
let list = ""
for (const image of images) list += "\n- " + image.src
alert("Too large: stitching area required for:" + list)
return
}
let coords = Coords[index]
let row = (coords[ROW] - r_min)*H
let col = (coords[COL] - c_min)*W
context.drawImage(image, col, row)
const length = images.length
for (++index; index < length; ++index) {
image = images[index]
if (image.naturalHeight == 0 || image.naturalWidth == 0) continue
if (image.naturalHeight != H || image.naturalWidth != W) alert("Dimension mismatch: " + image.src)
coords = Coords[index]
row = coords[ROW]*H
col = coords[COL]*W
context.drawImage(image, col, row)
}
return canvas.toDataURL()
}
function process(tasks, Coords) {
const task = tasks.shift()
const images = task[0]
for (const image of images) if (!image.complete) {
tasks.push(task)
setTimeout(process, 1000, tasks, Coords) // wait 1000ms = 1s
return
}
const target = task[1]
target.src = stitch(images, Coords)
target.onload = function () {
this.height = SCALE*this.naturalHeight
this.width = SCALE*this.naturalWidth
this.style = "border: solid black 1px"
}
if (tasks.length > 0) process(tasks, Coords)
}
带有Fe覆盖层的SEM_图像示例:
嗨@Caitlin,我不太明白你到底需要什么?包含所有图层的Photoshop文件?或者一组导出的图像(每个图像都位于正确的位置)?@Caitlin LGTM!!好的,我只是想问一下,因为有人可能知道一种不用Photoshop就能实现的方法。但是如果你最后需要Photoshop中的所有文件,当然必须在Photoshop中完成。在过去编写了很多ExtendScript脚本之后,我不得不说,您所要求的可能是在堆栈溢出问题上要解决的一个重要任务。你基本上是要求一个完整的脚本,通常需要雇用一个人为你。由于需要按名称加载文件,请将其排列在正确的图层上,并按坐标等进行定位。这很复杂。@mdomino啊,为了方便起见,我希望有一个photoshop文件输出,但导出的图像肯定也能工作!这里有另一篇关于StackOverflow的博文,很遗憾,它将StackOverflow与@Caitlin听起来你是在专门寻找解决这个问题的Photoshop脚本解决方案?你可能需要禁用跨源限制。
Merge("https://raw.githubusercontent.com/CaitlinCasar/dataStitcher/master/example_dataset/", "SEM_images", -2, 4)