Объединение изображений и данных совместной микроскопии/спектроскопии в панораму в Photoshop или R.

У меня есть набор изображений и рентгеновских данных, полученных с помощью совместной сканирующей электронной микроскопии и энергодисперсионной спектроскопии. Вот моя проблема:

Я изобразил разрезы поверхности скалы следующим образом (фиолетовый прямоугольник обводит область разреза):

введите описание изображения здесь

Мне нужно было действительно высокое разрешение, поэтому я сделал это, используя 7 изображений с 3000-кратным увеличением и склеил их вместе с помощью скрипта photomerge в Photoshop. Вот пример отдельного изображения:

введите описание изображения здесь

И его положение на разрезе фотообъединенного изображения:

введите описание изображения здесь

В каждом из этих 7 мест я также собирал рентгеновские данные, которые генерируют карту элементов для каждого обнаруженного элемента и записывают ее в файл TIFF. Я также хочу сшить каждую карту элементов в формате TIFF, чтобы я мог наложить ее на объединенное изображение разреза скалы. Это результат, который я хочу:

введите описание изображения здесь

Проблема в том, что в картах элементов недостаточно функций, чтобы их можно было сшить вместе с помощью photomerge. В основном это двоичный код — если элемент обнаружен, пиксель имеет определенный цвет (например, красный для железа или желтый для серы в моих примерах изображений) или черный, если элемент не обнаружен. Вы можете видеть, что большие части карт элементов в основном черные.

Теперь у меня есть ~20 разрезов x 7 изображений каждый x ~10 элементов, что приводит к ~1400 изображениям, которые необходимо соединить, отсюда и потребность в автоматизации.

Моя идея заключалась в том, чтобы склеить изображения камней с помощью фотомержа. Результатом работы photomerge является смарт-объект, в котором каждое изображение представляет собой слой. Затем я бы использовал скрипт, чтобы получить координаты верхнего левого угла, ширину и высоту для каждого из 7 изображений в объекте фотообъединенного изображения. Затем я бы поместил и присвоил эти свойства каждой из соответствующих карт элементов для 7 изображений, чтобы создать «объединенные» карты элементов для наложения на изображение. Я пытался работать над этим сам, но я не разбираюсь в javascript и не мог понять Photoshop API.

Я загрузил пример набора данных на Github здесь. 7 позиций трансект слева направо: -2, -1, 0, 1, 2, 3, 4. Имеются изображения скалы и подкаталоги с данными элементов для каждой позиции.


