Анализ экспортированных аннотаций .geojson из Qupath в SpatStat в R

Я новичок в программировании, имею опыт работы в области наук о жизни/биологии.

Я стремлюсь проанализировать совместную локализацию лейкозных клеток в областях кровеносных сосудов и адипоцитов.

Я экспортировал местоположения ячеек в виде точек с отметками, обозначающими либо положительный результат CKIT, либо положительный результат только DAPI.

Я экспортировал точечные узоры клеток в Qupath и аннотации расположения структур, окрашенных кровеносными сосудами/эндомуцином, и структур, окрашенных адипоцитами/перилипином, в виде файлов .geojson из Qupath.

Файл .geojson с экспортированными аннотациями Qupath

Окно должно представлять собой квадратную коробку, из которой я взял образец ткани.

Я приложил изображение примера того, как я получил точки и аннотации от qupath. Аннотации, окрашенные эндомуцином и перилипином, имеют белый и зеленый цвета соответственно и идентифицируются с помощью классификатора пикселей qupath, который я обучил. Точки — это ячейки.

Точки ячеек Qupath и аннотации

Я хотел бы, чтобы окно представляло собой квадратную внешнюю рамку, точки - в виде многотипного шаблона точек, а аннотации для окрашивания перилипином и эндомуцином - выступали в качестве пространственных ковариат для анализа шаблона точек. Однако как мне лучше всего смоделировать эти последние аннотации? После импорта их как .geojson в R и преобразования в формат SF некоторые края соприкасаются при проверке с помощью st_is_valid.

Должен ли я преобразовать это в пиксельное изображение или бинарную маску? Могут ли они остаться полигонами и быть преобразованы в объекты OWIN? (например, данные о золоте Мерчисона в замечательной книге Бэддели по анализу пространственных точечных структур). Мне не удалось преобразовать эти полигоны в объекты OWIN.

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

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

library(sf)
library(spatstat)
library(spatstat.geom)

setwd("~/RStudio/ImageAnalysis")

pt_geojson <- "Veh Tibia - Proximal Tibia.geojson"
annotations_sf <- st_read(pt_geojson)

# Check resulting sf object

head(annotations_sf)

annotations_transform <- st_transform(annotations_sf, crs = 26717)

annotations_owin <- as.owin(annotations_transform)

plot(annotations_owin)

К сожалению, это просто генерирует следующую ошибку:

Error in if (!all(st_dimension(W) == 2)) stop("as.owin.sfc needs polygonal geometries") : 
  missing value where TRUE/FALSE needed

Я добавил следующий код:

# Check for invalid geometries
invalid_geometries <- st_is_valid(annotations_sf, reason = TRUE)
print(invalid_geometries)

# Fix invalid geometries
annotations_sf <- st_make_valid(annotations_sf)

# Verify geometries are now valid
valid_geometries <- st_is_valid(annotations_sf, reason = TRUE)
print(valid_geometries)

Это привело к следующему:

> # Check for invalid geometries
> invalid_geometries <- st_is_valid(annotations_sf, reason = TRUE)
Warning messages:
1: In st_is_longlat(x) :
  bounding box has potentially an invalid value range for longlat data
2: In st_is_longlat(x) :
  bounding box has potentially an invalid value range for longlat data
> print(invalid_geometries)
 [1] "Valid Geometry"          "Edge 2 crosses edge 4"  
 [3] "Edge 16 crosses edge 20" "Valid Geometry"         
 [5] "Valid Geometry"          "Edge 14 crosses edge 20"
 [7] "Valid Geometry"          "Valid Geometry"         
 [9] "Edge 0 crosses edge 8"   "Edge 16 crosses edge 18"
