Diff of /R/conversion.R [000000] .. [413088]

Switch to unified view

a b/R/conversion.R
1
####
2
# Seurat ####
3
####
4
5
#' @param type the spatial data type of Seurat object: "image" or "spatial"
6
#' @param assay_name the assay name
7
#' @param verbose verbose
8
#' @param ... Additional parameter passed to \link{formVoltRon}
9
#'
10
#' @rdname as.VoltRon
11
#' @method as.VoltRon Seurat
12
#'
13
#' @importFrom stringr str_replace str_extract
14
#' @export
15
#'
16
as.VoltRon.Seurat <- function(object, type = c("image", "spatial"), assay_name = NULL, verbose = TRUE, ...){
17
18
  # check Seurat package
19
  if(!requireNamespace('Seurat'))
20
    stop("Please install Seurat package for using Seurat objects!: install.packages('Seurat')")
21
22
  # raw counts
23
  rawdata <- SeuratObject::LayerData(object, assay = Seurat::DefaultAssay(object), layer = "counts")
24
25
  # metadata
26
  metadata <- object@meta.data
27
28
  # embeddings
29
  if(length(object@reductions) > 0){
30
    embeddings_flag <- TRUE
31
    embedding_list <- sapply(object@reductions, Seurat::Embeddings, USE.NAMES = TRUE)
32
  } else {
33
    embeddings_flag <- FALSE
34
  }
35
36
  # image
37
  voltron_list <- list()
38
  spatialobjectlist <- object@images
39
  fov_names <- names(spatialobjectlist)
40
  if(length(spatialobjectlist) > 0){
41
    for(fn in fov_names){
42
43
      # message
44
      if(verbose)
45
        message("Converting FOV: ", fn, " ...")
46
47
      # image object
48
      spatialobject <- spatialobjectlist[[fn]]
49
50
      # cells
51
      cells <- Seurat::Cells(spatialobject)
52
      cells_nopostfix <- gsub("_Assay[0-9]+$", "", cells)
53
54
      # count
55
      cur_rawdata <- as.matrix(rawdata[,cells])
56
      colnames(cur_rawdata) <- cells_nopostfix
57
58
      # metadata
59
      cur_metadata <- metadata[cells,]
60
      rownames(cur_metadata) <- cells_nopostfix
61
62
      # coords
63
      coords <- as.matrix(Seurat::GetTissueCoordinates(spatialobject))[,seq_len(2)]
64
      coords <- apply(coords, 2, as.numeric)
65
      colnames(coords) <- c("x", "y")
66
      rownames(coords) <- cells_nopostfix
67
68
      # form voltron
69
      params <- list()
70
      assay.type <- "cell"
71
      assay_name <- "FOV"
72
      voltron_list[[fn]] <- formVoltRon(data = cur_rawdata, metadata = cur_metadata, coords = coords, main.assay = assay_name, params = params, assay.type = assay.type, sample_name = fn, ...)
73
74
      # embeddings
75
      spatialpoints <- vrSpatialPoints(voltron_list[[fn]])
76
      spatialpoints_nopostfix <- stringr::str_replace(spatialpoints, "_Assay[0-9]+$", "")
77
      spatialpoints_assay <- stringr::str_extract(spatialpoints, "Assay[0-9]+$")
78
      if(embeddings_flag){
79
        for(embed_name in names(embedding_list)){
80
          cur_embedding <- embedding_list[[embed_name]][cells,]
81
          rownames(cur_embedding) <- spatialpoints
82
          vrEmbeddings(voltron_list[[fn]], type = embed_name) <- cur_embedding
83
        }
84
      }
85
    }
86
87
    # merge object
88
    if(length(voltron_list) > 1){
89
      if(verbose)
90
        message("Merging object ...")
91
      vrobject <- merge(voltron_list[[1]], voltron_list[-1]) 
92
    } else {
93
      vrobject <- voltron_list[[1]]
94
    }
95
  } else{
96
    image <- NULL
97
    stop("There are no spatial objects available in this Seurat object")
98
  }
99
100
  return(vrobject)
101
}
102
103
#' as.Seurat
104
#'
105
#' Converting a VoltRon object into a Seurat object
106
#'
107
#' @param object a VoltRon object
108
#' @param cell.assay the name(type) of the cell assay to be converted
109
#' @param molecule.assay the name(type) of the molecule assay to be added to the cell assay in Seurat object
110
#' @param image_key the name (or prefix) of the image(s)
111
#' @param type the spatial data type of Seurat object: "image" or "spatial"
112
#' @param reg if TRUE, registered coordinates will be used
113
#'
114
#' @rdname as.Seurat
115
#'
116
#' @importFrom dplyr bind_cols
117
#' @importFrom stringr str_replace
118
#'
119
#' @export
120
as.Seurat <- function(object, cell.assay = NULL, molecule.assay = NULL, image_key = "fov", type = c("image", "spatial"), reg = FALSE){
121
  
122
  # sample metadata
123
  sample_metadata <- SampleMetadata(object)
124
  
125
  # check Seurat package
126
  if(!requireNamespace('Seurat'))
127
    stop("Please install Seurat package for using Seurat objects")
128
  
129
  # check the number of assays
130
  if(is.null(cell.assay)){
131
    if(length(unique(sample_metadata[["Assay"]])) > 1){
132
      stop("You can only convert a single VoltRon assay into a Seurat object!")
133
    } else {
134
      cell.assay <- sample_metadata[["Assay"]]
135
    }
136
  } else {
137
    vrMainAssay(object) <- cell.assay
138
  }
139
  
140
  # check the number of assays
141
  if(unique(vrAssayTypes(object, assay = cell.assay)) %in% c("spot","ROI")) {
142
    stop("Conversion of Spot or ROI assays into Seurat is not yet permitted!")
143
  }
144
  
145
  # data
146
  data <- vrData(object, assay = cell.assay, norm = FALSE)
147
  
148
  # metadata
149
  metadata <- Metadata(object, assay = cell.assay)
150
  
151
  # Seurat object
152
  seu <- Seurat::CreateSeuratObject(counts = data, meta.data = metadata, assay = cell.assay)
153
  
154
  # add embeddings
155
  if(length(vrEmbeddingNames(object)) > 0){
156
    for(embd in vrEmbeddingNames(object)){
157
      embd_data <- vrEmbeddings(object, type = embd)
158
      colnames(embd_data) <- paste0(embd, seq_len(ncol(embd_data)))
159
      seu[[embd]] <- Seurat::CreateDimReducObject(embd_data, key = paste0(embd, "_"), assay = Seurat::DefaultAssay(seu))
160
    }
161
  }
162
  
163
  # get image objects for each assay
164
  for(assy in vrAssayNames(object)){
165
    assay_object <- object[[assy]]
166
    if(type == "image"){
167
      coords <- vrCoordinates(assay_object, reg = reg)
168
      image.data <- list(centroids = SeuratObject::CreateCentroids(coords[,c("x", "y")]))
169
      if(!is.null(molecule.assay)){
170
        assay_metadata <- sample_metadata[assy,]
171
        molecule.assay.id <- rownames(sample_metadata)[sample_metadata$Assay == molecule.assay & (assay_metadata$Layer == sample_metadata$Layer & assay_metadata$Sample == sample_metadata$Sample)]
172
        if(length(molecule.assay.id) > 0){
173
          molecules_metadata <- Metadata(object, assay = molecule.assay.id)
174
          molecules_coords <- vrCoordinates(object, assay = molecule.assay.id, reg = reg)
175
          molecules <- dplyr::bind_cols(molecules_metadata, molecules_coords)
176
          rownames(molecules) <- stringr::str_replace(rownames(molecules), pattern = molecule.assay.id, replacement = assy)
177
          colnames(molecules)[colnames(molecules) %in% "feature_name"] <- "gene"
178
        }
179
      } else {
180
        molecules <- NULL
181
      }
182
      image.data <- SeuratObject::CreateFOV(coords = image.data, type = c("centroids"), molecules = molecules, assay = cell.assay)
183
      image <- paste0(image_key, assy)
184
      seu[[image]] <- image.data
185
    } else if(type == "spatial"){
186
      stop("Currently VoltRon does not support converting into Spatial-type (e.g. VisiumV1) Spatial objects!")
187
    }
188
  }
189
  
190
  
191
  # return
192
  seu
193
}
194
195
####
196
# AnnData ####
197
####
198
199
#' convertAnnDataToVoltRon
200
#'
201
#' converting AnnData h5ad files to VoltRon objects
202
#'
203
#' @param file h5ad file
204
#' @param AssayID the ID assays in the h5ad file
205
#' @param ... additional parameters passed to \link{formVoltRon}
206
#'
207
#' @export
208
#'
209
convertAnnDataToVoltRon <- function(file, AssayID = NULL, ...){
210
  
211
  # check anndata package
212
  if(!requireNamespace('anndata'))
213
    stop("Please install anndata package!: install.packages('anndata')")
214
  
215
  # read anndata
216
  adata <- anndata::read_h5ad(file)
217
  
218
  # raw counts
219
  rawdata <- as.matrix(t(adata$X))
220
  
221
  # metadata
222
  metadata <- adata$obs
223
  
224
  # coordinates and subcellular
225
  if(is.null(AssayID)){
226
    coords <- data.frame(adata$obsm, row.names = colnames(rawdata))
227
    coords <- apply(coords, 2, as.numeric)
228
    colnames(coords) <- c("x", "y")
229
    
230
    # scale coordinates and assay.type
231
    params <- list()
232
    assay.type <- "cell"
233
    assay_name <- "Xenium"
234
    
235
    # create VoltRon
236
    object <- formVoltRon(rawdata, metadata, image = NULL, coords, main.assay = assay_name, params = params, assay.type = assay.type, ...)
237
    
238
    # return
239
    return(object)
240
  } else {
241
  }
242
}
243
244
#' as.AnnData
245
#'
246
#' Converting a VoltRon object into a AnnData (.h5ad) object
247
#'
248
#' @param object a VoltRon object
249
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
250
#' if NULL, the default assay will be used, see \link{vrMainAssay}.
251
#' @param file the name of the h5ad file.
252
#' @param flip_coordinates if TRUE, the spatial coordinates (including segments) will be flipped.
253
#' @param method the package to use for conversion: "anndataR" or "anndata".
254
#' @param create.ometiff should an ometiff file be generated of default image of the object
255
#' @param python.path the path to the python binary, otherwise either \code{basilisk} package is used or \code{getOption("voltron.python.path")} should be not NULL.
256
#' @param ... additional parameters passed to \link{vrImages}.
257
#' 
258
#' @details
259
#' This function converts a VoltRon object into an AnnData object (.h5ad file). It extracts assay data,
260
#' spatial coordinates, and optionally flips coordinates. Images associated with the assay can be included in the 
261
#' resulting AnnData file, with additional customization parameters like channel, scale.perc. 
262
#' 
263
#' @rdname as.AnnData
264
#'
265
#' @importFrom stringr str_extract
266
#' @importFrom magick image_data
267
#' 
268
#' @export
269
as.AnnData <- function(object, 
270
                       file, 
271
                       assay = NULL, 
272
                       flip_coordinates = FALSE, 
273
                       method = "anndata", 
274
                       create.ometiff = FALSE, 
275
                       python.path = NULL,
276
                       ...) {
277
  
278
  # Check the number of assays
279
  if (is.null(assay)) {
280
    if (length(unique(SampleMetadata(object)[["Assay"]])) > 1) {
281
      stop("You can only convert a single VoltRon assay into a Anndata object!")
282
    } else {
283
      assay <- SampleMetadata(object)[["Assay"]]
284
    }
285
  }
286
  assay <- vrAssayNames(object, assay = assay)
287
  
288
  # Check the number of assays
289
  if (unique(vrAssayTypes(object, assay = assay)) %in% c("ROI", "tile")) {
290
    stop("Conversion of tile or ROI assays into Anndata is not permitted!")
291
  }
292
  
293
  # Data
294
  data <- vrData(object, assay = assay, norm = FALSE)
295
  
296
  # Metadata
297
  metadata <- Metadata(object, assay = assay)
298
  metadata[["library_id"]] <- stringr::str_extract(rownames(metadata), "_Assay[0-9]+$")
299
  metadata[["library_id"]] <- gsub("^_", "", metadata[["library_id"]])
300
  
301
  # Embeddings
302
  obsm <- list()
303
  if (length(vrEmbeddingNames(object, assay = assay)) > 0) {
304
    for (embed_name in vrEmbeddingNames(object, assay = assay)) {
305
      obsm[[embed_name]] <- vrEmbeddings(object, assay = assay, type = embed_name)
306
    }
307
  }
308
  
309
  # Flip coordinates
310
  if (flip_coordinates) {
311
    object <- flipCoordinates(object, assay = assay)
312
  }
313
  
314
  # Coordinates
315
  coords <- vrCoordinates(object, assay = assay)
316
  
317
  # Segments
318
  segments <- vrSegments(object, assay = assay)
319
  if(length(segments) > 0){
320
    max_vertices <- max(vapply(segments, nrow, numeric(1)))
321
    num_cells <- length(segments)
322
    segmentations_array <- array(NA, dim = c(num_cells, max_vertices, 2))
323
    cell_ids <- names(segments)
324
    for (i in seq_along(cell_ids)) {
325
      seg <- segments[[i]]
326
      seg_matrix <- as.matrix(seg[, c("x", "y")])
327
      nrow_diff <- max_vertices - nrow(seg_matrix)
328
      if (nrow_diff > 0) {
329
        seg_matrix <- rbind(seg_matrix, matrix(NA, nrow = nrow_diff, ncol = 2))
330
      }
331
      segmentations_array[i, , ] <- seg_matrix
332
    }
333
    for (k in seq_len(2)) {
334
      segmentations_array[,,k] <- t(apply(segmentations_array[,,k], 1, fill_na_with_preceding))
335
    } 
336
  } else {
337
    segmentations_array <- array(dim = nrow(coords))
338
  }
339
340
  # Images
341
  images_mgk <- vrImages(object, assay = assay, ...)
342
  if(!is.list(images_mgk)){
343
    images_mgk <- list(images_mgk)
344
    names(images_mgk) <- vrAssayNames(object, assay = assay)  
345
  }
346
  image_list <- lapply(images_mgk, function(img) {
347
    list(images = list(hires = as.numeric(magick::image_data(img, channels = "rgb"))),
348
         scalefactors = list(tissue_hires_scalef = 1, spot_diameter_fullres = 0.5))
349
  })
350
  
351
  # obsm
352
  # TODO: currently embedding and spatial dimensions should of the same size, but its not 
353
  # always the case in VoltRon objects
354
  obsm <- c(obsm, list(spatial = coords, 
355
                       spatial_AssayID = coords, 
356
                       segmentation = segmentations_array))
357
  
358
  # save as zarr
359
  if(grepl(".zarr[/]?$", file)){
360
    
361
    # check packages
362
    if(!requireNamespace('reticulate'))
363
      stop("Please install reticulate package!: install.packages('reticulate')")
364
    if(!requireNamespace('DelayedArray'))
365
      stop("Please install DelayedArray package for using DelayedArray functions")
366
    
367
    # run basilisk to call zarr methods
368
    python.path <- getPythonPath(python.path)
369
    if(!is.null(python.path)){
370
      reticulate::use_python(python = python.path)
371
      
372
      zarr <- reticulate::import("zarr")
373
      anndata <- reticulate::import("anndata")
374
      make_numpy_friendly <- function(x) {
375
        if (DelayedArray::is_sparse(x)) {
376
          methods::as(x, "dgCMatrix")
377
        }
378
        else {
379
          as.matrix(x)
380
        }
381
      }
382
      X <- make_numpy_friendly(t(data))
383
      obsm <- list(spatial = coords, 
384
                   spatial_AssayID = coords, 
385
                   segmentation = segmentations_array)
386
      adata <- anndata$AnnData(X = X, 
387
                               obs = metadata, 
388
                               obsm = obsm, 
389
                               uns = list(spatial = image_list))
390
      adata <- reticulate::r_to_py(adata)
391
      adata$write_zarr(file)   
392
      success <- TRUE
393
    } else if(requireNamespace('basilisk')){
394
      py_env <- getBasilisk()
395
      proc <- basilisk::basiliskStart(py_env)
396
      on.exit(basilisk::basiliskStop(proc))
397
      success <- basilisk::basiliskRun(proc, function(data, metadata, obsm, coords, segments, image_list, file) {
398
        zarr <- reticulate::import("zarr")
399
        anndata <- reticulate::import("anndata")
400
        make_numpy_friendly <- function(x) {
401
          if (DelayedArray::is_sparse(x)) {
402
            methods::as(x, "dgCMatrix")
403
          }
404
          else {
405
            as.matrix(x)
406
          }
407
        }
408
        X <- make_numpy_friendly(t(data))
409
        obsm <- list(spatial = coords, 
410
                     spatial_AssayID = coords, 
411
                     segmentation = segmentations_array)
412
        adata <- anndata$AnnData(X = X, 
413
                                 obs = metadata, 
414
                                 obsm = obsm, 
415
                                 uns = list(spatial = image_list))
416
        adata <- reticulate::r_to_py(adata)
417
        adata$write_zarr(file)       
418
        return(TRUE)
419
      }, data = data, metadata = metadata, obsm = obsm, coords = coords, segments = segmentations_array, image_list = image_list, file = file)
420
    } else {
421
      stop("Please define the 'python.path' or install the basilisk package!: BiocManager::install('basilisk')")
422
    }
423
    
424
    if(create.ometiff){
425
      success2 <- as.OmeTiff(images_mgk[[1]], out_path = gsub("zarr[/]?$", "ome.tiff", file), python.path = python.path) 
426
      success <- success & success2
427
    } 
428
    
429
    return(success)
430
    
431
  # save as h5ad
432
  } else if(grepl(".h5ad$", file)) {
433
    
434
    # Check and use a package for saving h5ad
435
    if (method == "anndataR") {
436
      if (!requireNamespace('anndataR', quietly = TRUE)) {
437
        stop("The anndataR package is not installed. Please install it or choose the 'anndata' method.")
438
      }
439
      
440
      # Create anndata using anndataR
441
      adata <- anndataR::AnnData(obs_names = rownames(metadata), 
442
                                 var_names = rownames(data), 
443
                                 X = t(data), 
444
                                 obs = metadata, 
445
                                 obsm = list(spatial = coords, 
446
                                             spatial_AssayID = coords, 
447
                                             segmentation = segmentations_array),
448
                                 uns = list(spatial = image_list))
449
      
450
      # Write to h5ad file using anndataR
451
      anndataR::write_h5ad(adata, path = file)
452
      
453
    } else if (method == "anndata") {
454
      if (!requireNamespace('anndata', quietly = TRUE)) {
455
        stop("The anndata package is not installed. Please install it or choose the 'anndataR' method.")
456
      }
457
      
458
      # check reticulate
459
      python.path <- getPythonPath(python.path)
460
      if(!is.null(python.path)){
461
        reticulate::use_python(python.path)
462
      }
463
      
464
      # Create anndata using anndata
465
      adata <- anndata::AnnData(X = t(data), 
466
                                obs = metadata, 
467
                                obsm = list(spatial = coords,
468
                                            spatial_AssayID = coords,
469
                                            segmentation = segmentations_array),
470
                                uns = list(spatial = image_list))
471
      
472
      
473
      # Write to h5ad file using anndata
474
      anndata::write_h5ad(adata, filename = file)
475
      
476
    } else {
477
      stop("Invalid method selected. Please choose either 'anndataR' or 'anndata'.")
478
    }
479
    
480
  } else {
481
    stop("the 'file' should have an .h5ad, .zarr or .zarr/ extension")
482
  }
483
}  
484
485
####
486
# Zarr ####
487
####
488
489
#' @rdname as.Zarr
490
#'
491
#' @importFrom magick image_raster
492
#' @importFrom grDevices col2rgb
493
#'
494
#' @export
495
"as.Zarr.magick-image" <- function (object, out_path, image_id = "image_1")
496
{
497
  # check packages
498
  if(!requireNamespace('basilisk'))
499
    stop("Please install basilisk package!: BiocManager::install('basilisk')")
500
  if(!requireNamespace('reticulate'))
501
    stop("Please install reticulate package!: install.packages('reticulate')")
502
  
503
  img_arr <- apply(as.matrix(magick::image_raster(object, tidy = FALSE)), c(1, 2), col2rgb)
504
  py_env <- getBasilisk()
505
  proc <- basilisk::basiliskStart(py_env)
506
  on.exit(basilisk::basiliskStop(proc))
507
  success <- basilisk::basiliskRun(proc, function(img_arr, image_id, out_path) {
508
    zarr <- reticulate::import("zarr")
509
    ome_zarr <- reticulate::import("ome_zarr")
510
    z_root <- zarr$open_group(out_path, mode = "w")
511
    obj_list <- function(...) {
512
      retval <- stats::setNames(list(), character(0))
513
      param_list <- list(...)
514
      for (key in names(param_list)) {
515
        retval[[key]] = param_list[[key]]
516
      }
517
      retval
518
    }
519
    default_window <- obj_list(start = 0, min = 0, max = 255, end = 255)
520
    ome_zarr$writer$write_image(image = img_arr,
521
                                group = z_root,
522
                                axes = "cyx",
523
                                omero = obj_list(name = image_id, version = "0.3",
524
                                                 rdefs = obj_list(),
525
                                                 channels = list(obj_list(label = "r", color = "FF0000", window = default_window),
526
                                                                 obj_list(label = "g", color = "00FF00", window = default_window),
527
                                                                 obj_list(label = "b", color = "0000FF", window = default_window))))
528
    return(TRUE)
529
  }, img_arr = img_arr, image_id = image_id, out_path = out_path)
530
  return(success)
531
}
532
533
####
534
# OME ####
535
####
536
537
#' as.OmeTiff
538
#'
539
#' Converting VoltRon (magick) images to ome.tiff
540
#'
541
#' @param object a magick-image object
542
#' @param out_path output path to ome.tiff file
543
#' @param image_id image name
544
#' @param python.path the path to the python binary, otherwise either \code{basilisk} package is used or \code{getOption("voltron.python.path")} should be not NULL.
545
#' 
546
#' @importFrom magick image_raster
547
#' @importFrom grDevices col2rgb
548
#'
549
#' @export
550
as.OmeTiff <- function (object, out_path, image_id = "image_1", python.path = NULL){
551
  
552
  # check packages
553
  if(!requireNamespace('reticulate'))
554
    stop("Please install reticulate package!: install.packages('reticulate')")
555
  
556
  # get image and transpose the array
557
  img_arr <- apply(as.matrix(magick::image_raster(object, tidy = FALSE)), c(1, 2), col2rgb)
558
  img_arr <- aperm(img_arr, c(2,3,1))
559
  
560
  # run basilisk
561
  python.path <- getPythonPath(python.path)
562
  if(!is.null(python.path)){
563
    reticulate::use_python(python = python.path)
564
    e <- new.env()
565
    options("reticulate.engine.environment" = e)
566
    img_arr <- reticulate::r_to_py(img_arr)
567
    assign("img_arr_py", img_arr, envir = e)
568
    reticulate::py_run_string(
569
      paste0("import numpy as np
570
import tifffile
571
tifimage = r.img_arr_py.astype('uint8')
572
# tifimage = np.random.randint(0, 255, (32, 32, 3), 'uint8')
573
with tifffile.TiffWriter('", out_path, "') as tif: tif.write(tifimage, photometric='rgb')"
574
      ))
575
    success <- TRUE
576
  } else if(requireNamespace('basilisk')) {
577
    py_env <- getBasilisk()
578
    proc <- basilisk::basiliskStart(py_env)
579
    on.exit(basilisk::basiliskStop(proc))
580
    success <- basilisk::basiliskRun(proc, function(img_arr, image_id, out_path, e) {
581
      
582
      # set up environment
583
      e <- new.env()
584
      options("reticulate.engine.environment" = e)
585
      
586
      # get image data to python environment
587
      img_arr <- reticulate::r_to_py(img_arr)
588
      assign("img_arr_py", img_arr, envir = e)
589
      
590
      # save ome.tiff
591
      reticulate::py_run_string(
592
        paste0("import numpy as np
593
import tifffile
594
tifimage = r.img_arr_py.astype('uint8')
595
# tifimage = np.random.randint(0, 255, (32, 32, 3), 'uint8')
596
with tifffile.TiffWriter('", out_path, "') as tif: tif.write(tifimage, photometric='rgb')"
597
        ))
598
      
599
      return(TRUE)
600
    }, img_arr = img_arr, image_id = image_id, out_path = out_path)
601
  } else {
602
    stop("Please define the 'python.path' or install the basilisk package!: BiocManager::install('basilisk')")
603
  }
604
  
605
  return(success)
606
}
607
608
#' as.OmeZarr
609
#'
610
#' Converting VoltRon (magick) images to ome.tiff
611
#'
612
#' @param object a magick-image object
613
#' @param out_path output path to ome.tiff file
614
#' @param image_id image name
615
#' 
616
#' @importFrom magick image_raster
617
#' @importFrom grDevices col2rgb
618
#'
619
#' @export
620
as.OmeZarr <- function (object, out_path, image_id = "image_1"){
621
  
622
  # check packages
623
  if(!requireNamespace('basilisk'))
624
    stop("Please install basilisk package!: BiocManager::install('basilisk')")
625
  if(!requireNamespace('reticulate'))
626
    stop("Please install reticulate package!: install.packages('reticulate')")
627
  
628
  # get image and transpose the array
629
  img_arr <- apply(as.matrix(magick::image_raster(object, tidy = FALSE)), c(1, 2), col2rgb)
630
631
  # run basilisk
632
  py_env <- getBasilisk()
633
  proc <- basilisk::basiliskStart(py_env)
634
  on.exit(basilisk::basiliskStop(proc))
635
  success <- basilisk::basiliskRun(proc, function(img_arr, image_id, out_path) {
636
    zarr <- reticulate::import("zarr")
637
    ome_zarr <- reticulate::import("ome_zarr")
638
    z_root <- zarr$open_group(out_path, mode = "w")
639
    obj_list <- function(...) {
640
      retval <- stats::setNames(list(), character(0))
641
      param_list <- list(...)
642
      for (key in names(param_list)) {
643
        retval[[key]] = param_list[[key]]
644
      }
645
      retval
646
    }
647
    default_window <- obj_list(start = 0, min = 0, max = 255, end = 255)
648
    ome_zarr$writer$write_image(image = img_arr,
649
                                group = z_root,
650
                                axes = "cyx",
651
                                omero = obj_list(name = image_id, version = "0.3",
652
                                                 rdefs = obj_list(),
653
                                                 channels = list(obj_list(label = "r", color = "FF0000", window = default_window),
654
                                                                 obj_list(label = "g", color = "00FF00", window = default_window),
655
                                                                 obj_list(label = "b", color = "0000FF", window = default_window))))
656
    return(TRUE)
657
  }, img_arr = img_arr, image_id = image_id, out_path = out_path)
658
  return(success)
659
}
660
661
####
662
# Giotto ####
663
####
664
665
#' as.Giotto
666
#'
667
#' Converting a VoltRon object into a Giotto object
668
#'
669
#' @param object a VoltRon object
670
#' @param assay the name of the assay to be converted
671
#' @param reg if TRUE, registered coordinates will be used
672
#'
673
#' @rdname as.Giotto
674
#'
675
#' @importFrom dplyr bind_cols
676
#' @importFrom stringr str_replace str_extract
677
#' @importFrom magick image_write
678
#'
679
#' @export
680
as.Giotto <- function(object, assay = NULL, reg = FALSE){
681
  
682
  # sample metadata
683
  sample_metadata <- SampleMetadata(object)
684
  
685
  # check Seurat package
686
  if(!requireNamespace('Giotto'))
687
    stop("Please install Giotto package!devtools::install_github('drieslab/Giotto')")
688
  
689
  # check the number of assays
690
  if(is.null(assay)){
691
    if(length(unique(sample_metadata[["Assay"]])) > 1){
692
      stop("You can only convert a single VoltRon assay into a Seurat object!")
693
    } else {
694
      assay <- sample_metadata[["Assay"]]
695
    }
696
  } else {
697
    vrMainAssay(object) <- assay
698
  }
699
  
700
  # check the number of assays
701
  if(!unique(vrAssayTypes(object, assay = assay)) %in% c("cell")) {
702
    stop("Conversion of assay types other than cells into Giotto is not yet permitted!")
703
  }
704
  
705
  # data
706
  rowdata <- vrData(object, assay = assay, norm = FALSE)
707
  
708
  # metadata
709
  metadata <- Metadata(object, assay = assay)
710
  metadata$cell_ID <- rownames(metadata)
711
  assays <- stringr::str_extract(rownames(metadata), pattern = "_Assay[0-9]+$")
712
  assays <- gsub("^_", "", assays)
713
  
714
  # coordinates
715
  coords <- vrCoordinates(object, assay = assay, reg = reg)
716
  
717
  # Seurat object
718
  gio <- Giotto::createGiottoObject(expression = rowdata, 
719
                                    spatial_locs = coords, 
720
                                    cell_metadata = metadata)
721
  
722
  # get image objects for each assay
723
  for(assy in vrAssayNames(object)){
724
    assay_object <- object[[assy]]
725
    if(vrAssayTypes(assay_object) == "cell"){
726
      img <- vrImages(assay_object)
727
      gio_img <- Giotto::createGiottoImage(gio, 
728
                                   spat_unit = "cell", 
729
                                   mg_object = img)
730
      gio <- Giotto::addGiottoImage(gio, images = list(gio_img), spat_loc_name = "cell")
731
    } else {
732
      stop("Currently VoltRon does only support converting cell type spatial data sets into SpatialExperiment objects!")
733
    }
734
  }
735
  
736
  # return
737
  gio
738
}
739
740
####
741
# SpatialExperiment ####
742
####
743
744
#' @param type the spatial data type of Seurat object: "image" or "spatial"
745
#' @param assay_type one of two types, 'cell' or 'spot' etc.
746
#' @param assay_name the assay name of the voltron assays (e.g. Visium, Xenium etc.)
747
#' @param image_id select image_id names if needed.
748
#' @param verbose verbose
749
#' @param ... Additional parameter passed to \link{formVoltRon}
750
#'
751
#' @rdname as.VoltRon
752
#' @method as.VoltRon SpatialExperiment
753
#' 
754
#' @importFrom magick image_read
755
#'
756
#' @export
757
as.VoltRon.SpatialExperiment <- function(object, assay_type = "cell", assay_name = NULL, image_id = NULL, verbose = TRUE, ...){
758
  
759
  # check SpatialExperiment package
760
  if(!requireNamespace('SpatialExperiment'))
761
    stop("Please install SpatialExperiment package for using SpatialExperiment objects!: BiocManager::install('SpatialExperiment')")
762
  
763
  # raw counts
764
  data <- SummarizedExperiment::assay(object, i = "counts")
765
766
  # metadata
767
  metadata <- as.data.frame(SummarizedExperiment::colData(object))
768
  
769
  # embeddings
770
  dim_names <- SingleCellExperiment::reducedDimNames(object)
771
  if(length(dim_names) > 0){
772
    embeddings_flag <- TRUE
773
    embedding_list <- sapply(dim_names, function(x) {
774
      SingleCellExperiment::reducedDim(object, type = x)
775
    }, USE.NAMES = TRUE)
776
  } else {
777
    embeddings_flag <- FALSE
778
  }
779
  
780
  # coords
781
  coords <- SpatialExperiment::spatialCoords(object)
782
  colnames(coords) <- c("x", "y")
783
  
784
  # img data
785
  imgdata <- SpatialExperiment::imgData(object)
786
  
787
  # image
788
  voltron_list <- list()
789
  sample_names <- unique(metadata$sample_id)
790
  for(samp in sample_names){
791
    
792
    # spatial points
793
    sppoints <- rownames(metadata)[metadata$sample_id == samp]
794
795
    # metadata 
796
    cur_metadata <- metadata[sppoints,]
797
    
798
    # data
799
    cur_data <- data[,sppoints]
800
    
801
    # coords
802
    cur_coords <- coords[sppoints,]
803
    
804
    # image
805
    if(nrow(imgdata) > 0){
806
      
807
      # get image names 
808
      if(is.null(image_id)){
809
        image_names <- imgdata$image_id[imgdata$sample_id == samp]
810
      } else {
811
        image_names <- image_id
812
      }
813
      
814
      # get image scales
815
      scale.factors_list <- vapply(image_names, function(img){ 
816
        SpatialExperiment::scaleFactors(object, 
817
                                        sample_id = samp, 
818
                                        image_id = img)
819
      }, numeric(1))
820
      if(length(unique(scale.factors_list)) > 1){
821
        stop("All images of a single sample should have the same scale for VoltRon object conversion!: please select an 'image_id'")
822
      }
823
      
824
      # get image list
825
      img_list <- sapply(image_names, function(img){ 
826
        imgraster <- SpatialExperiment::imgRaster(object, 
827
                                                  sample_id = samp, 
828
                                                  image_id = img)
829
        magick::image_read(imgraster)
830
      }, USE.NAMES = TRUE)
831
      
832
      # scale coordinates
833
      scale.factors <- unique(unlist(scale.factors_list))
834
      cur_coords <- cur_coords*scale.factors
835
      
836
      # reverse y coordinates
837
      imginfo <- getImageInfo(img_list[[1]])
838
      cur_coords[,2] <- imginfo$height - cur_coords[,2]
839
    } else {
840
      img_list <- NULL
841
    }
842
    
843
    # get params
844
    if(assay_type == "spot"){
845
      vis.spot.radius <- 1 
846
      spot.radius <- 1
847
    } else {
848
      params <- list()
849
    }
850
    
851
    # form voltron
852
    assay_name <- assay_name
853
    assay_type <- assay_type
854
    voltron_list[[samp]] <- formVoltRon(data = cur_data, metadata = cur_metadata, coords = cur_coords, 
855
                                      main.assay = assay_name, image = img_list, params = params, 
856
                                      assay.type = assay_type, sample_name = samp, ...)
857
    
858
    # add embeddings
859
    spatialpoints <- vrSpatialPoints(voltron_list[[samp]])
860
    spatialpoints_nopostfix <- stringr::str_replace(spatialpoints, "_Assay[0-9]+$", "")
861
    spatialpoints_assay <- stringr::str_extract(spatialpoints, "Assay[0-9]+$")
862
    if(embeddings_flag){
863
      for(embed_name in names(embedding_list)){
864
        cur_embedding <- embedding_list[[embed_name]][sppoints,]
865
        rownames(cur_embedding) <- spatialpoints
866
        vrEmbeddings(voltron_list[[samp]], type = embed_name) <- cur_embedding
867
      }
868
    }
869
  }
870
  
871
  # merge object
872
  if(verbose)
873
    message("Merging object ...")
874
  if(length(voltron_list) > 1){
875
    vrobject <- merge(voltron_list[[1]], voltron_list[-1])
876
  } else {
877
    vrobject <- voltron_list[[1]]
878
  }
879
880
  return(vrobject)
881
}
882
883
#' as.SpatialExperiment
884
#'
885
#' Converting a VoltRon object into a SpatialExperiment object
886
#'
887
#' @param object a VoltRon object
888
#' @param assay the name of the assay to be converted
889
#' @param reg if TRUE, registered coordinates will be used
890
#'
891
#' @rdname as.SpatialExperiment
892
#'
893
#' @importFrom dplyr bind_cols
894
#' @importFrom stringr str_replace str_extract
895
#' @importFrom magick image_write
896
#'
897
#' @export
898
as.SpatialExperiment <- function(object, assay = NULL, reg = FALSE){
899
  
900
  # sample metadata
901
  sample_metadata <- SampleMetadata(object)
902
  
903
  # check Seurat package
904
  if(!requireNamespace('SpatialExperiment'))
905
    stop("Please install SpatialExperiment package!: BiocManager::install('SpatialExperiment'')")
906
  
907
  # check the number of assays
908
  if(is.null(assay)){
909
    if(length(unique(sample_metadata[["Assay"]])) > 1){
910
      stop("You can only convert a single VoltRon assay into a Seurat object!")
911
    } else {
912
      assay <- sample_metadata[["Assay"]]
913
    }
914
  } else {
915
    vrMainAssay(object) <- assay
916
  }
917
  
918
  # check the number of assays
919
  if(unique(vrAssayTypes(object, assay = assay)) %in% c("ROI", "molecule", "tile")) {
920
    stop("Conversion of ROI, molecule and tile assays into SpatialExperiment is not yet permitted!")
921
  }
922
  
923
  # data
924
  rawdata <- as.matrix(vrData(object, assay = assay, norm = FALSE))
925
  
926
  # metadata
927
  metadata <- Metadata(object, assay = assay)
928
  if(is.null(rownames(metadata)))
929
    rownames(metadata) <- metadata$id
930
  metadata <- metadata[colnames(rawdata),]
931
  assays <- stringr::str_extract(rownames(metadata), pattern = "_Assay[0-9]+$")
932
  assays <- gsub("^_", "", assays)
933
  
934
  # Embeddings
935
  reduceddims <- list()
936
  if (length(vrEmbeddingNames(object, assay = assay)) > 0) {
937
    for (embed_name in vrEmbeddingNames(object, assay = assay)) {
938
      reduceddims[[embed_name]] <- vrEmbeddings(object, assay = assay, type = embed_name)
939
    }
940
  }
941
  
942
  # coordinates
943
  coords <- as.matrix(vrCoordinates(flipCoordinates(object, assay = assay), assay = assay, reg = reg))
944
  coords <- coords[colnames(rawdata),]
945
  coords <- coords[,c("x", "y")]
946
  colnames(coords) <- c("x_centroid", "y_centroid")
947
  
948
  # Seurat object
949
  spe <- SpatialExperiment::SpatialExperiment(assay=list(counts = rawdata),
950
                                              colData=metadata, 
951
                                              reducedDims = reduceddims,
952
                                              sample_id=assays,
953
                                              spatialCoords=coords)
954
  spe$sample_id <- assays
955
  
956
  # get image objects for each assay
957
  for(assy in vrAssayNames(object)){
958
    assay_object <- object[[assy]]
959
    channels <- vrImageChannelNames(assay_object)
960
    for(ch in channels){
961
      img <- vrImages(assay_object, channel = ch)
962
      imgfile <- tempfile(fileext='.png')
963
      magick::image_write(image = img, path = imgfile, format = 'png')
964
      spe <- SpatialExperiment::addImg(spe,
965
                                       sample_id = vrAssayNames(assay_object),
966
                                       image_id = ch,
967
                                       imageSource = imgfile,
968
                                       scaleFactor = 1,
969
                                       load = TRUE)
970
      file.remove(imgfile) 
971
    }
972
  }
973
  
974
  # return
975
  spe
976
}
977
978
####
979
# Auxiliary ####
980
####
981
982
getPythonPath <- function(python.path){
983
  voltron.python.path <- getOption("voltron.python.path")
984
  python.path <- python.path %||% voltron.python.path
985
  if(is.null(python.path)){
986
    return(NULL)
987
  } else {
988
    if(file.exists(python.path)){
989
      return(python.path)
990
    } else if(file.exists(voltron.python.path)){
991
      return(voltron.python.path)
992
    } else {
993
      stop("The python path '", python.path, "' doesn't exist!")
994
    }
995
  }
996
}