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

Switch to unified view

a b/R/annotation.R
1
####
2
# Main Shiny App ####
3
####
4
5
#' annotateSpatialData
6
#'
7
#' A mini shiny app to for annotating spatial points
8
#'
9
#' @param object a VoltRon object
10
#' @param label the name of the new metadata column (default: annotation) annotating spatial points by selected polygons
11
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. 
12
#' if NULL, the default assay will be used, see \link{vrMainAssay}.
13
#' @param annotation_assay name of the annotation assay ()
14
#' @param use.image.only if TRUE, use only the image
15
#' @param shiny.options a list of shiny options (launch.browser, host, port etc.) passed \code{options} arguement of \link{shinyApp}. For more information, see \link{runApp}
16
#' @param image_name the name/key of the image
17
#' @param channel the name of the main channel
18
#' @param ... additional parameters passed to \link{vrSpatialPlot}.
19
#'
20
#' @import shiny
21
#' @importFrom shinyjs useShinyjs show hide
22
#' @importFrom stats median
23
#' @importFrom sp point.in.polygon
24
#' @import ggplot2
25
#'
26
#' @export
27
#' 
28
#' @examples
29
#' \dontrun{
30
#' # Annotate based on images
31
#' visium_data <- annotateSpatialData(visium_data, use.image.only = TRUE)
32
#' 
33
#' # Annotate based on spatial plot
34
#' xenium_data <- annotateSpatialData(xenium_data, group.by = "clusters")
35
#' }
36
annotateSpatialData <- function(object, label = "annotation", assay = NULL, annotation_assay = "ROIAnnotation", use.image.only = FALSE, 
37
                                shiny.options = list(launch.browser = getOption("shiny.launch.browser", interactive())), 
38
                                image_name = NULL, channel = NULL, ...) {
39
  
40
  if(!inherits(object, "VoltRon"))
41
    stop("Please provide a VoltRon object!")
42
  
43
  ## Importing images ####
44
  
45
  # get assay names, and always get a single assay
46
  assay_names <- vrAssayNames(object, assay = assay)
47
  if(length(assay_names) > 0)
48
    assay <- assay_names[1]
49
  
50
  # get image name and channel
51
  if(is.null(image_name)){
52
    image_name <- vrMainSpatial(object[[assay]])
53
  }
54
  
55
  # get image
56
  img <- vrImages(object[[assay]], name = image_name, channel = channel, as.raster = TRUE)
57
  if(!inherits(img, "Image_Array")){
58
    if(!requireNamespace("ImageArray")){
59
      message("Please install ImageArray package to speed up visualization")
60
      img <- magick::image_read(img)
61
    } else{
62
      img <- ImageArray::createImageArray(img)
63
    }
64
  }
65
  if(!use.image.only){
66
    # get spatial plot
67
    g_spatial <- vrSpatialPlot(object, assay = assay, spatial = image_name, channel = channel, scale.image = FALSE, ...)
68
    g_spatial <- g_spatial$layers[[2]]
69
  }
70
  
71
  # get image info
72
  max.pixel.size <- 1200
73
  imginfo <- getImageInfo(img)
74
  
75
  ## Get previous annotation ####
76
  
77
  # set label names
78
  sample_metadata <- SampleMetadata(object)
79
  metadata <- Metadata(object, assay = sample_metadata[assay, "Assay"])
80
  coords <- vrCoordinates(object, assay = assay)
81
  if(label %in% colnames(metadata)){
82
    unique_names <- make.unique(c(colnames(metadata)[grepl(paste0("^", label), colnames(metadata))], label))
83
    label <- unique_names[length(unique_names)]
84
  }
85
  
86
  # get segmentations (if exists) from the same layer
87
  if(!is.null(annotation_assay)){
88
    layer_metadata <- sample_metadata[sample_metadata$Layer == sample_metadata[assay, "Layer"] & sample_metadata$Sample == sample_metadata[assay, "Sample"],]
89
    if(annotation_assay %in% layer_metadata$Assay){
90
      
91
      # get segments
92
      segments <- vrSegments(object, assay = annotation_assay)
93
      segments <- lapply(segments, function(seg) seg[,colnames(seg)[colnames(seg) != "id"]])
94
      segment_names <- gsub("_Assay[0-9]+$", "", names(segments)) 
95
      
96
      # remove the latest annotation
97
      all_assay_names <- vrAssayNames(object, assay = "all")
98
      object <- subsetVoltRon(object, assays = all_assay_names[!all_assay_names %in% rownames(layer_metadata)[layer_metadata$Assay == annotation_assay]])
99
      
100
    } else {
101
      segments <- list()
102
      segment_names <- c()
103
    }
104
  }
105
  
106
  ## UI and Server ####
107
  
108
  # Define UI for the application
109
  ui <- fluidPage(
110
    sidebarLayout(position = "left",
111
                  
112
                  sidebarPanel(
113
                    
114
                    # margin settings
115
                    tags$style(make_css(list('.well', 'margin', '7%'))),
116
                    
117
                    # # specific settings for dealing with simultaneous click and brush events
118
                    # # https://jokergoo.github.io/2021/02/20/differentiate-brush-and-click-event-in-shiny/
119
                    tags$script(HTML("
120
            $('#plot').mousedown(function(e) {
121
                var parentOffset = $(this).offset();
122
                var relX = e.pageX - parentOffset.left;
123
                var relY = e.pageY - parentOffset.top;
124
                Shiny.setInputValue('x1', relX);
125
                Shiny.setInputValue('y1', relY);
126
            }).mouseup(function(e) {
127
                var parentOffset = $(this).offset();
128
                var relX = e.pageX - parentOffset.left;
129
                var relY = e.pageY - parentOffset.top;
130
                Shiny.setInputValue('x2', relX);
131
                Shiny.setInputValue('y2', relY);
132
                Shiny.setInputValue('action', Math.random());
133
            });
134
          ")),
135
                    
136
                    # Interface
137
                    fluidRow(
138
                      column(12,h4("Spatial Annotation")),
139
                      column(12,shiny::actionButton("reset_btn",     "Reset Points     ")),
140
                      column(12,shiny::actionButton("rmvlast_btn",   "Remove Last Point")),
141
                      column(12,shiny::actionButton("addregion_btn", "Add Region       ")),
142
                    ),
143
                    br(),
144
                    
145
                    fluidRow(
146
                      column(6,shiny::selectInput("region_type", label = "Region Type", choices = c("Polygon", "Circle"), selected = "Polygon")),
147
                      column(6,shiny::sliderInput("alpha", "Transparency", min = 0, max = 1, value = 0.2)),
148
                    ),
149
                    
150
                    fluidRow(
151
                      column(6,shiny::sliderInput("label_size", label = "Label Size", min = 1, max = 5, step = 0.5, value = 4)),
152
                    ),
153
                    
154
                    # instructions
155
                    h4("How to use"),
156
                    p(style="font-size: 12px;", strong("Single-L-click"), " to select polygon or circle points"),
157
                    p(style="font-size: 12px;", strong("Add Region"), " to set points as a new region"),
158
                    p(style="font-size: 12px;", strong("Circles"), " require only 2 points"),
159
                    p(style="font-size: 12px;", strong("Polygons"), " require at least 4 points"),
160
                    br(),
161
                    
162
                    # Subsets
163
                    fluidRow(
164
                      column(12,h4("Selected Regions")),
165
                      br(),
166
                      column(12,shiny::actionButton("done", "Done")),
167
                      br()  
168
                    ),
169
                    
170
                    # Subsets
171
                    fluidRow(
172
                      br(),
173
                      uiOutput("textbox_ui"),
174
                      br()  
175
                    ),
176
                    
177
                    width = 4
178
                  ),
179
                  mainPanel(
180
                    shinyjs::useShinyjs(),
181
                    plotOutput("image_plot",
182
                               height = "1000px",
183
                               click = "plot_click",
184
                               dblclick = "plot_dblclick",
185
                               brush = brushOpts(
186
                                 id = "plot_brush", fill = "green",
187
                                 resetOnNew = TRUE
188
                               )),
189
                    width = 8
190
                  )
191
    )
192
  )
193
  
194
  # Define server logic required to create, add, and remove textboxes
195
  server <- function(input, output, session) {
196
    
197
    # Reactive values ####
198
    selected_corners_list <- reactiveVal(segments)
199
    selected_corners <- reactiveVal(data.frame(x = numeric(0), y = numeric(0)))
200
    ranges <- reactiveValues(x = c(0, imginfo$width), y = c(0, imginfo$height))
201
    
202
    # Zoom Event ####
203
    manageImageBrush(img, ranges, max.pixel.size, input, output, session)
204
    
205
    # Corner Events ####
206
    observeEvent(input$reset_btn, {
207
      selected_corners(data.frame(x = numeric(0), y = numeric(0)))
208
    })
209
    observeEvent(input$rmvlast_btn, {
210
      selected_corners(selected_corners()[-nrow(selected_corners()),])
211
    })
212
    manageSelectedCorners(selected_corners, img, ranges, max.pixel.size, input, output, session)
213
214
    # Region Events ####
215
    n <- length(segments)
216
    textboxes <- reactiveVal(if (n > 0) seq_len(n) else numeric(0))
217
    if (n > 0) {
218
      segment_names <- as.list(segment_names)
219
      names(segment_names) <- paste0("region", seq_len(n))
220
      textbox_values <- do.call("reactiveValues", segment_names)
221
    } else {
222
      textbox_values <- reactiveValues()
223
    }
224
    
225
    # Dynamically generate UI for textboxes and remove buttons
226
    output$textbox_ui <- renderUI({
227
      lapply(rev(textboxes()), function(i) {
228
        column(12,
229
               textInputwithButton(textinputId = paste0("region", i), label = paste0("Region ", i),
230
                                   buttoninputId = paste0("remove", i), value = isolate(textbox_values[[paste0("region", i)]]), 
231
                                   onclick = sprintf('Shiny.setInputValue("remove", %d)', i))
232
               
233
        )
234
      })
235
    })
236
  
237
    ### update Region text ####
238
    
239
    observe({
240
      lapply(textboxes(), function(i) {
241
        observeEvent(input[[paste0("region", i)]], {
242
          textbox_values[[paste0("region", i)]] <- isolate(input[[paste0("region", i)]])
243
        }, ignoreNULL = FALSE)
244
      })
245
    })
246
    
247
    ### remove a Region ####
248
    
249
    # Observe event to remove textbox when the button is clicked
250
    observeEvent(input$remove, {
251
      
252
      # remove one point
253
      selected_corners_list(selected_corners_list()[!(textboxes() == as.numeric(isolate(input$remove)))])
254
      
255
      # Update the reactive value to remove the textbox
256
      textboxes(setdiff(textboxes(), as.numeric(isolate(input$remove))))
257
      
258
      # Remove the value from textbox_values
259
      textbox_values[[paste0("region", as.numeric(input$remove))]] <- NULL
260
      
261
    }, ignoreInit = TRUE)
262
    
263
    ### add a Region ####
264
    
265
    # Observe event to add a new textbox
266
    observeEvent(input$addregion_btn, {
267
      
268
      # Polygon selection
269
      if(isolate(input$region_type == "Polygon")){
270
        if(nrow(selected_corners()) > 3){
271
          
272
          # add to region list
273
          selected_corners_list(c(selected_corners_list(), list(selected_corners())))
274
          
275
          # remove selected points
276
          selected_corners(data.frame(x = numeric(0), y = numeric(0)))
277
          
278
          # add buttons
279
          new_id <- if (length(textboxes()) == 0) 1 else max(textboxes()) + 1
280
          textboxes(c(textboxes(), new_id))
281
          textbox_values[[paste0("region", new_id)]] <- ""
282
        } else {
283
          showNotification("You must selected at least 4 points for each polygon!")
284
        }
285
      } 
286
      
287
      # Circle selection
288
      if(isolate(input$region_type == "Circle")){
289
        if(nrow(selected_corners()) == 2){
290
          
291
          # add to region list
292
          circle <- makeCircleData(selected_corners())
293
          selected_corners_list(c(selected_corners_list(), list(circle)))
294
          
295
          # remove selected points
296
          selected_corners(data.frame(x = numeric(0), y = numeric(0)))
297
          
298
          # add buttons
299
          new_id <- if (length(textboxes()) == 0) 1 else max(textboxes()) + 1
300
          textboxes(c(textboxes(), new_id))
301
          textbox_values[[paste0("region", new_id)]] <- ""
302
        } else {
303
          showNotification("You must selected only 2 points for each circle!")
304
        }
305
      }
306
    })
307
    # Image output ####
308
    output$image_plot <- renderPlot({
309
      
310
      ## get image ####
311
      zoom_info <- FromBoxToCrop(cbind(ranges$x, ranges$y), imageinfo = imginfo)
312
      img <- cropImage(img, zoom_info)
313
      g <- plotImage(img, max.pixel.size = max.pixel.size) + labs(title = "")
314
      if(!use.image.only){
315
        g_spatial_clone <- cloneLayer(g_spatial)
316
        g <- g + transformSpatialLayer(g_spatial_clone, img, ranges, max.pixel.size)
317
      }
318
319
      # visualize currently selected corners ####
320
      transformed_corners <- transformSelectedCorners(selected_corners(), img, ranges, max.pixel.size)
321
      if(nrow(transformed_corners) > 0){
322
        if(isolate(input$region_type == "Polygon")){
323
          g <- g +
324
            ggplot2::geom_polygon(aes(x = x, y = y), data = transformed_corners, alpha = input$alpha, color = "red")
325
        } else {
326
          circle <- makeCircleData(transformed_corners)
327
          g <- g +
328
            ggforce::geom_ellipse(aes(x0 = as.numeric(x), y0 = as.numeric(y), a = as.numeric(rx), b = as.numeric(ry), angle = 0), 
329
                                  data = circle, alpha = input$alpha, color = "red", fill = "red")         
330
        }
331
        g <- g +
332
          ggplot2::geom_point(aes(x = x, y = y), data = transformed_corners, color = "red", shape = 16) 
333
      }
334
      
335
      # visualize already selected regions ####
336
      transformed_corners_list <- lapply(selected_corners_list(), transformSelectedCorners, img, ranges, max.pixel.size)
337
      if(length(transformed_corners_list) > 0){
338
        for (i in seq_len(length(transformed_corners_list))){
339
          cur_corners <- transformed_corners_list[[i]]
340
          
341
          # visualize regions
342
          if(ncol(cur_corners) == 2){
343
            g <- g +
344
              ggplot2::geom_polygon(aes(x = x, y = y, group = "region"), data = cur_corners, alpha = input$alpha, color = "red") 
345
          } else {
346
            g <- g +
347
              ggforce::geom_ellipse(aes(x0 = as.numeric(x), y0 = as.numeric(y), a = as.numeric(rx), b = as.numeric(ry), angle = 0), 
348
                                    data = cur_corners, alpha = input$alpha, color = "red", fill = "red")
349
          }
350
          
351
          # visualize labels
352
          if(ncol(cur_corners) == 2){
353
            cur_corners <- data.frame(x = mean(cur_corners[,1]), y = max(cur_corners[,2]), region = paste("Region ", isolate(textboxes()[i])))
354
          } else {
355
            cur_corners <- data.frame(x = cur_corners[,1], y = cur_corners[,2] + cur_corners[,3], region = paste("Region ", isolate(textboxes()[i])))
356
          }
357
          g <- g +
358
            ggrepel::geom_label_repel(mapping = aes(x = x, y = y, label = region), data = cur_corners,
359
                                      size = input$label_size, direction = "y", nudge_y = 6, box.padding = 0, label.padding = input$label_size*0.1, seed = 1, color = "red")
360
          
361
        }
362
      }
363
      
364
      # return graph
365
      g
366
    })
367
    
368
    # Return values for the shiny app ####
369
    observe({
370
      if(length(selected_corners_list()) > 0){
371
        shinyjs::show(id = "done")
372
      } else {
373
        shinyjs::hide(id = "done")
374
      }
375
    })
376
    observeEvent(input$done, {
377
      
378
      # selected list
379
      selected_polygon_list <- selected_corners_list()
380
      
381
      # collect labels
382
      selected_label_list <- vapply(seq_len(length(selected_polygon_list)), function(i) input[[paste0("region",i)]], character(1))
383
      
384
      if(length(selected_corners_list()) == 0){
385
        showNotification("You have not annotated the data yet!")
386
      } else if(any(selected_label_list == "")) {
387
        showNotification("Some regions have blank annotations (empty labels!)")
388
      } else {
389
        
390
        ### annotate spatial points ####
391
        if(inherits(metadata, "data.table")){
392
          spatialpoints <- as.vector(metadata$id)
393
        } else {
394
          spatialpoints <- rownames(metadata)
395
        }
396
        
397
        new_label <- rep("undefined", length(spatialpoints))
398
        names(new_label) <- spatialpoints
399
        result_list <- list()
400
        for(i in seq_len(length(selected_polygon_list))){
401
          cur_poly <- selected_polygon_list[[i]]
402
          if(ncol(cur_poly) > 2){
403
            in.list <- point.in.circle(coords[,1], coords[,2], cur_poly[,1], cur_poly[,2], cur_poly[,3])
404
          } else {
405
            in.list <- sp::point.in.polygon(coords[,1], coords[,2], cur_poly[,1], cur_poly[,2])
406
          }
407
          new_label[rownames(coords)[!!in.list]] <- selected_label_list[i]
408
        }
409
        
410
        # place annotation to metadata
411
        metadata[[label]] <- new_label
412
        Metadata(object, assay = sample_metadata[assay, "Assay"]) <- metadata
413
        
414
        # add polygons to a new assay ####
415
        segments <- list()
416
        for(i in seq_len(length(selected_label_list))){
417
          segments[[selected_label_list[i]]] <- data.frame(id = i, selected_polygon_list[[i]])
418
        }
419
        coords <- t(vapply(segments, function(seg){
420
          apply(seg[,c("x", "y")], 2, mean)
421
        }, numeric(2)))
422
        new_assay <- formAssay(coords = coords, 
423
                               segments = segments,
424
                               type = "ROI",
425
                               image = vrImages(object, assay = assay),
426
                               main_image = vrMainImage(object[[assay]]),
427
                               name = assay)
428
        metadata <- data.frame(check.rows = FALSE, row.names = rownames(coords), selected_label_list)
429
        colnames(metadata) <- label
430
        object <- addAssayVoltRon(object,
431
                                  assay = new_assay,
432
                                  metadata = metadata,
433
                                  assay_name = annotation_assay,
434
                                  sample = sample_metadata[assay, "Sample"],
435
                                  layer = sample_metadata[assay, "Layer"])
436
        
437
        # stop app and return
438
        stopApp(object)
439
      }
440
    })
441
  }
442
  
443
  # Run App ####
444
  shiny.options <- configure_shiny_options(shiny.options)
445
  shiny::runApp(
446
    shiny::shinyApp(ui, server, options = list(host = shiny.options[["host"]], port = shiny.options[["port"]], launch.browser = shiny.options[["launch.browser"]]),
447
                    onStart = function() {
448
                      onStop(function() {
449
                      })
450
                    })
451
  )
452
}
453
454
####
455
# Annotation Utilities ####
456
####
457
458
manageImageBrush <- function(image, ranges, max.pixel.size, input, output, session){
459
  imginfo <- getImageInfo(image)
460
  observeEvent(input$plot_dblclick, {
461
    brush <- isolate(input$plot_brush)
462
    if (!is.null(brush)) {
463
      
464
      # get brush
465
      brush_mat <- data.frame(x = c(brush$xmin, brush$xmax), 
466
                              y = c(brush$ymin, brush$ymax))
467
      
468
      # if width is large, then correct the brush event for the downsize (scaling) effect
469
      limits <- data.frame(x = ranges$x, y = ranges$y)
470
      width <- limits[2,1]-limits[1,1]
471
      height <- limits[2,2]-limits[1,2]
472
      if(max(height,width) > max.pixel.size){
473
        if(inherits(image, "Image_Array")){
474
          n.series <- ImageArray::len(image)
475
          cur_width <- width
476
          cur_height <- height
477
          for(ii in 2:n.series){
478
            cur_width <- width/(2^(ii-1))
479
            cur_height <- height/(2^(ii-1))
480
            if(max(cur_height, cur_width) <= max.pixel.size){
481
              break
482
            }
483
          }
484
          brush_mat <- brush_mat*width/ceiling(cur_width)
485
        } else {
486
          brush_mat <- brush_mat*width/max.pixel.size
487
        }
488
      }
489
      
490
      # correct brush for the zoom effect
491
      brush_mat[,1] <- brush_mat[,1] + limits[1,1]
492
      brush_mat[,2] <- brush_mat[,2] + limits[1,2]
493
      brush_mat[1,1] <- floor(brush_mat[1,1])
494
      brush_mat[1,2] <- floor(brush_mat[1,2])
495
      brush_mat[2,1] <- ceiling(brush_mat[2,1])
496
      brush_mat[2,2] <- ceiling(brush_mat[2,2])
497
498
      # update ranges
499
      ranges$x <- brush_mat[,1]
500
      ranges$y <- brush_mat[,2]
501
      
502
    } else {
503
      ranges$x <- c(0, imginfo$width)
504
      ranges$y <- c(0, imginfo$height)
505
    }
506
  })
507
}
508
509
manageSelectedCorners <- function(selected_corners, image, ranges, max.pixel.size, input, output, session){
510
  observeEvent(input$plot_click, {
511
    brush <- isolate(input$plot_brush)
512
    if(is.null(brush)) {
513
      
514
      # if a circle, dont allow more than two points
515
      if(isolate(input$region_type == "Circle")){
516
        if(nrow(selected_corners()) == 2){
517
          selected_corners(data.frame(x = numeric(0), y = numeric(0)))
518
        }
519
      }
520
      
521
      # get points
522
      click <- input$plot_click
523
      pts <- c(click$x, click$y)
524
525
      # correct for zoom effect
526
      limits <- data.frame(x = ranges$x, y = ranges$y)
527
      width <- limits[2,1]-limits[1,1]
528
      height <- limits[2,2]-limits[1,2]
529
      if(max(height,width) > max.pixel.size){
530
        if(inherits(image, c("Image_Array"))){
531
          n.series <- ImageArray::len(image)
532
          cur_width <- width
533
          cur_height <- height
534
          for(ii in 2:n.series){
535
            cur_width <- width/(2^(ii-1))
536
            cur_height <- height/(2^(ii-1))
537
            if(max(cur_height, cur_width) <= max.pixel.size){
538
              break
539
            }
540
          }
541
          pts <- pts*width/ceiling(cur_width)
542
        } else {
543
          pts <- pts*width/max.pixel.size
544
        }
545
      }
546
    
547
      # correct for offset effect
548
      pts[1] <- pts[1] + limits[1,1]
549
      pts[2] <- pts[2] + limits[1,2]
550
      
551
      # Append new point to the data frame
552
      new_point <- data.frame(x = pts[1], y = pts[2])
553
      
554
      # add new point
555
      selected_corners(rbind(selected_corners(), new_point))
556
    }
557
  })
558
}
559
560
transformSelectedCorners <- function(selected_corners, image, ranges, max.pixel.size){
561
  
562
  # if empty, return
563
  if(nrow(selected_corners) == 0){
564
    return(selected_corners)
565
  }
566
  selected_corners <- as.matrix(selected_corners)
567
  
568
  # circle settings
569
  circle <- FALSE
570
  if(ncol(selected_corners) > 2)
571
    circle <- TRUE
572
  
573
  # get circle radius 
574
  if(circle){
575
    selected_radius <- selected_corners[,3:4, drop = FALSE]
576
    selected_corners <- selected_corners[,seq_len(2), drop = FALSE]
577
  }
578
  
579
  # get image info
580
  imginfo <- getImageInfo(image)
581
  
582
  # correct for offset effect
583
  limits <- cbind(ranges$x, ranges$y)
584
  selected_corners <- selected_corners - matrix(rep(limits[1,], nrow(selected_corners)), nrow = nrow(selected_corners), byrow = T)
585
586
  # correct for zoom effect
587
  width <- limits[2,1]-limits[1,1]
588
  height <- limits[2,2]-limits[1,2]
589
  if(max(height,width) > max.pixel.size){
590
    if(inherits(image, "Image_Array")){
591
      n.series <- ImageArray::len(image)
592
      cur_width <- width
593
      cur_height <- height
594
      for(ii in 2:n.series){
595
        cur_width <- width/(2^(ii-1))
596
        cur_height <- height/(2^(ii-1))
597
        if(max(cur_height, cur_width) <= max.pixel.size){
598
          break
599
        }
600
      }
601
      selected_corners <- selected_corners*ceiling(cur_width)/width
602
      if(circle){
603
        selected_radius <- selected_radius*ceiling(cur_width)/width
604
      }
605
    } else {
606
      selected_corners <- selected_corners*max.pixel.size/width
607
      if(circle){
608
        selected_radius <- selected_radius*max.pixel.size/width
609
      }
610
    }
611
  }
612
  
613
  # make data frame
614
  if(circle){
615
    selected_corners <- as.data.frame(cbind(selected_corners,selected_radius))
616
    colnames(selected_corners) <- c("x", "y", "rx", "ry")
617
  } else {
618
    selected_corners <- as.data.frame(selected_corners)
619
    colnames(selected_corners) <- c("x", "y") 
620
  }
621
  
622
  # return
623
  selected_corners
624
}
625
626
#' @importFrom stats na.omit
627
transformSpatialLayer <- function(g_spatial, image, ranges, max.pixel.size){
628
  
629
  # correct for zoom
630
  ind <- (g_spatial$data$x > ranges$x[1] & g_spatial$data$x < ranges$x[2]) & (g_spatial$data$y > ranges$y[1] & g_spatial$data$y < ranges$y[2])
631
  g_spatial$data <- g_spatial$data[ind,]
632
  
633
  # is.na
634
  g_spatial$data <- stats::na.omit(g_spatial$data)
635
  
636
  # correct for zoom effect
637
  limits <- data.frame(x = ranges$x, y = ranges$y)
638
  width <- limits[2,1]-limits[1,1]
639
  height <- limits[2,2]-limits[1,2]
640
  if(max(height,width) > max.pixel.size){
641
    if(inherits(image, "Image_Array")){
642
      n.series <- ImageArray::len(image)
643
      cur_width <- width
644
      cur_height <- height
645
      for(ii in 2:n.series){
646
        cur_width <- width/(2^(ii-1))
647
        cur_height <- height/(2^(ii-1))
648
        if(max(cur_height, cur_width) <= max.pixel.size){
649
          break
650
        }
651
      }
652
      g_spatial$data$x <- g_spatial$data$x*ceiling(cur_width)/width
653
      g_spatial$data$y <- g_spatial$data$y*ceiling(cur_width)/width
654
    } else {
655
      g_spatial$data$x <- g_spatial$data$x*max.pixel.size/width
656
      g_spatial$data$y <- g_spatial$data$y*max.pixel.size/width
657
    }
658
  }
659
  
660
  # correct for offset effect
661
  # g_spatial$data$x <- g_spatial$data$x - min(g_spatial$data$x)
662
  # g_spatial$data$y <- g_spatial$data$y - min(g_spatial$data$y)
663
  g_spatial$data$x <- g_spatial$data$x - limits[1,1]
664
  g_spatial$data$y <- g_spatial$data$y - limits[1,2]
665
  
666
  # return
667
  g_spatial
668
}
669
670
####
671
# Shiny Utilities ####
672
####
673
674
#' internal Text input with button
675
#' 
676
#' Reproduced since it is not exported in the Shiny namespace.
677
#' 
678
#' @importFrom shiny tags validateCssUnit
679
#' 
680
#' @keywords internal
681
textInputwithButton <- function (textinputId, label, buttoninputId, value = "", width = NULL, placeholder = NULL, ...) 
682
{
683
  textvalue <- restoreInput(id = textinputId, default = value)
684
  buttonvalue <- restoreInput(id = buttoninputId, default = NULL)
685
  div(class = "form-group shiny-input-container", 
686
      style =  css(width = shiny::validateCssUnit(width), display = "inline-block"),
687
      shinyInputLabel(textinputId, label), 
688
      shiny::tags$input(id = textinputId, 
689
                        style = css(width = "80%", float = "left"),
690
                        type = "text", class = "shiny-input-text form-control", 
691
                        value = textvalue, placeholder = placeholder),
692
      shiny::tags$button(id = buttoninputId, 
693
                         style = css(width = "20%", float = "left"),
694
                         type = "button", class = "btn btn-default action-button", 
695
                         `data-val` = buttonvalue, disabled = NULL, list(shiny::icon("trash")), ...)
696
  )
697
}
698
699
#' Shiny's internal \code{shinyInputLabel} function
700
#' 
701
#' Reproduced since it is not exported in the Shiny namespace.
702
#' 
703
#' @importFrom shiny tags
704
#' 
705
#' @keywords internal
706
shinyInputLabel <- function(inputId, label=NULL) {
707
  shiny::tags$label(label,
708
                    class = "control-label",
709
                    class = if (is.null(label)) "shiny-label-null",
710
                    `for` = inputId
711
  )
712
}
713
714
####
715
# Auxiliary ####
716
####
717
718
#' makeCircleData
719
#' 
720
#' creates circle ROI from selected corners
721
#' 
722
#' @keywords internal
723
makeCircleData <- function(selected_corners) {
724
  radius <- sqrt((selected_corners[1,1] - selected_corners[2,1])^2 + (selected_corners[1,2] - selected_corners[2,2])^2)
725
  data.frame(x = selected_corners[1,1], y = selected_corners[1,2], rx = radius, ry = radius)
726
}
727
728
#' point.in.circle
729
#' 
730
#' checks if points are in the defined circle ROI with a (x,y) center and radius
731
#' 
732
#' @keywords internal
733
point.in.circle <- function(x,y,circle.x, circle.y, radius) {
734
  x <- x - circle.x
735
  y <- y - circle.y
736
  distall <- sqrt((x - circle.x)^2 + (y - circle.y)^2)
737
  return(distall < radius)
738
}