person Caitlin    schedule 13.03.2020    source источник
comment
Привет @Caitlin, я не совсем понимаю, что именно тебе нужно в результате? Файл Photoshop со всеми слоями? Или набор экспортированных изображений (каждое изображение расположено в правильном месте)?   -  person mdomino    schedule 13.03.2020
comment
@Кейтлин ЛГТМ!!   -  person Nosniw    schedule 13.03.2020
comment
Привет @mdomino, спасибо за ваш комментарий и извините, что не ясно выразился! Мне нужен файл Photoshop со всеми слоями, где каждое изображение расположено в правильном месте. Я вручную вводил координаты и т. д. для каждого изображения элемента, чтобы они соответствовали координатам изображений горных пород, а затем создавал группы из слоев, т.е. у меня есть группа изображений железа (Fe), группа изображений серы (S). , и группа каменных изображений. Дайте мне знать, если я могу уточнить дальше!   -  person Caitlin    schedule 13.03.2020
comment
Хорошо, просто хотел спросить, потому что кто-то может знать, как это сделать без Photoshop. Но если вам нужны все файлы в Photoshop в конце, это, конечно, нужно сделать в Photoshop. Написав в прошлом множество сценариев ExtendScript, я должен сказать, что то, что вы просите, вероятно, является слишком большой задачей, которую нужно решить здесь, в Stack Overflow. Вы в основном просите весь сценарий, который обычно требует найма кого-то для вас. Так как вам нужно будет загружать файлы по имени, располагать их на нужных слоях, располагать по координатам и так далее. Это довольно сложно.   -  person mdomino    schedule 13.03.2020
comment
@mdomino ах, я надеялся на вывод файла фотошопа для удобства, но экспортированные изображения определенно сработают!   -  person Caitlin    schedule 13.03.2020
comment
Спасибо @Codebling! Причина, по которой я хочу использовать Photoshop, заключается в функции фотообъединения, потому что я могу объединять свои изображения в панораму. Все, что мне действительно нужно, это координаты XY из слоев, созданных этой функцией, например: community.adobe.com/t5/photoshop/ Однако, когда я пытаюсь запустить этот скрипт, я получаю сообщение об ошибке, которое вы можете увидеть в моем комментарии к этой ссылке. В противном случае у меня есть обходной путь в R для остальных!   -  person Caitlin    schedule 17.03.2020
comment
Возможно, @mdomino знает, почему этот скрипт Photoshop выдает ошибку? Я поместил проблему с ошибкой в ​​комментарий внизу страницы: community.adobe.com/t5/photoshop/   -  person Caitlin    schedule 17.03.2020
comment
@Caitlin Я понимаю, но если бы у кого-то был другой альтернативный метод объединения слоев, был бы он приемлем?   -  person Codebling    schedule 17.03.2020
comment
@Codebling определенно! Я пытался возиться с OpenCV в Python и нашел плагин для ImageJ, но ссылка для скачивания не работала. По-видимому, в R это невозможно сделать, я искал в Интернете отовсюду! Но я смог воспроизвести панораму в R из изображений, преобразовав их в растр, а затем объединив растры с заданными вручную координатами, которые я получил из Photoshop. Было бы здорово полностью отказаться от Photoshop!   -  person Caitlin    schedule 17.03.2020
comment
Я добавил свой скрипт, используя свой пример набора данных в моем репозитории github здесь: github.com/CaitlinCasar/dataStitcher   -  person Caitlin    schedule 17.03.2020
comment
Вы решили свою проблему?   -  person greg-tumolo    schedule 10.07.2020
comment
@greg-tumolo Я склеил панораму в Photoshop, используя изображения SEM, а затем получил координаты для каждого изображения в панораме. Затем я использовал эти координаты, чтобы сшить изображения соответствующих элементов в панорамные растры в R, а затем сложить их в растровые блоки. Буду рад услышать ваши идеи, если вы знаете, как сшивать изображения в панорамы в R!   -  person Caitlin    schedule 10.07.2020
comment
Да, объединенные изображения РЭМ/элементов были получены из одного и того же поля зрения. И да, соседние изображения обычно смещаются как по оси X, так и по оси Y. Из-за дрейфа образца при таком увеличении в большинстве случаев я не мог смещаться только в направлениях X или Y.   -  person Caitlin    schedule 11.07.2020
comment
Упс... Я попытался удалить/перепостить свой предыдущий комментарий с 10 (элементами) вместо 3 и потерял его. Вы писали, что я потом использовал эти координаты, чтобы сшивать изображения соответствующих элементов в панорамные растры в R.... Так вы уже не умеете сшивать изображения в панорамы в R?   -  person greg-tumolo    schedule 11.07.2020
comment
Верно, мне пришлось делать панорамную сшивку изображений РЭМ в Photoshop с помощью функции фотообъединения. Я не смог найти никаких пакетов для этого в R, а написание этого с нуля в R стоило бы мне слишком много времени.   -  person Caitlin    schedule 11.07.2020
comment
Когда я перечитывал эти комментарии, я заметил, что мой ответ не удовлетворяет ваше желание. Я реализовал альтернативу фотомержу; однако альтернатива, вероятно, слишком медленная. Сколько времени занимает photomerge, чтобы объединить 7 фотографий SEM?   -  person greg-tumolo    schedule 13.07.2020
comment
Фотообъединение 7 изображений SEM занимает менее 1 минуты. Процесс сшивания соответствующих растровых изображений элементов в панорамные растры в R, а затем объединение их в растровый блок занимает около 10 минут для заданного набора изображений SEM/элементов. Как долго работал ваш скрипт?   -  person Caitlin    schedule 13.07.2020
comment
Теперь это занимает ~8 минут с OVERLAP = 1.0 (как в моем коде ответа). Если вы уменьшите OVERLAP, скажем, до 0,5, он будет работать быстрее; однако вы представите (другой) режим отказа: как видно из примера вывода (в нижней части моего ответа), некоторые изображения перекрываются более чем на 40%!   -  person greg-tumolo    schedule 16.07.2020
comment
проверю на моем конце как можно скорее! Большое спасибо за то, что справились с этим! :D   -  person Caitlin    schedule 16.07.2020
comment
@DavidArenburg Я не знаю, но вы можете увидеть мое решение здесь: github. com/CaitlinCasar/dataStitcher/blob/master/dataStitchR.R   -  person Caitlin    schedule 03.08.2020
comment
Я надеялся, что кто-нибудь переведет для вас мой код с JavaScript на R. Увы! Вы тестировали мой код как есть?   -  person greg-tumolo    schedule 09.08.2020


Ответы (1)


Я не знаю 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)

Пример SEM_images с наложением Fe: SEM_images с наложением Fe

person greg-tumolo    schedule 13.07.2020
comment
Возможно, вам придется отключить ограничения на кросс-происхождение. - person greg-tumolo; 13.07.2020