javascript - 在 Photoshop 或 R 中将来自耦合显微镜/光谱学的图像和数据拼接成全景图

标签 javascript r raster photoshop extendscript

我有一组由耦合扫描电子显微镜和能量色散光谱生成的图像和 X 射线数据。这是我的问题:

我像这样对岩石表面的横断面进行了成像(紫色框勾勒出横断面区域):

enter image description here

我想要非常高的分辨率,所以我使用 7 张放大 3000 倍的图像,并在 Photoshop 中使用 photomerge 脚本将它们拼接在一起。以下是单个图像的示例:

enter image description here

及其在光合并图像横断面中的位置:

enter image description here

在这 7 个位置中的每一个,我还收集了 X 射线数据,这些数据为检测到的每个元素生成一个元素图,并将其写入 TIFF。我还想将每个元素图 TIFF 拼接在一起,这样我就可以将它叠加在岩石的合并横断面图像上。这是我想要的结果:

enter image description here

问题是元素映射中没有足够的特征来将它们与 photomerge 拼接在一起。它基本上是二元的——如果检测到一个元素,则像素是某种颜色(如在我的示例图像中,红色代表铁或黄色代表硫),如果未检测到该元素,则为黑色。您可以看到大部分元素贴图大部分是黑色的。

我现在有约 20 个横断面 x 7 个图像,每个 x 约 10 个元素,这导致需要将约 1400 个图像放在一起,因此需要自动化。

我的想法是用 photomerge 将岩石图像拼接在一起。 photomerge 的输出是一个智能对象,其中每个图像都是一个图层。然后,我将使用脚本来获取光合并图像对象中 7 个图像中每一个的左上角坐标、宽度和高度。然后,我会将这些属性放置并分配给 7 个图像的每个相应元素映射,以生成“合并”元素映射以覆盖在图像上。我尝试自己解决这个问题,但我并不精通 javascript,也无法完全理解 Photoshop API。

我在 Github here 上上传了一个示例数据集. 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)
}
要运行,请执行以下操作:
Merge("https://raw.githubusercontent.com/CaitlinCasar/dataStitcher/master/example_dataset/", "SEM_images", -2, 4)
带有 Fe 覆盖的示例 SEM_images:
SEM_images with Fe overlay

关于javascript - 在 Photoshop 或 R 中将来自耦合显微镜/光谱学的图像和数据拼接成全景图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60675149/

相关文章:

r - 为什么在尝试将 CRS 分配给 R 中的栅格时出现错误?

javascript - 如何在JS中装饰querySelector/querySelectorAll

javascript - 将 ES6 javascript 函数代码转换为非 es6 代码

R : Load only once a . 部署的 ShinyApp 中的 RData

r - 制作不同范围的光栅堆栈

python - 在 Python 中根据日期值从多个 NetCDF 文件中提取栅格

javascript - 点击键盘输入上的指定事件 : up, 下、左、右

javascript - 从新对象的属性更新对象的属性,但前提是新对象的属性不是未定义的

r - 在 R 中计算多个数据帧的 z 分数

r - ^ R 中 lm() 中的符号