[11] "Edge 6 crosses edge 8"   "Valid Geometry"         
[13] "Valid Geometry"          "Valid Geometry"         
[15] "Edge 0 crosses edge 26"  "Edge 4 crosses edge 10" 
[17] "Valid Geometry"          "Valid Geometry"         
[19] "Edge 12 crosses edge 18" "Valid Geometry"         
[21] "Edge 0 crosses edge 10"  "Valid Geometry"         
[23] "Valid Geometry"          "Valid Geometry"         
[25] "Edge 10 crosses edge 12" "Valid Geometry"         
[27] "Valid Geometry"          "Valid Geometry"         
[29] "Valid Geometry"          "Edge 4 crosses edge 24" 
> 
> # Fix invalid geometries
> annotations_sf <- st_make_valid(annotations_sf)
Warning message:
In st_is_longlat(x) :
  bounding box has potentially an invalid value range for longlat data
> 
> # Verify geometries are now valid
> valid_geometries <- st_is_valid(annotations_sf, reason = TRUE)
> print(valid_geometries)
 [1] "Valid Geometry"          "Edge 2 crosses edge 4"  
 [3] "Edge 16 crosses edge 20" "Valid Geometry"         
 [5] "Valid Geometry"          "Edge 14 crosses edge 20"
 [7] "Valid Geometry"          "Valid Geometry"         
 [9] "Edge 0 crosses edge 8"   "Edge 16 crosses edge 18"
[11] "Edge 6 crosses edge 8"   "Valid Geometry"         
[13] "Valid Geometry"          "Valid Geometry"         
[15] "Edge 0 crosses edge 26"  "Edge 4 crosses edge 10" 
[17] "Valid Geometry"          "Valid Geometry"         
[19] "Edge 12 crosses edge 18" "Valid Geometry"         
[21] "Edge 0 crosses edge 10"  "Valid Geometry"         
[23] "Valid Geometry"          "Valid Geometry"         
[25] "Edge 10 crosses edge 12" "Valid Geometry"         
[27] "Valid Geometry"          "Valid Geometry"         
[29] "Valid Geometry"          "Edge 4 crosses edge 24"

И последующий повторный запуск сюжета создал этот ужасно объединенный сюжет всех аннотаций.

Неудачный сюжет полигонального окна

Любая помощь будет очень признательна! Спасибо :))

Редактировать

Я думаю, что нашел решение, но не уверен, вызовет ли оно проблемы в дальнейшем. Я пришел к выводу, что экспорт аннотаций в виде отдельных объединенных полигонов избавляет от многих проблем и, похоже, работает. Я также изолировал ограничивающую рамку из исходного экспорта .geojson, но в последующих попытках я упрощу это, просто экспортировав ограничивающую рамку непосредственно из qupath.

#Load required packages

library(sf)
library(spatstat)
library(spatstat.geom)

#Set working directory (CHANGE AS REQUIRED)

setwd("~/RStudio/ImageAnalysis")

#Import .geojson files, generated by Qupath

pt_geojson <- "Veh Tibia - Proximal Tibia.geojson"
pt_peri <- "pt_peri.geojson"
pt_endo <- "pt_endo.geojson"

#Generate sf data

ann_sf <- st_read(pt_geojson)
ann_peri <- st_read(pt_peri)
ann_endo <- st_read(pt_endo)

#Generate outer bounding box

bbox <- st_bbox(ann_sf)
bbox <- st_as_sfc(bbox)
bbox <- st_bbox(bbox[[1]])

xmin <- bbox[[1]]
xmax <- bbox[[3]]
ymin <- bbox[[2]]
ymax <- bbox[[4]]

W <- owin(xrange = c(xmin, xmax), yrange = c(ymin, ymax))

# Generate adipocyte and vascular annotations

peri <- ann_peri$geometry[[1]]
peri <- as.owin(peri)

endo <- ann_endo$geometry[[1]]
endo <- as.owin(endo)

com <- solist(W, endo, peri, pt_ppp)

plot(W, main = "Adipocytes + Vasculature")
plot(peri, col = "green", add = TRUE)
plot(endo, col = "red", add = TRUE)
plot(pt_ppp, cols = c("yellow3","skyblue1"), chars = c(15,16), 
     use.marks = TRUE, legend = TRUE, cex = 0.5, 
     main = "Proximal Tibia Cell Point Pattern", add = TRUE)
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
82
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Первая проблема заключается в том, что файл geojson не имеет географических координат WGS84, как неявно предполагает st_read(). Это означает, что компьютер считает, что координаты находятся на сфере, и вам нужно преобразовать их в плоскую систему координат, чтобы иметь возможность передать их в spatstat. Это делается с помощью функции st_transform(), которая разрушает геометрию всего. Чтобы избежать неправильного предположения о WGS84 crs, вы можете установить crs = NA в st_read(). Ниже приведен рабочий пример.

library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1; sf_use_s2() is TRUE
library(spatstat)
#> Loading required package: spatstat.data
#> Loading required package: spatstat.univar
#> spatstat.univar 2.0-3.003
#> Loading required package: spatstat.geom
#> spatstat.geom 3.2-9.017
#> Loading required package: spatstat.random
#> spatstat.random 3.2-3.002
#> Loading required package: spatstat.explore
#> Loading required package: nlme
#> spatstat.explore 3.2-7.005
#> Loading required package: spatstat.model
#> Loading required package: rpart
#> spatstat.model 3.2-11.004
#> Loading required package: spatstat.linnet
#> spatstat.linnet 3.1-5.003
#> 
#> spatstat 3.0-8.004 
#> For an introduction to spatstat, type 'beginner'
pt_geojson <- "Veh Tibia - Proximal Tibia.geojson"
annotations_sf <- st_read(pt_geojson, crs = NA)
#> Reading layer `Veh Tibia - Proximal Tibia' from data source 
#>   `/home/rubak/OneDrive/spatstat/experiments/QuPath/Veh Tibia - Proximal Tibia.geojson' 
#>   using driver `GeoJSON'
#> Simple feature collection with 30 features and 6 fields
#> Geometry type: POLYGON
#> Dimension:     XY
#> Bounding box:  xmin: 31884 ymin: 3236 xmax: 32958 ymax: 4310
#> CRS:           NA

geo <- st_geometry(annotations_sf)
windows <- lapply(geo, as.owin)
df <- st_drop_geometry(annotations_sf)
te <- tess(tiles=windows)
marks(te) <- df

te
#> Tessellation
#> Tiles are windows of general type
#> 30 tiles (irregular windows)
#> Tessellation has 6 columns of marks: 'id', 'objectType', 'classification', 
#> 'isLocked', 'measurements' and 'name'
#> window: rectangle = [31884, 32958] x [3236, 4310] units

plot(te, do.col = FALSE, do.labels = TRUE, main = "")


plot(tiles(te), main = "")

Обратите внимание, что на графике выше плитки имеют разные физические масштабы. Плитка под номером 12 не имеет классификации и соответствует всему окну наблюдения, поэтому, вероятно, разумнее ее не учитывать.

plot(tiles(te)[[12]], main = "")

Если мы его опустим, то получим следующее:

te2 <- tess(tiles=windows[-12])
marks(te2) <- df[-12,]
plot(te2, values = marks(te2)$classification, main = "")

Примечание. Я не тратил время на очистку данных, поэтому оставил метки классификации такими, какие они были в исходных данных. Также: поскольку исходные данные представляют собой пиксели, я думаю, что имело бы смысл работать с двоичными масками вместо полигонов.

Спасибо! Я не рассматривал использование тесселяции в качестве варианта создания окон, так как думал, что существует вероятность того, что в будущем анализе выходные данные Qupath .geojson будут генерировать эндо- и пери-окна, которые могут перекрываться (они не должны, но это возможно). Я предполагаю, что в этом случае функция тесселяции потерпит неудачу? Однако я согласен, что двоичная маска лучше всего подходит для моих данных.

Callum Fernando 19.06.2024 11:02

Другие вопросы по теме