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

Switch to unified view

a b/R/registration.R
1
####
2
# Main Shiny App ####
3
####
4
5
#' registerSpatialData
6
#'
7
#' A mini shiny app to for registering images and spatial coordinates of multiple consequtive spatial datasets
8
#'
9
#' @param object_list a list of VoltRon (or Seurat) objects
10
#' @param reference_spatdata a reference spatial data set, used only if \code{object_list} is \code{NULL}
11
#' @param query_spatdata a query spatial data set, used only if \code{object_list} is \code{NULL}
12
#' @param keypoints (DEPRECATED) a list of tables, each points to matching keypoints from registered images.
13
#' @param mapping_parameters for manual image registration, a list of tables, each points to matching keypoints from registered images, and for automated image registration, a set of mapping parameters
14
#' @param interactive if TRUE, the shiny application for image registration will be triggered, otherwise 'mapping_parameters' or 'keypoints' should be defined.
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
#'
17
#' @import shiny
18
#' @importFrom shinyjs useShinyjs show hide
19
#' @importFrom stats median
20
#' @importFrom magick image_read
21
#'
22
#' @export
23
registerSpatialData <- function(object_list = NULL, reference_spatdata = NULL, query_spatdata = NULL, keypoints = NULL, mapping_parameters = list(), interactive = TRUE,
24
                                shiny.options = list(launch.browser = getOption("shiny.launch.browser", interactive()))) {
25
26
  ## Importing images ####
27
28
  # if the input is not a list, switch to reference vs query mode
29
  if(!is.null(object_list)){
30
    spatdata_list <- object_list
31
    centre <- floor(stats::median(seq_len(length(spatdata_list))))
32
    register_ind <- setdiff(seq_len(length(spatdata_list)), centre)
33
  } else {
34
    spatdata_list <- list(reference_spatdata, query_spatdata)
35
    centre <- 1
36
    register_ind <- 2
37
  }
38
39
  # get images from the list of objects
40
  orig_image_query_list_full <- lapply(spatdata_list, function(spat){
41
    assayname <- vrAssayNames(spat)
42
    channel_names <- vrImageChannelNames(spat[[assayname]])
43
    sapply(channel_names, function(chan){
44
      img <- vrImages(spat[[assayname]], channel = chan, as.raster = TRUE)
45
      if(!inherits(img, "Image_Array")){
46
        img <- magick::image_read(img)
47
      }
48
      img
49
    }, USE.NAMES = TRUE)
50
  })
51
  orig_image_query_list <- lapply(orig_image_query_list_full, function(spat_img) {
52
    return(spat_img[[1]])
53
  })
54
  orig_image_channelname_list <- lapply(spatdata_list, function(spat){
55
    assayname <- vrAssayNames(spat)
56
    vrImageChannelNames(spat[[assayname]])
57
  })
58
  
59
  ## Parameters ####
60
  if(!is.null(keypoints)){
61
    message("The use of 'keypoints' is deprecated, please use 'mapping_parameters' instead!")
62
    mapping_parameters[["keypoints"]] <- keypoints
63
  }
64
  if(!"keypoints" %in% names(mapping_parameters) && !all(is.null(names(mapping_parameters)))){
65
    if(all(grepl("[0-9]-[0-9]", names(mapping_parameters)))){
66
      mapping_parameters[["keypoints"]] <- mapping_parameters
67
    } else {
68
      stop("'mapping_parameters' does not include keypoints")
69
    }
70
  }
71
  
72
  ## Non-interactive Registration ####
73
  if(!interactive){
74
    return(getNonInteractiveRegistration(obj_list = spatdata_list, 
75
                                         centre = centre, 
76
                                         register_ind = register_ind, 
77
                                         mapping_parameters = mapping_parameters, 
78
                                         image_list = orig_image_query_list,
79
                                         image_list_full = orig_image_query_list_full,
80
                                         channel_names = orig_image_channelname_list))
81
  }
82
83
  ## UI and Server ####
84
  ui <- fluidPage(
85
    
86
    # use javascript extensions for Shiny
87
    shinyjs::useShinyjs(),
88
    
89
    # side bar
90
    sidebarLayout(position = "left",
91
                  
92
                  # Side bar
93
                  sidebarPanel(
94
                    tags$style(make_css(list('.well', 'margin', '7%'))),
95
                    
96
                    # # specific settings for dealing with simultaneous click and brush events
97
                    # # https://jokergoo.github.io/2021/02/20/differentiate-brush-and-click-event-in-shiny/
98
                    tags$script(HTML("
99
                        $('#plot').mousedown(function(e) {
100
                            var parentOffset = $(this).offset();
101
                            var relX = e.pageX - parentOffset.left;
102
                            var relY = e.pageY - parentOffset.top;
103
                            Shiny.setInputValue('x1', relX);
104
                            Shiny.setInputValue('y1', relY);
105
                        }).mouseup(function(e) {
106
                            var parentOffset = $(this).offset();
107
                            var relX = e.pageX - parentOffset.left;
108
                            var relY = e.pageY - parentOffset.top;
109
                            Shiny.setInputValue('x2', relX);
110
                            Shiny.setInputValue('y2', relY);
111
                            Shiny.setInputValue('action', Math.random());
112
                        });
113
                    ")),
114
                    
115
                    # side bar for configuration
116
                    getSideBar(params = mapping_parameters),
117
                    
118
                    # panel options
119
                    width = 3,
120
                  ),
121
                  
122
                  mainPanel(
123
                    
124
                    # Interface for the reference image
125
                    br(),
126
                    column(6,
127
                           
128
                           # Reference Images
129
                           getImageTabPanels(length(orig_image_query_list), 
130
                                             orig_image_channelname_list, 
131
                                             type = "ref", 
132
                                             params = mapping_parameters),
133
                           
134
                           br(),
135
                           
136
                           # Matching Alignment
137
                           getAlignmentTabPanel(length(orig_image_query_list), centre, register_ind),
138
                    ),
139
                    
140
                    # Interface for the query images
141
                    column(6,
142
                           
143
                           # Query Images
144
                           getImageTabPanels(length(orig_image_query_list), 
145
                                             orig_image_channelname_list, 
146
                                             type = "query", 
147
                                             params = mapping_parameters),
148
                           
149
                           br(),
150
                           
151
                           # Registered Query Images
152
                           getRegisteredImageTabPanels(length(orig_image_query_list), 
153
                                                       centre, 
154
                                                       register_ind)
155
                    ),
156
                    
157
                    # panel options
158
                    width = 9
159
                  )
160
    )
161
  )
162
  
163
  server <- function(input, output, session) {
164
    
165
    ## Manage interface ####
166
    updateParameterPanels(length(orig_image_query_list), mapping_parameters, input, output, session)
167
    updateTabPanels(centre, register_ind, input, output, session)
168
    # initiateParameterPanels(mapping_parameters, length(orig_image_query_list), input, output, session)
169
    
170
    ## Transform images ####
171
    trans_image_query_list <- transformImageQueryList(orig_image_query_list, input)
172
    
173
    ## get image and zoom info ####
174
    orig_image_query_info_list <- getImageInfoList(orig_image_query_list)
175
    zoom_list <- initiateZoomOptions(orig_image_query_info_list)
176
    manageImageZoomOptions(centre, register_ind, zoom_list, orig_image_query_list, orig_image_query_info_list, input, output, session)
177
    
178
    ## Manage reference and query keypoints ####
179
    # xyTable_list <- initateKeypoints(length(orig_image_query_list), keypoints)
180
    xyTable_list <- initateKeypoints(length(orig_image_query_list), mapping_parameters$keypoints)
181
    manageKeypoints(centre, register_ind, xyTable_list, orig_image_query_list, orig_image_query_info_list, zoom_list, input, output, session)
182
    
183
    ## Image registration ####
184
    registration_mapping_list <- initiateMappings(length(spatdata_list))
185
    getManualRegisteration(registration_mapping_list, spatdata_list, orig_image_query_list, xyTable_list,
186
                           centre, register_ind, input, output, session)
187
    getAutomatedRegisteration(registration_mapping_list, spatdata_list, orig_image_query_list_full, orig_image_channelname_list,
188
                              centre, register_ind, input, output, session)
189
    
190
    ## Main observable ####
191
    observe({
192
      
193
      # output the list of query images
194
      getImageOutput(orig_image_query_list_full, orig_image_query_info_list, xyTable_list, zoom_list, centre, input, output, session)
195
      
196
    })
197
    
198
    ## Return values for the shiny app ####
199
    observeEvent(input$done, {
200
      
201
      # keypoints and mapping
202
      keypoints <- reactiveValuesToList(xyTable_list)
203
      mapping <- reactiveValuesToList(registration_mapping_list)
204
      
205
      # mapping parameters
206
      mapping_parameters <- transferParameterInput(input, 
207
                                                   image_list = orig_image_query_list)
208
      
209
      # get keypoints and registered spatial datasets
210
      stopApp(
211
        list(keypoints = keypoints,
212
             mapping_parameters = c(as.list(mapping_parameters), 
213
                                    list(keypoints = keypoints, 
214
                                         mapping = mapping)),
215
             registered_spat = getRegisteredObject(spatdata_list,
216
                                                   registration_mapping_list,
217
                                                   register_ind,
218
                                                   centre,
219
                                                   input,
220
                                                   reg_mode = ifelse(input$automatictag, "auto", "manual"),
221
                                                   image_list = orig_image_query_list))
222
      )
223
    })
224
  }
225
  
226
  # configure options
227
  shiny.options <- configure_shiny_options(shiny.options)
228
  
229
  # run app
230
  shiny::runApp(
231
    shiny::shinyApp(ui, server, options = list(host = shiny.options[["host"]], port = shiny.options[["port"]], launch.browser = shiny.options[["launch.browser"]]),
232
                    onStart = function() {
233
                      onStop(function() {
234
                      })
235
                    })
236
  )
237
}
238
239
####
240
# User Interface ####
241
####
242
243
#' getSideBar
244
#'
245
#' The UI for the app side bar
246
#'
247
#' @param params mapping parameters
248
#' 
249
#' @import shiny
250
#' 
251
#' @noRd
252
getSideBar <- function(params = NULL){
253
  list(
254
    h4("Spatial Data Alignment"),
255
    fluidRow(
256
      column(12,shiny::checkboxInput("automatictag", "Automated", value = params[["automatictag"]])),
257
      br(),
258
      column(12,selectInput("Method", "Method", 
259
                            choices = c("Homography", "Non-Rigid", "Homography + Non-Rigid"), 
260
                            # selected = "Homography")),
261
                            selected = ifelse(is.null(params[["Method"]]), "Homography", params[["Method"]]))),
262
      br(),
263
      column(12,selectInput("Matcher", "Matcher", 
264
                            choices = c("FLANN", "BRUTE-FORCE"), 
265
                            # selected = "FLANN")),
266
                            selected = ifelse(is.null(params[["Matcher"]]), "FLANN", params[["Matcher"]]))),
267
      br(),
268
      column(12,textInput("GOOD_MATCH_PERCENT", "Match %", 
269
                          # value = "0.20", 
270
                          value = ifelse(is.null(params[["GOOD_MATCH_PERCENT"]]), "0.20", params[["GOOD_MATCH_PERCENT"]]),
271
                          width = "80%", placeholder = NULL)),
272
      column(12,textInput("MAX_FEATURES", "# of Features", 
273
                          # value = "1000", 
274
                          value = ifelse(is.null(params[["MAX_FEATURES"]]), "1000", params[["MAX_FEATURES"]]),
275
                          width = "80%", placeholder = NULL)),
276
      br(),
277
      column(12,shiny::actionButton("register", "Register!")),
278
      br(),
279
    ),
280
    br(),
281
    fluidRow(
282
      column(12,shiny::htmlOutput("summary"))
283
    ),
284
    br(),
285
    fluidRow(
286
      column(12,shiny::actionButton("done", "Done")),
287
      br()
288
    ),
289
    br(),
290
    h4("How to use"),
291
    p(style="font-size: 12px;", strong("Single-L-click:"), "Select point"),
292
    p(style="font-size: 12px;", strong("Single-L-hold-drag:"), "Select area"),
293
    p(style="font-size: 12px;", strong("Double-L-click (selected area):"), "Zoom in"),
294
    p(style="font-size: 12px;", strong("Double-L-click (no area):"), "Zoom out")
295
  )
296
}
297
298
#' getImageTabPanels
299
#'
300
#' The UI for a set of reference/query spatial slides
301
#'
302
#' @param len_images the number of query images
303
#' @param channel_names the list of channel names for each image
304
#' @param type Either reference (ref) or query (query) image
305
#' @param params mapping parameters
306
#'
307
#' @noRd
308
getImageTabPanels <- function(len_images, channel_names, type, params = NULL){
309
310
  # get panel label
311
  label <- ifelse(type == "ref", "Ref. ", "Query ")
312
313
  # call panels
314
  do.call(tabsetPanel, c(id=paste0('image_tab_panel_',type), lapply(seq_len(len_images), function(i) {
315
    tabPanel(paste0(label,i),
316
             br(),
317
             fluidRow(
318
               column(4, selectInput(paste0("rotate_", type, "_image",i), 
319
                                     "Rotate (ClockWise):", 
320
                                     choices = c(0,90,180,270), 
321
                                     # selected = 0)),
322
                                     selected = ifelse(is.null(params[[paste0("rotate_", type, "_image",i)]]), 0, params[[paste0("rotate_", type, "_image",i)]]))),
323
               column(4, selectInput(paste0("flipflop_", type, "_image",i), 
324
                                     "Transform:", 
325
                                     choices = c("None", "Flip", "Flop"), 
326
                                     # selected = "None")),
327
                                     selected = ifelse(is.null(params[[paste0("flipflop_", type, "_image",i)]]), "None", params[[paste0("flipflop_", type, "_image",i)]]))),
328
               column(4, selectInput(paste0("negate_", type, "_image",i), 
329
                                     "Negate Image:", 
330
                                     choices = c("No", "Yes"), 
331
                                     # selected = "No"))
332
                                     selected = ifelse(is.null(params[[paste0("negate_", type, "_image",i)]]), "No", params[[paste0("negate_", type, "_image",i)]])))
333
             ),
334
             fluidRow(
335
               column(4, selectInput(paste0("channel_", type, "_image",i), 
336
                                     "Channel:", 
337
                                     choices = channel_names[[i]])),
338
               column(4, sliderInput(paste0("scale_", type, "_image",i), 
339
                                     "Scale Parameter", 
340
                                     min = 0, 
341
                                     max = 1,  
342
                                     # value = 1)),
343
                                     value = ifelse(is.null(params[[paste0("scale_", type, "_image",i)]]), "1", params[[paste0("scale_", type, "_image",i)]]))),
344
               textOutput(paste0("scaleinfo_", type, "_image",i))
345
             ),
346
             fluidRow(imageOutput(paste0("plot_", type, i), 
347
                                  click = paste0("click_plot_", type, i),
348
                                  dblclick = paste0("dblclick_plot_", type, i),
349
                                  brush = brushOpts(paste0("brush_plot_", type, i), fill = "green", resetOnNew = TRUE))),
350
             br(),
351
             fluidRow(
352
               shiny::actionButton(paste0("remove_", type, i), "Remove Point")
353
             ),
354
    )
355
  })))
356
}
357
358
#' getRegisteredImageTabPanels
359
#'
360
#' The UI for a set of query spatial slides
361
#'
362
#' @param len_images the number of query images
363
#' @param centre center image index
364
#' @param register_ind query image indices
365
#'
366
#' @noRd
367
getAlignmentTabPanel <- function(len_images, centre, register_ind){
368
369
  # tab panels
370
  do.call(tabsetPanel, c(id='image_tab_panel_alignment',lapply(register_ind, function(i) {
371
    tabPanel(paste0("Ali. ",i, "->", centre),
372
             br(),
373
             fluidRow(imageOutput(paste0("plot_alignment",i)))
374
    )
375
  })))
376
}
377
378
#' getRegisteredImageTabPanels
379
#'
380
#' The UI for a set of query spatial slides
381
#'
382
#' @param len_images the number of query images
383
#' @param centre center image index
384
#' @param register_ind query image indices
385
#'
386
#' @return tabsetpanel
387
#'
388
#' @noRd
389
getRegisteredImageTabPanels <- function(len_images, centre, register_ind){
390
391
  # tab panels
392
  do.call(tabsetPanel, c(id='image_tab_panel_reg_query',lapply(register_ind, function(i) {
393
    tabPanel(paste0("Reg. ",i, "->", centre),
394
             br(),
395
             fluidRow(
396
               column(12, align="center",
397
                      imageOutput(paste0("plot_query_reg",i))
398
               )
399
             )
400
    )
401
  })))
402
}
403
404
#' updateTabPanels
405
#'
406
#' A function for automatized selection of reference/query tab panels
407
#'
408
#' @param centre center image index
409
#' @param register_ind query image indices
410
#' @param input input
411
#' @param output output
412
#' @param session session
413
#'
414
#' @noRd
415
updateTabPanels <- function(centre, register_ind, input, output, session){
416
417
  # number of panels
418
  npanels <- length(register_ind) + 1
419
420
  # observe changes in the reference tab panel
421
  observeEvent(input$image_tab_panel_ref,{
422
    selected_panel <- input$image_tab_panel_ref
423
    selected_panel_ind <- as.numeric(strsplit(selected_panel, split = " ")[[1]][2])
424
425
    query_panel_ind <- (selected_panel_ind + 1)
426
    if(query_panel_ind == 1) query_panel_ind <- npanels
427
    updateTabsetPanel(session, "image_tab_panel_query", paste0("Query ", query_panel_ind))
428
    updateTabsetPanel(session, "image_tab_panel_reg_query", paste0("Reg. ",selected_panel_ind, "->", centre))
429
430
    if(selected_panel_ind == npanels)
431
      updateTabsetPanel(session, "image_tab_panel_ref", paste0("Ref. ", selected_panel_ind-1))
432
  })
433
434
  # observe changes in the query tab panel
435
  observeEvent(input$image_tab_panel_query,{
436
    selected_panel <- input$image_tab_panel_query
437
    selected_panel_ind <- as.numeric(strsplit(selected_panel, split = " ")[[1]][2])
438
439
    query_panel_ind <- (selected_panel_ind - 1)
440
    if(query_panel_ind == 0) query_panel_ind <- 1
441
    updateTabsetPanel(session, "image_tab_panel_ref", paste0("Ref. ", query_panel_ind))
442
443
    if(selected_panel_ind == 1){
444
      updateTabsetPanel(session, "image_tab_panel_query", paste0("Query ", selected_panel_ind+1))
445
      updateTabsetPanel(session, "image_tab_panel_reg_query", paste0("Reg. ",selected_panel_ind+1, "->", centre))
446
    } else {
447
      query_panel_ind <- selected_panel_ind
448
      updateTabsetPanel(session, "image_tab_panel_reg_query", paste0("Reg. ",query_panel_ind, "->", centre))
449
    }
450
  })
451
452
  # observe changes in the registered query tab panel
453
  observeEvent(input$image_tab_panel_reg_query,{
454
    selected_panel <- input$image_tab_panel_reg_query
455
    selected_panel_ind <- strsplit(selected_panel, split = " ")[[1]][2]
456
    selected_panel_ind <- as.numeric(strsplit(selected_panel_ind, split = "->")[[1]][1])
457
    updateTabsetPanel(session, "image_tab_panel_query", paste0("Query ", selected_panel_ind))
458
    selected_panel_ali <- gsub("Reg.", "Ali.", selected_panel)
459
    updateTabsetPanel(session, "image_tab_panel_alignment", selected_panel_ali)
460
  })
461
462
  # observe changes in the registered query tab panel
463
  observeEvent(input$image_tab_panel_alignment,{
464
    selected_panel <- input$image_tab_panel_alignment
465
    selected_panel_reg <- gsub("Ali.", "Reg.", selected_panel)
466
    updateTabsetPanel(session, "image_tab_panel_reg_query", selected_panel_reg)
467
  })
468
}
469
470
#' updateParameterPanels
471
#'
472
#' A function for managing which parameter panels or input boxes to appear on UI
473
#'
474
#' @param len_images the length of images
475
#' @param params mapping parameters
476
#' @param input input
477
#' @param output output
478
#' @param session session
479
#' 
480
#' @importFrom shinyjs hide show
481
#' @import shiny
482
#'
483
#' @noRd
484
updateParameterPanels <- function(len_images, params, input, output, session){
485
486
  # done event
487
  shinyjs::hide(id = "done")
488
  observeEvent(input$register, {
489
    shinyjs::show(id = "done")
490
  })
491
492
  # registration panels/buttons
493
  shinyjs::hide(id = "GOOD_MATCH_PERCENT")
494
  shinyjs::hide(id = "MAX_FEATURES")
495
496
  # hide scale parameters
497
  for(i in seq_len(len_images)){
498
    shinyjs::hide(id = paste0("scale_ref_image",i))
499
    shinyjs::hide(id = paste0("scale_query_image",i))
500
    shinyjs::hide(id = paste0("scaleinfo_ref_image",i))
501
    shinyjs::hide(id = paste0("scaleinfo_query_image",i))
502
  }
503
504
  observeEvent(input$automatictag, {
505
    if(input$automatictag){
506
      
507
      # Method and Matcher
508
      choices <- c("Homography", "Homography + Non-Rigid")
509
      selected <- ifelse(is.null(params[["Method"]]), choices[1],
510
                         ifelse(!params[["Method"]] %in% choices, choices[1], params[["Method"]]))
511
      # selected <- choices[1]
512
      updateSelectInput(session, 
513
                        "Method", 
514
                        choices = choices, 
515
                        # selected = "Homography")
516
                        selected = selected)
517
      shinyjs::show(id = "Matcher")
518
519
      # show automatic registration parameters of BRUTE-FORCE
520
      if(input$Matcher == "BRUTE-FORCE"){
521
        shinyjs::show(id = "GOOD_MATCH_PERCENT")
522
        shinyjs::show(id = "MAX_FEATURES")
523
      } 
524
      if(input$Matcher == "FLANN"){
525
        shinyjs::hide(id = "GOOD_MATCH_PERCENT")
526
        shinyjs::hide(id = "MAX_FEATURES")
527
      }
528
529
      # show scale parameters
530
      for(i in seq_len(len_images)){
531
        shinyjs::show(id = paste0("scale_ref_image",i))
532
        shinyjs::show(id = paste0("scale_query_image",i))
533
        shinyjs::show(id = paste0("scaleinfo_ref_image",i))
534
        shinyjs::show(id = paste0("scaleinfo_query_image",i))
535
      }
536
537
    } else {
538
      
539
      # Method and Matcher
540
      choices <- c("Non-Rigid", "Homography + Non-Rigid")
541
      selected <- ifelse(is.null(params[["Method"]]), choices[1],
542
                         ifelse(!params[["Method"]] %in% choices, choices[1], params[["Method"]]))
543
      # selected <- choices[1]
544
      updateSelectInput(session, "Method", 
545
                        choices = choices, 
546
                        # selected = "Non-Rigid")
547
                        selected = selected)
548
      shinyjs::hide(id = "Matcher")
549
550
      # hide automatic registration parameters of BRUTE-FORCE
551
      if(input$Matcher == "FLANN"){
552
        shinyjs::hide(id = "GOOD_MATCH_PERCENT")
553
        shinyjs::hide(id = "MAX_FEATURES")
554
      }
555
556
      # hide scale parameters
557
      for(i in seq_len(len_images)){
558
        shinyjs::hide(id = paste0("scale_ref_image",i))
559
        shinyjs::hide(id = paste0("scale_query_image",i))
560
        shinyjs::hide(id = paste0("scaleinfo_ref_image",i))
561
        shinyjs::hide(id = paste0("scaleinfo_query_image",i))
562
      }
563
    }
564
  })
565
566
  observeEvent(input$Method, {
567
    if(grepl("FLANN", input$Matcher)){
568
      shinyjs::hide(id = "GOOD_MATCH_PERCENT")
569
      shinyjs::hide(id = "MAX_FEATURES")
570
    } else {
571
      shinyjs::show(id = "GOOD_MATCH_PERCENT")
572
      shinyjs::show(id = "MAX_FEATURES")
573
      if(grepl("Non-Rigid", input$Method)){
574
        updateSelectInput(session, "Method", selected = "Homography") 
575
        showNotification("Brute-Force Matching can't be used with Non-Rigid Registration\n")
576
      }
577
    }
578
  })
579
  
580
  observeEvent(input$Matcher, {
581
    if(grepl("FLANN", input$Matcher)){
582
      shinyjs::hide(id = "GOOD_MATCH_PERCENT")
583
      shinyjs::hide(id = "MAX_FEATURES")
584
    } else {
585
      shinyjs::show(id = "GOOD_MATCH_PERCENT")
586
      shinyjs::show(id = "MAX_FEATURES")
587
      if(grepl("Non-Rigid", input$Method)){
588
        updateSelectInput(session, "Method", selected = "Homography") 
589
        showNotification("Brute-Force Matching can't be used with Non-Rigid Registration\n")
590
      }
591
    }
592
  })
593
}
594
595
#' initiateParameterPanels
596
#'
597
#' A function for managing which initialized parameters
598
#'
599
#' @param mapping_parameters mapping parameters
600
#' @param len_images the length of images
601
#' @param input input
602
#' @param output output
603
#' @param session session
604
#' 
605
#' @import shiny
606
#'
607
#' @noRd
608
initiateParameterPanels <- function(mapping_parameters, len_images, input, output, session){
609
610
  # update image specific parameters
611
  lapply(c("ref", "query"), function(t){
612
    lapply(seq_len(len_images), function(i){
613
      lapply(c("rotate", "flipflop", "negate", "channel"), function(c){
614
          updateSelectInput(session = session, paste0(c, "_", t, "_image",i), selected = mapping_parameters[[paste0(c, "_", t, "_image",i)]])
615
      })
616
      updateSliderInput(session = session, paste0("scale_", t, "_image",i), value = mapping_parameters[[paste0("scale_", t, "_image",i)]])
617
    })
618
  })
619
  
620
  # update alignment parameters
621
  updateCheckboxInput(session = session, "automatictag", value = mapping_parameters[["automatictag"]])
622
  updateTextInput(session = session, "GOOD_MATCH_PERCENT", value = mapping_parameters[["GOOD_MATCH_PERCENT"]])
623
  updateTextInput(session = session, "MAX_FEATURES", value = mapping_parameters[["MAX_FEATURES"]])
624
  updateSelectInput(session = session, "Method", selected = mapping_parameters[["Method"]])
625
  updateSelectInput(session = session, "Matcher", selected = mapping_parameters[["Matcher"]])
626
}
627
628
####
629
# Registering Objects ####
630
####
631
632
#' getRegisteredObject
633
#'
634
#' Get registered list of VoltRon objects
635
#'
636
#' @param obj_list a list of VoltRon objects
637
#' @param mapping_list a list of transformation matrices
638
#' @param register_ind the indices of query images/spatialdatasets
639
#' @param centre the index of the central reference image/spatialdata
640
#' @param input input
641
#' @param reg_mode the registration mode, either "auto" or "manual"
642
#' @param image_list the list of query/ref images
643
#' @param aligned_image_list the list of aligned query/ref images
644
#'
645
#' @noRd
646
getRegisteredObject <- function(obj_list, mapping_list, register_ind, centre, input, reg_mode = "manual", image_list = NULL, aligned_image_list = NULL){
647
648
  # initiate registered VoltRon objects
649
  ref_ind <- centre
650
  registered_sr <- list()
651
652
  # the original reference object
653
  registered_sr[[ref_ind]] <- obj_list[[ref_ind]]
654
655
  # waiter start
656
  withProgress(message = 'Register Coordinates (and Segments)', value = 0, {
657
658
    # register all assays
659
    for(i in register_ind){
660
  
661
      # choose image query and ref order
662
      if(i > ref_ind){
663
        ref_extension = paste0("ref_image",ref_ind)
664
        query_extension = paste0("query_image",i)
665
      } else {
666
        ref_extension = paste0("query_image",ref_ind)
667
        query_extension = paste0("ref_image",i)
668
      }
669
  
670
      # register the VoltRon object
671
      for(assy in vrAssayNames(obj_list[[i]], assay = "all")){
672
  
673
        # Increment the progress bar, and update the detail text.
674
        incProgress(1/length(register_ind), detail = paste("Register", assy, "of Layer", i, sep = " "))
675
  
676
        # register assay
677
        obj_list[[i]] <- applyPerspectiveTransform(obj_list[[i]],
678
                                                   assay = assy,
679
                                                   mapping = mapping_list[[paste0(i)]],
680
                                                   reference_image = image_list[[ref_ind]],
681
                                                   input = input,
682
                                                   reg_mode = reg_mode,
683
                                                   ref_extension = ref_extension,
684
                                                   query_extension = query_extension)
685
  
686
      }
687
      registered_sr[[i]] <- obj_list[[i]]
688
  
689
    }
690
691
  })
692
  return(registered_sr)
693
}
694
695
#' getRegisteredObjectNonShiny
696
#'
697
#' Get registered list of VoltRon objects, without shiny
698
#'
699
#' @param obj_list a list of VoltRon objects
700
#' @param mapping_list a list of transformation matrices
701
#' @param register_ind the indices of query images/spatialdatasets
702
#' @param centre the index of the central reference image/spatialdata
703
#' @param input input
704
#' @param reg_mode the registration mode, either "auto" or "manual"
705
#' @param image_list the list of query/ref images
706
#' @param aligned_image_list the list of aligned query/ref images
707
#'
708
#' @noRd
709
getRegisteredObjectNonShiny <- function(obj_list, mapping_list, register_ind, centre, input, reg_mode = "manual", image_list = NULL, aligned_image_list = NULL){
710
  
711
  # initiate registered VoltRon objects
712
  ref_ind <- centre
713
  registered_sr <- list()
714
  
715
  # the original reference object
716
  registered_sr[[ref_ind]] <- obj_list[[ref_ind]]
717
  
718
  # message
719
  message('Register Coordinates (and Segments)')
720
  
721
  # register all assays
722
  for(i in register_ind){
723
    
724
    # choose image query and ref order
725
    if(i > ref_ind){
726
      ref_extension = paste0("ref_image",ref_ind)
727
      query_extension = paste0("query_image",i)
728
    } else {
729
      ref_extension = paste0("query_image",ref_ind)
730
      query_extension = paste0("ref_image",i)
731
    }
732
    
733
    # register the VoltRon object
734
    for(assy in vrAssayNames(obj_list[[i]], assay = "all")){
735
      
736
      # message
737
      message("Register ", assy, " of Layer ", i)
738
739
      # register assay
740
      obj_list[[i]] <- applyPerspectiveTransform(obj_list[[i]],
741
                                                 assay = assy,
742
                                                 mapping = mapping_list[[paste0(i)]],
743
                                                 reference_image = image_list[[ref_ind]],
744
                                                 input = input,
745
                                                 reg_mode = reg_mode,
746
                                                 ref_extension = ref_extension,
747
                                                 query_extension = query_extension)
748
      
749
    }
750
    registered_sr[[i]] <- obj_list[[i]]
751
    
752
  }
753
  return(registered_sr)
754
}
755
756
#' applyPerspectiveTransform
757
#'
758
#' Applying a perspective transformation to the VoltRon object
759
#'
760
#' @param object a VoltRon objects
761
#' @param mapping a list of transformation matrices
762
#' @param reference_image the reference image
763
#' @param input input
764
#' @param reg_mode the registration mode, either "auto" or "manual"
765
#' @param ref_extension the shiny extension of reference image
766
#' @param query_extension the shiny extension of query image
767
#'
768
#' @importFrom magick image_info
769
#'
770
#' @noRd
771
applyPerspectiveTransform <- function(object,
772
                                      assay = NULL,
773
                                      mapping,
774
                                      reference_image,
775
                                      input,
776
                                      reg_mode,
777
                                      ref_extension,
778
                                      query_extension){
779
780
  # check assay
781
  if(is.null(assay))
782
    assay <- vrAssayNames(object)
783
784
  # get coordinates, segments and spatial points
785
  coords <- vrCoordinates(object, assay = assay)
786
  segments <- vrSegments(object, assay = assay)
787
788
  if(reg_mode == "manual"){
789
790
    # get the multiplication of all homography matrices
791
    # cur_mapping <- Reduce("%*%", mapping)
792
    mapping <- manageMapping(mapping)
793
    
794
    # get registered coordinates
795
    coords_reg <- as.matrix(as(coords, "dgCMatrix"))
796
    coords_reg[,c("x", "y")] <- applyMapping(coords[,c("x", "y")], mapping)
797
    rownames(coords_reg) <- rownames(coords)
798
    colnames(coords_reg) <- colnames(coords)
799
800
    # get registered segments
801
    if(length(segments) > 0){
802
      segments_reg <- do.call(rbind, segments)
803
      segments_reg[,colnames(segments_reg) %in% c("x", "y")] <- applyMapping(as.matrix(segments_reg[,colnames(segments_reg) %in% c("x", "y")]), mapping)
804
      segments_reg <- split(segments_reg, segments_reg[,1])
805
      names(segments_reg) <- names(segments)
806
    } else {
807
      segments_reg <- segments
808
    }
809
810
    # get registered image (including all channels)
811
    image_reg_list <- sapply(vrImageChannelNames(object[[assay]]), function(x) NULL, USE.NAMES = TRUE)
812
    for(channel_ind in names(image_reg_list)){
813
      query_image <- vrImages(object[[assay]], channel = channel_ind, as.raster = TRUE)
814
      if(!inherits(query_image, "Image_Array")){
815
        query_image <- magick::image_read(query_image)
816
      }
817
      warped_image <- getRcppWarpImage(ref_image = reference_image,
818
                                       query_image = query_image,
819
                                       mapping = mapping)
820
      image_reg_list[[channel_ind]] <- warped_image
821
    }
822
823
  } else if(reg_mode == "auto"){
824
825
    # get the multiplication of all homography matrices
826
    mapping <- manageMapping(mapping)
827
    
828
    # images
829
    ref_image <- transformImage(reference_image, ref_extension, input)
830
    query_image <- vrImages(object[[assay]], as.raster = TRUE)
831
    if(!inherits(query_image, "Image_Array")){
832
      query_image <- magick::image_read(query_image)
833
    }
834
    query_image <- transformImage(query_image, query_extension, input)
835
836
    # image info
837
    query_info <- getImageInfo(query_image)
838
    ref_info <- getImageInfo(ref_image)
839
840
    # get registered coordinates
841
    coords_reg <- as.data.frame(as.matrix(as(coords, "dgCMatrix")))
842
    coords_reg <- transformImageKeypoints(query_image, coords_reg[,c("x","y")], query_extension, input)$keypoints
843
844
    coords_reg[,2] <- query_info$height - coords_reg[,2]
845
    coords_reg <- as.matrix(coords_reg)
846
    coords_reg <- applyMapping(coords_reg, mapping)
847
    coords_reg <- as.data.frame(coords_reg)
848
    coords_reg[,2] <- ref_info$height - coords_reg[,2]
849
850
    colnames(coords_reg) <- c("x", "y")
851
    coords_reg <- transformKeypoints(ref_image, coords_reg, ref_extension, input)
852
    coords_reg <- as.matrix(coords_reg)
853
    rownames(coords_reg) <- rownames(coords)
854
    
855
    # fix 3rd dimension 
856
    coords[,c("x", "y")] <- coords_reg[,c("x", "y")]
857
    coords_reg <- coords
858
859
    # get registered segments
860
    if(length(segments) > 0){
861
      segments_reg <- do.call(rbind, segments)
862
      segments_reg <- as.data.frame(segments_reg)
863
      segments_reg <- transformImageKeypoints(query_image, segments_reg, query_extension, input)$keypoints
864
      segments_reg[,colnames(segments_reg) %in% c("y")] <- query_info$height - segments_reg[,colnames(segments_reg) %in% c("y")]
865
      segments_reg[,colnames(segments_reg) %in% c("x", "y")] <- applyMapping(as.matrix(segments_reg[,colnames(segments_reg) %in% c("x", "y")]), mapping)
866
      segments_reg[,colnames(segments_reg) %in% c("y")]  <- ref_info$height - segments_reg[,colnames(segments_reg) %in% c("y")]
867
      segments_reg <- transformKeypoints(ref_image, segments_reg, ref_extension, input)
868
      segments_reg <- split(segments_reg, segments_reg[,1])
869
      names(segments_reg) <- names(segments)
870
    } else {
871
      segments_reg <- segments
872
    }
873
874
    # get registered image (including all channels)
875
    image_reg_list <- sapply(vrImageChannelNames(object[[assay]]), function(x) NULL, USE.NAMES = TRUE)
876
    for(channel_ind in names(image_reg_list)){
877
878
      # rotate, flip and flop before warping in C++
879
      ref_image <- transformImage(reference_image, ref_extension, input)
880
      query_image <- vrImages(object[[assay]], channel = channel_ind, as.raster = TRUE)
881
      if(!inherits(query_image, "Image_Array")){
882
        query_image <- magick::image_read(query_image)
883
      }
884
      query_image <- transformImage(query_image, query_extension, input)
885
      query_image <- getRcppWarpImage(ref_image, query_image, mapping = mapping)
886
      query_image <- transformImageReverse(query_image, ref_extension, input)
887
888
      image_reg_list[[channel_ind]] <- query_image
889
    }
890
  }
891
892
  # make new image object
893
  vrImages(object[[assay]], reg = TRUE) <- formImage(coords = coords_reg, segments = segments_reg, image = image_reg_list)
894
  
895
  # set up the spatial coordinate name 
896
  vrMainSpatial(object[[assay]]) <- paste0(vrMainSpatial(object[[assay]]), "_reg")
897
898
  # return object
899
  return(object)
900
}
901
902
####
903
# Managing Mappings ####
904
####
905
906
manageMapping <- function(mappings){
907
  
908
  # check if all transformations are homography
909
  allHomography <- suppressWarnings(all(lapply(mappings, function(map){
910
    nrow(map[[1]] > 0) && is.null(map[[2]])
911
  })))
912
  
913
  # change the mapping
914
  new_mappings <- list()
915
  if(allHomography){
916
    mappings <- lapply(mappings, function(map) map[[1]])
917
    new_mappings <- list(
918
      list(Reduce("%*%", mappings),
919
           NULL)
920
    )
921
  } else {
922
    new_mappings <- mappings
923
  }
924
  
925
    
926
  # return
927
  return(new_mappings)
928
}
929
  
930
####
931
# Managing Parameters ####
932
####
933
934
#' initateKeypoints
935
#'
936
#' Initiate shiny reactive values for keypoint dataframes for pairwise reference and query images
937
#'
938
#' @param len_images the length of images
939
#' @param keypoints_list the list of keypoint pairs
940
#' @param input shiny input
941
#' @param output shiny output
942
#' @param session shiny session
943
#'
944
#' @importFrom dplyr tibble
945
#'
946
#' @noRd
947
initateKeypoints <- function(len_images, keypoints_list, input, output, session){
948
949
  # initiate keypoints
950
  if(is.null(keypoints_list)){
951
    keypoints_list <- lapply(seq_len(len_images-1), function(i) {
952
      list(ref = dplyr::tibble(KeyPoint = numeric(), x = numeric(), y = numeric()),
953
           query = dplyr::tibble(KeyPoint = numeric(), x = numeric(), y = numeric()))
954
    })
955
956
    # set names for keypoints
957
    names(keypoints_list) <- paste0(seq_len(len_images-1),"-",2:len_images)
958
  }
959
960
  # return keypoints as reactive values
961
  do.call("reactiveValues", keypoints_list)
962
}
963
964
#' manageKeypoints
965
#'
966
#' A list of shiny observe events for tables and auxiliary operations for pairwise reference and query image
967
#'
968
#' @param centre center image index
969
#' @param register_ind query image indices
970
#' @param xyTable_list a list of keypoints x,y coordinates for each magick image
971
#' @param image_list a lost of magick image
972
#' @param info_list a list of magick image info on width and height
973
#' @param zoom_list a list of x,y ranges of query and ref images
974
#' @param input shiny input
975
#' @param output shiny output
976
#' @param session shiny session
977
#'
978
#' @noRd
979
manageKeypoints <- function(centre, register_ind, xyTable_list, image_list, info_list, zoom_list, input, output, session){
980
981
  # get image types
982
  image_types <- c("ref","query")
983
984
  # get the length of tables
985
  len_tables <- length(xyTable_list)
986
987
  # set click operations for reference and query points
988
  lapply(seq_len(len_tables), function(i){
989
    lapply(image_types, function(type){
990
991
      # listen to click operations for reference/query plots
992
      observeEvent(input[[paste0("click_plot_", type ,i)]], {
993
994
        # get brush information
995
        brush <- input[[paste0("brush_plot_", type ,i)]]
996
        limits <- cbind(zoom_list[[paste0(i)]][[type]]$x, zoom_list[[paste0(i)]][[type]]$y)
997
        if (is.null(brush)) {
998
999
          # get image
1000
          image <- image_list[[i]]
1001
1002
          # get and transform keypoints
1003
          keypoint <- data.frame(x = input[[paste0("click_plot_",type,i)]]$x,
1004
                                 y = input[[paste0("click_plot_",type,i)]]$y)
1005
          
1006
          # get the transformed zoom info first and calculate width, then record transformed image
1007
          limits_trans <- data.frame(x = limits[,1], y = limits[,2])
1008
          limits_trans <- transformImageKeypoints(image, limits_trans, paste0(type, "_image",i), input)
1009
          image_trans <- limits_trans$image
1010
          limits_trans <- data.frame(x = range(limits_trans$keypoints[,1]), y = range(limits_trans$keypoints[,2]))
1011
          
1012
          # correct for scaling, scale factor = 1000
1013
          width <- limits_trans[2,1]-limits_trans[1,1]
1014
          height <- limits_trans[2,2]-limits_trans[1,2]
1015
          if(max(height,width) > 1000){
1016
            if(inherits(image_trans, "Image_Array")){
1017
              n.series <- ImageArray::len(image_trans)
1018
              cur_width <- width
1019
              cur_height <- height
1020
              for(ii in 2:n.series){
1021
                cur_width <- width/(2^(ii-1))
1022
                cur_height <- height/(2^(ii-1))
1023
                if(max(cur_height, cur_width) <= 1000){
1024
                  break
1025
                }
1026
              }
1027
              keypoint <- keypoint*width/ceiling(cur_width)
1028
            } else {
1029
              keypoint <- keypoint*width/1000 
1030
            }
1031
          }
1032
          
1033
          # correct for zoom information
1034
          keypoint <- keypoint + limits_trans[1,]
1035
          
1036
          # correct for flipflop and rotate
1037
          keypoint <- transformKeypoints(image_trans, keypoint, paste0(type, "_image",i), input)
1038
          
1039
          # insert keypoint to associated table
1040
          ref_ind <- ifelse(type == "ref", i, i-1) # select reference image
1041
          
1042
          # insert keypoint to associated table
1043
          temp <- xyTable_list[[paste0(ref_ind, "-", ref_ind+1)]][[type]]
1044
          temp <- temp %>%
1045
            add_row(KeyPoint = nrow(temp)+1, x = keypoint$x, y = keypoint$y)
1046
          xyTable_list[[paste0(ref_ind, "-", ref_ind+1)]][[type]] <- temp
1047
1048
        }
1049
      })
1050
    })
1051
  })
1052
1053
  # remove keypoints from images
1054
  lapply(seq_len(len_tables), function(i){
1055
    lapply(image_types, function(type){
1056
      observeEvent(input[[paste0("remove_", type, i)]], {
1057
        ref_ind <- ifelse(type == "ref", i, i-1) # select reference image
1058
        temp <- xyTable_list[[paste0(ref_ind, "-", ref_ind+1)]][[type]]
1059
        if(nrow(temp) > 0){
1060
          temp <- temp %>% filter(KeyPoint != nrow(temp))
1061
          xyTable_list[[paste0(ref_ind, "-", ref_ind+1)]][[type]] <- temp
1062
        }
1063
      })
1064
    })
1065
  })
1066
}
1067
1068
#' transformImageKeypoints
1069
#'
1070
#' Apply given transformations to a magick image and keypoints for plotting
1071
#'
1072
#' @param image magick image
1073
#' @param extension name extension for the shiny input parameter
1074
#' @param keypoints a set of keypoints
1075
#' @param input shiny input
1076
#' @param session shiny session
1077
#'
1078
#' @importFrom magick image_negate image_rotate image_flip image_flop image_info
1079
#'
1080
#' @noRd
1081
transformImageKeypoints <- function(image, keypoints, extension, input, session){
1082
1083
  if(is.null(keypoints))
1084
    return(list(image = image, keypoints = keypoints))
1085
1086
  # negate image
1087
  input_negate <- input[[paste0("negate_", extension)]]
1088
  if(input_negate == "Yes"){
1089
    image <- negateImage(image)
1090
  }
1091
1092
  # get unrotated image info
1093
  image_limits <- unlist(getImageInfo(image)[1,c("width", "height")])
1094
  image_origin <- image_limits/2
1095
1096
  # rotate image and keypoints
1097
  input_rotate <- as.numeric(input[[paste0("rotate_", extension)]])
1098
  image <- rotateImage(image, input_rotate)
1099
1100
  # get rotated image info
1101
  rotated_image_limits <- unlist(getImageInfo(image)[1,c("width", "height")])
1102
  rotated_image_origin <- rotated_image_limits/2
1103
1104
  # rotate keypoints
1105
  keypoints <- rotateKeypoint(keypoints, input_rotate, image_origin, image_limits, rotated_image_origin, rotated_image_limits)
1106
1107
  # flip flop image and keypoints
1108
  input_flipflop <- input[[paste0("flipflop_", extension)]]
1109
  if(input_flipflop == "Flip"){
1110
    # image <- magick::image_flip(image)
1111
    image <- flipImage(image)
1112
  } else if(input_flipflop == "Flop"){
1113
    # image <- magick::image_flop(image)
1114
    image <- flopImage(image)
1115
  }
1116
1117
  # flipflop keypoints
1118
  keypoints <- flipflopKeypoint(keypoints, rotated_image_limits, input_flipflop)
1119
1120
  # return both the image and the keypoints
1121
  return(list(image = image, keypoints = keypoints))
1122
}
1123
1124
#' transformKeypoints
1125
#'
1126
#' Apply transformations to keypoints given transformed images to find the keypoints locations in the original image
1127
#'
1128
#' @param image magick image
1129
#' @param keypoints keypoints visualized on image
1130
#' @param extension name extension for the shiny input parameter
1131
#' @param input shiny input
1132
#'
1133
#' @importFrom magick image_flip image_flop image_rotate
1134
#'
1135
#' @noRd
1136
transformKeypoints <- function(image, keypoints, extension, input){
1137
1138
  # get unrotated image info
1139
  image_limits <- unlist(getImageInfo(image)[1,c("width", "height")])
1140
  image_origin <- image_limits/2
1141
1142
  # flip flop image and keypoints
1143
  input_flipflop <- input[[paste0("flipflop_", extension)]]
1144
  if(input_flipflop == "Flip"){
1145
    image <- flipImage(image)
1146
  } else if(input_flipflop == "Flop"){
1147
    image <- flopImage(image)
1148
  }
1149
  keypoints <- flipflopKeypoint(keypoints, image_limits, input_flipflop)
1150
1151
  # rotate image (reverse) and keypoints
1152
  input_rotate <- 360 - as.numeric(input[[paste0("rotate_", extension)]])
1153
  image <- rotateImage(image, input_rotate)
1154
1155
  # get rotated image info
1156
  rotated_image_limits <- unlist(getImageInfo(image)[1,c("width", "height")])
1157
  rotated_image_origin <- rotated_image_limits/2
1158
1159
  # rotate keypoints
1160
  keypoints <- rotateKeypoint(keypoints, input_rotate, image_origin, image_limits, rotated_image_origin, rotated_image_limits)
1161
1162
  return(keypoints)
1163
}
1164
1165
#' rotateKeypoint
1166
#'
1167
#' Find transformations of keypoints under clockwise rotations of the image
1168
#'
1169
#' @param keypoints dataset of keypoints
1170
#' @param angle angle of rotation [0,360]
1171
#' @param origin center of the image
1172
#' @param limits limits of the image
1173
#' @param rotated_origin center of the rotated image
1174
#' @param rotated_limits limits of the rotated image
1175
#'
1176
#' @noRd
1177
rotateKeypoint <- function(keypoints, angle, origin, limits, rotated_origin, rotated_limits){
1178
1179
  # if there are no points, return
1180
  if(nrow(keypoints) == 0)
1181
    return(keypoints)
1182
1183
  # get coordinates from the keypoints dataset
1184
  points <- keypoints[,c("x","y")]
1185
1186
  # set rotation matrix for angles
1187
  radii <- ((360-angle)*pi/180)
1188
  s = sin(radii);
1189
  c = cos(radii);
1190
  rotation_mat <- matrix(c(c, s, -s, c), nrow = 2, byrow = F)
1191
1192
  # rotate point
1193
  points <- points - matrix(rep(origin, nrow(points)), nrow = nrow(points), byrow = T)
1194
  points <- points * matrix(rep(1/limits, nrow(points)), nrow = nrow(points), byrow = T)
1195
  rotated_points <- t(rotation_mat %*% t(points))
1196
  rotated_points <- rotated_points * matrix(rep(rotated_limits, nrow(points)), nrow = nrow(rotated_points), byrow = T)
1197
  rotated_points <- rotated_points + matrix(rep(rotated_origin, nrow(points)), nrow = nrow(rotated_points), byrow = T)
1198
1199
  # put rotated points back to keypoints
1200
  keypoints[,c("x","y")] <- rotated_points
1201
1202
  return(keypoints)
1203
}
1204
1205
#' flipflopKeypoint
1206
#'
1207
#' Find transformed keypoints on image given any flip or flop action by magick
1208
#'
1209
#' @param keypoints dataset of keypoints
1210
#' @param image_limits limits of the images
1211
#' @param flipflop a flip or flop action as string
1212
#'
1213
#' @noRd
1214
flipflopKeypoint <- function(keypoints, image_limits, flipflop){
1215
1216
  if(nrow(keypoints) == 0)
1217
    return(keypoints)
1218
1219
  if(grepl("Flop", flipflop))
1220
    keypoints$x = image_limits[1] - keypoints$x
1221
1222
  if(grepl("Flip", flipflop))
1223
    keypoints$y = image_limits[2] - keypoints$y
1224
1225
  return(keypoints)
1226
}
1227
1228
#' imageKeypoint
1229
#'
1230
#' add keypoints as points on ggplot object
1231
#'
1232
#' @param image magick image
1233
#' @param keypoints keypoints to draw on image
1234
#'
1235
#' @noRd
1236
imageKeypoint <- function(image, keypoints){
1237
1238
  if(is.null(keypoints))
1239
    return(image)
1240
1241
  # select keypoints and texts on image
1242
  image <- image +
1243
    geom_point(mapping = aes(x = x, y = y), keypoints, size = 8, shape = 21, fill = "white") +
1244
    geom_text(mapping = aes(x = x, y = y, label = KeyPoint), keypoints, size = 5)
1245
}
1246
1247
#' checkKeypoints
1248
#'
1249
#' check keypoints list
1250
#'
1251
#' @param keypoints_list list of matching keypoints
1252
#'
1253
#' @noRd
1254
checkKeypoints <- function(keypoints_list){
1255
  keypoints_check_flag <- sapply(keypoints_list, function(key_list){
1256
    nrow(key_list$ref) > 0 | nrow(key_list$query) > 0
1257
  })
1258
  if(!all(unlist(keypoints_check_flag))){
1259
    showNotification("Please select keypoints for all images\n")
1260
    return(NULL)
1261
  }
1262
1263
  keypoints_check_flag <- sapply(keypoints_list, function(key_list){
1264
    nrow(key_list$ref) == nrow(key_list$query)
1265
  })
1266
  if(!all(unlist(keypoints_check_flag))){
1267
    showNotification("The number of reference and query keypoints should be equal! \n")
1268
    return(NULL)
1269
  }
1270
}
1271
1272
transferParameterInput <- function(params, image_list){
1273
  
1274
  # the number of registrations
1275
  len_image <- length(image_list)
1276
  
1277
  # transfer params
1278
  input <- list()
1279
  input[["automatictag"]] <- params[["automatictag"]]
1280
  input[["GOOD_MATCH_PERCENT"]] <- params[["GOOD_MATCH_PERCENT"]]
1281
  input[["MAX_FEATURES"]] <- params[["MAX_FEATURES"]]
1282
  input[["Method"]] <- params[["Method"]]
1283
  input[["Matcher"]] <- params[["Matcher"]]
1284
  for(i in seq_len(len_image)){
1285
    for(imgtype in c("ref","query")){
1286
      input[[paste0("rotate_", imgtype, "_image", i)]] <-  params[[paste0("rotate_", imgtype, "_image", i)]]
1287
      input[[paste0("flipflop_", imgtype, "_image", i)]] <-  params[[paste0("flipflop_", imgtype, "_image", i)]]
1288
      input[[paste0("negate_", imgtype, "_image", i)]] <-  params[[paste0("negate_", imgtype, "_image", i)]]
1289
      input[[paste0("scale_", imgtype, "_image", i)]] <-  params[[paste0("scale_", imgtype, "_image", i)]]
1290
      input[[paste0("channel_", imgtype, "_image", i)]] <-  params[[paste0("channel_", imgtype, "_image", i)]]
1291
    }
1292
  }
1293
  
1294
  input
1295
}
1296
1297
####
1298
# Managing Zoom Options ####
1299
####
1300
1301
#' imageZoom
1302
#'
1303
#' zoom image
1304
#'
1305
#' @param image magick image
1306
#' @param zoom_info zoom info to draw on image
1307
#'
1308
#' @importFrom magick image_info
1309
#' 
1310
#' @noRd
1311
imageZoom <- function(image, zoom_info = NULL){
1312
  
1313
  if(is.null(zoom_info))
1314
    return(image)
1315
  
1316
  # get image info
1317
  imageinfo <- getImageInfo(image)
1318
  
1319
  # get info of zoom
1320
  zoom_info <- FromBoxToCrop(as.data.frame(zoom_info), imageinfo)
1321
  
1322
  # return
1323
  return(zoom_info)
1324
}
1325
1326
#' initiateZoomOptions
1327
#'
1328
#' Initiate shiny reactive values for capturing zoom/brush limits
1329
#'
1330
#' @param info_list the list of image information
1331
#' @param input shiny input
1332
#' @param output shiny output
1333
#' @param session shiny session
1334
#'
1335
#' @noRd
1336
initiateZoomOptions <- function(info_list, input, output, session){
1337
  
1338
  # length of images
1339
  len_images <- length(info_list)
1340
  
1341
  # initiate zoom options list
1342
  zoom_list <- lapply(seq_len(len_images), function(i) {
1343
    list(ref = list(x = c(0, info_list[[i]][1]), y = c(0, info_list[[i]][2])),
1344
         query = list(x = c(0, info_list[[i]][1]), y = c(0, info_list[[i]][2])))
1345
  })
1346
  
1347
  # set names for keypoints
1348
  names(zoom_list) <- paste0(seq_len(len_images))
1349
  
1350
  # return keypoints as reactive values
1351
  do.call("reactiveValues", zoom_list)
1352
}
1353
1354
#' manageImageZoomOptions
1355
#'
1356
#' A list of shiny observe events for handling zoom options of image outputs
1357
#'
1358
#' @param centre center image index
1359
#' @param register_ind query image indices
1360
#' @param zoom_list a list of x,y ranges of query and ref images
1361
#' @param image_list a list of transformed magick image
1362
#' @param info_list the list of image information
1363
#' @param input shiny input
1364
#' @param output shiny output
1365
#' @param session shiny session
1366
#'
1367
#' @noRd
1368
manageImageZoomOptions <- function(centre, register_ind, zoom_list, image_list, info_list, input, output, session){
1369
1370
  # get image types
1371
  image_types <- c("ref","query")
1372
  
1373
  # get the length of tables
1374
  len_tables <- length(zoom_list)
1375
  
1376
  # set click operations for reference and query points
1377
  lapply(seq_len(len_tables), function(i){
1378
    lapply(image_types, function(type){
1379
      
1380
      # listen to click operations for reference/query plots
1381
      observeEvent(input[[paste0("dblclick_plot_", type ,i)]], {
1382
        
1383
        # get brush information
1384
        brush <- input[[paste0("brush_plot_", type ,i)]]
1385
        limits <- cbind(zoom_list[[paste0(i)]][[type]]$x, zoom_list[[paste0(i)]][[type]]$y)
1386
        if (!is.null(brush)) {
1387
          
1388
          # get brush variables
1389
          brush_mat <- data.frame(x = c(brush$xmin, brush$xmax), 
1390
                                  y = c(brush$ymin, brush$ymax))
1391
1392
          # get image
1393
          image <- image_list[[i]]
1394
          
1395
          # get the transformed limits first and calculate width, then record transformed image
1396
          limits_trans <- data.frame(x = limits[,1], y = limits[,2])
1397
          limits_trans <- transformImageKeypoints(image, limits_trans, paste0(type, "_image",i), input)
1398
          image_trans <- limits_trans$image
1399
          limits_trans <- data.frame(x = range(limits_trans$keypoints[,1]), y = range(limits_trans$keypoints[,2]))
1400
1401
          # if width is large, then correct the brush event for the downsize effect
1402
          width <- limits_trans[2,1]-limits_trans[1,1]
1403
          height <- limits_trans[2,2]-limits_trans[1,2]
1404
          if(max(height,width) > 1000){
1405
            if(inherits(image_trans, "Image_Array")){
1406
              n.series <- ImageArray::len(image_trans)
1407
              cur_width <- width
1408
              cur_height <- height
1409
              for(ii in 2:n.series){
1410
                cur_width <- width/(2^(ii-1))
1411
                cur_height <- height/(2^(ii-1))
1412
                if(max(cur_height, cur_width) <= 1000){
1413
                  break
1414
                }
1415
              }
1416
              brush_mat <- brush_mat*width/ceiling(cur_width)
1417
            } else {
1418
              brush_mat <- brush_mat*width/1000
1419
            }
1420
          }
1421
1422
          # correct brush for the zoom effect
1423
          brush_mat[,1] <- brush_mat[,1] + limits_trans[1,1]
1424
          brush_mat[,2] <- brush_mat[,2] + limits_trans[1,2]
1425
1426
          # correct for flipflop and rotate using the transformed image from above
1427
          brush_mat <- transformKeypoints(image_trans, as.data.frame(brush_mat), paste0(type, "_image",i), input)
1428
          brush_mat <- data.frame(x = range(brush_mat[,1]), y = range(brush_mat[,2]))
1429
          brush_mat <- as.matrix(brush_mat)
1430
1431
          # make new zoom information
1432
          zoom_list[[paste0(i)]][[type]]$x <- brush_mat[,1]
1433
          zoom_list[[paste0(i)]][[type]]$y <- brush_mat[,2]
1434
        
1435
        } else {
1436
          zoom_list[[paste0(i)]][[type]]$x <- c(0, info_list[[i]][1])
1437
          zoom_list[[paste0(i)]][[type]]$y <- c(0, info_list[[i]][2])
1438
        }
1439
      })
1440
    })
1441
  })
1442
}
1443
1444
####
1445
# Managing Images ####
1446
####
1447
1448
#' getImageOutput
1449
#'
1450
#' Shiny outputs for a set of magick images with keypoints
1451
#'
1452
#' @param image_list a list of magick images
1453
#' @param info_list a list of magick image info on width and height
1454
#' @param keypoints_list a list of data frames, each having a set of keypoints
1455
#' @param zoom_list a list of x,y ranges of query and ref images
1456
#' @param centre the center image index
1457
#' @param input shiny input
1458
#' @param output shiny output
1459
#' @param session shiny session
1460
#'
1461
#' @importFrom magick image_ggplot image_resize image_crop geometry_size_percent
1462
#'
1463
#' @noRd
1464
getImageOutput <- function(image_list, info_list, keypoints_list = NULL, zoom_list, centre, input, output, session){
1465
1466
  # get image types
1467
  image_types <- c("ref","query")
1468
1469
  # get the length of images
1470
  len_images <- length(image_list)
1471
1472
  # output query images
1473
  lapply(seq_len(len_images), function(i){
1474
    lapply(image_types, function(type){
1475
1476
      # image output
1477
      output[[paste0("plot_", type, i)]] <- renderPlot({
1478
1479
        # select keypoints
1480
        ref_ind <- ifelse(type == "ref", i, i-1) # select reference image
1481
        keypoints <- keypoints_list[[paste0(ref_ind, "-", ref_ind+1)]][[type]]
1482
        
1483
        # transform image and keypoints
1484
        img <- image_list[[i]][[input[[paste0("channel_", type, "_image", i)]]]]
1485
        img_trans <- transformImageKeypoints(img, keypoints, paste0(type, "_image",i), input, session)
1486
        
1487
        # zoom images and keypoints
1488
        limits <- as.data.frame(zoom_list[[paste0(i)]][[type]])
1489
        img_limits <- transformImageKeypoints(img, limits, paste0(type, "_image",i), input, session)
1490
        img_limits$keypoints <- data.frame(x = range(img_limits$keypoints[,1]), y = range(img_limits$keypoints[,2]))
1491
        imgzoom <- imageZoom(img_trans$image, zoom_info = img_limits$keypoints)
1492
        if(!is.null(img_trans$keypoints)){
1493
          if(nrow(img_trans$keypoints) > 0){
1494
            temp <- as.matrix(img_trans$keypoints[,c("x","y")])
1495
            temp <- temp - matrix(unlist(rep(img_limits$keypoints[1,], nrow(img_trans$keypoints))), nrow = nrow(img_trans$keypoints), byrow = T)
1496
            img_trans$keypoints[,c("x","y")] <- temp
1497
          }
1498
        }
1499
        
1500
        # img_trans$image <- magick::image_crop(img_trans$image, geometry = imgzoom)
1501
        img_trans$image <- cropImage(img_trans$image, geometry = imgzoom)
1502
        
1503
        # lower resolution
1504
        width <- img_limits$keypoints[2,1]-img_limits$keypoints[1,1]
1505
        height <- img_limits$keypoints[2,2]-img_limits$keypoints[1,2]
1506
        if(max(height, width) > 1000){
1507
          
1508
          # scale keypoints
1509
          if(inherits(img_trans$image, "Image_Array")){
1510
            n.series <- ImageArray::len(img_trans$image)
1511
            cur_width <- width
1512
            cur_height <- height
1513
            for(ii in 2:n.series){
1514
              cur_width <- width/(2^(ii-1))
1515
              cur_height <- height/(2^(ii-1))
1516
              if(max(cur_height, cur_width) <= 1000){
1517
                break
1518
              }
1519
            }
1520
            img_trans$keypoints[,c("x","y")] <- img_trans$keypoints[,c("x","y")]*(cur_width/width)
1521
          } else {
1522
            img_trans$keypoints[,c("x","y")] <- img_trans$keypoints[,c("x","y")]*(1000/width)
1523
          }
1524
        } 
1525
1526
1527
        # visualize
1528
        img_ggplot <- plotImage(img_trans$image, max.pixel.size = 1000)
1529
        img_ggplot <- imageKeypoint(img_ggplot, img_trans$keypoints)
1530
        
1531
        # return
1532
        return(img_ggplot)
1533
      })
1534
1535
      # update info
1536
      output[[paste0("scaleinfo_", type, "_image", i)]] <- renderText({
1537
        cur_info <- info_list[[i]] * input[[paste0("scale_", type, "_image", i)]]
1538
        paste(cur_info, collapse = "x")
1539
      })
1540
    })
1541
  })
1542
}
1543
1544
#' plotImage
1545
#'
1546
#' plot image
1547
#'
1548
#' @param image a magick image or DelayedArray object
1549
#' 
1550
#' @importFrom magick image_ggplot
1551
#'
1552
#' @noRd
1553
plotImage <- function(image, max.pixel.size = NULL){
1554
  
1555
  if(inherits(image, "magick-image")){
1556
    imageinfo <- getImageInfo(image)
1557
    if(!is.null(max.pixel.size)){
1558
      if(max(imageinfo$width, imageinfo$height) > max.pixel.size){
1559
        image <- magick::image_resize(image, geometry = as.character(max.pixel.size))
1560
      } 
1561
    }
1562
    imgggplot <- magick::image_ggplot(image)
1563
  } else if(inherits(image, "Image_Array")){
1564
    img_raster <- as.raster(image, max.pixel.size = max.pixel.size)
1565
    info <- list(width = dim(img_raster)[2], height = dim(img_raster)[1])
1566
    imgggplot <- ggplot2::ggplot(data.frame(x = 0, y = 0), ggplot2::aes_string("x", "y")) + 
1567
      ggplot2::geom_blank() + 
1568
      ggplot2::theme_void() + 
1569
      ggplot2::coord_fixed(expand = FALSE, 
1570
                           xlim = c(0, info$width), 
1571
                           ylim = c(0, info$height)) + 
1572
      ggplot2::annotation_raster(img_raster, 0, info$width, info$height, 0, interpolate = FALSE)
1573
  }
1574
  imgggplot
1575
}
1576
1577
#' getImageInfoList
1578
#'
1579
#' get information on list of images
1580
#'
1581
#' @param image_list a list of magick images or DelayedArray objects
1582
#'
1583
#' @noRd
1584
getImageInfoList <- function(image_list){
1585
  lapply(image_list, function(x){
1586
    imginfo <- getImageInfo(x)
1587
    c(imginfo$width, imginfo$height)
1588
  })
1589
}
1590
1591
#' getImageInfo
1592
#'
1593
#' get information on images
1594
#'
1595
#' @param image a magick image or DelayedArray object
1596
#'
1597
#' @importFrom magick image_info
1598
#'
1599
#' @noRd
1600
getImageInfo <- function(image){
1601
  
1602
  if(inherits(image, "magick-image")){
1603
    imginfo <- magick::image_info(image)
1604
  } else if(inherits(image, "Image_Array")){
1605
    imginfo <- ImageArray::getImageInfo(image)
1606
  }
1607
  as.data.frame(imginfo)
1608
}
1609
1610
#' rotateImage
1611
#'
1612
#' rotate images
1613
#'
1614
#' @param image a magick image or DelayedArray object
1615
#' @param degrees value between 0 and 360 for how many degrees to rotate
1616
#'
1617
#' @importFrom magick image_rotate
1618
#'
1619
#' @noRd
1620
rotateImage <- function(image, degrees){
1621
  
1622
  if(inherits(image, "magick-image")){
1623
    image <- magick::image_rotate(image, degrees = degrees)
1624
  } else if(inherits(image, "Image_Array")){
1625
    image <- ImageArray::rotate(image, degrees)
1626
  }
1627
  image
1628
}
1629
1630
#' negateImage
1631
#'
1632
#' negate images
1633
#'
1634
#' @param image a magick image or DelayedArray object
1635
#' 
1636
#' @importFrom magick image_negate
1637
#'
1638
#' @noRd
1639
negateImage <- function(image){
1640
  
1641
  if(inherits(image, "magick-image")){
1642
    image <- magick::image_negate(image)
1643
  } else if(inherits(image, "Image_Array")){
1644
    image <- ImageArray::negate(image)
1645
  }
1646
  image
1647
}
1648
1649
#' flipImage
1650
#'
1651
#' flip images
1652
#'
1653
#' @param image a magick image or DelayedArray object
1654
#' 
1655
#' @importFrom magick image_negate
1656
#'
1657
#' @noRd
1658
flipImage <- function(image){
1659
  
1660
  if(inherits(image, "magick-image")){
1661
    image <- magick::image_flip(image)
1662
  } else if(inherits(image, "Image_Array")){
1663
    image <- ImageArray::flip(image)
1664
  }
1665
  image
1666
}
1667
1668
#' flopImage
1669
#'
1670
#' flop images
1671
#'
1672
#' @param image a magick image or DelayedArray object
1673
#' 
1674
#' @importFrom magick image_negate
1675
#'
1676
#' @noRd
1677
flopImage <- function(image){
1678
  
1679
  if(inherits(image, "magick-image")){
1680
    image <- magick::image_flop(image)
1681
  } else if(inherits(image, "Image_Array")){
1682
    image <- ImageArray::flop(image)
1683
  }
1684
  image
1685
}
1686
1687
#' cropImage
1688
#'
1689
#' crop images
1690
#'
1691
#' @param image a magick image or DelayedArray object
1692
#' @param geometry a geometry string specifying area (for cropping) or size (for resizing).
1693
#' 
1694
#' @importFrom magick image_crop
1695
#'
1696
#' @noRd
1697
cropImage <- function(image, geometry){
1698
  
1699
  if(inherits(image, "magick-image")){
1700
    image <- magick::image_crop(image, geometry = geometry)
1701
  } else if(inherits(image, "Image_Array")){
1702
    crop_info_int <- as.integer(strsplit(geometry, split = "[x|+]")[[1]])
1703
    image <- ImageArray::crop(image, ind = list(crop_info_int[3]:(crop_info_int[3]+crop_info_int[1]), crop_info_int[4]:(crop_info_int[4]+crop_info_int[2])))
1704
  }
1705
  image
1706
}
1707
1708
#' resizeImage
1709
#'
1710
#' resize images
1711
#'
1712
#' @param image a magick image or DelayedArray object
1713
#' @param geometry a geometry string specifying area (for cropping) or size (for resizing).
1714
#' 
1715
#' @importFrom magick image_resize image_info image_read geometry_size_percent
1716
#'
1717
#' @noRd
1718
resize_Image <- function(image, geometry){
1719
  
1720
  # get image info
1721
  image_info_large <- getImageInfo(image)
1722
  
1723
  if(inherits(image, "magick-image")){
1724
    image <- magick::image_resize(image, geometry = geometry)
1725
  } else if(inherits(image, "Image_Array")){
1726
    
1727
    # get scale factor 
1728
    if(grepl("%$", geometry)){
1729
      scale_factor <- as.numeric(gsub("%$", "", geometry))/100
1730
    } else if(grepl("x$", geometry)){
1731
      scale_factor <- (as.numeric(gsub("x$", "", geometry))/image_info_large$width)
1732
    }
1733
1734
    # get scaled array 
1735
    scaled_image_info <- ceiling(image_info_large*scale_factor)
1736
    image <- as.array(image, min.pixel.size = max(scaled_image_info))
1737
    
1738
    # convert to magick image
1739
    image <- magick::image_read(array(as.raw(image), dim = dim(image)))
1740
    image_info <- magick::image_info(image)
1741
    image <- magick::image_resize(image, geometry = geometry_size_percent(100*scaled_image_info[1]/image_info$width))
1742
  }
1743
  image
1744
}
1745
1746
#' transformImage
1747
#'
1748
#' Apply given transformations to a magick image
1749
#'
1750
#' @param image magick image
1751
#' @param extension name extension for the shiny input parameter
1752
#' @param input shiny input
1753
#'
1754
#' @importFrom magick image_flip image_flop image_rotate
1755
#'
1756
#' @noRd
1757
transformImage <- function(image, extension, input){
1758
1759
  # rotate image and keypoints
1760
  input_rotate <- as.numeric(input[[paste0("rotate_", extension)]])
1761
  image <- rotateImage(image, input_rotate)
1762
  
1763
  # flip flop image and keypoints
1764
  input_flipflop <- input[[paste0("flipflop_", extension)]]
1765
  if(input_flipflop == "Flip"){
1766
    image <- flipImage(image)
1767
  } else if(input_flipflop == "Flop"){
1768
    image <- flopImage(image)
1769
  }
1770
1771
  # return image
1772
  image
1773
}
1774
1775
#' transformImageReverse
1776
#'
1777
#' Apply given transformations to a magick image in reverse fashion
1778
#'
1779
#' @param image magick image
1780
#' @param extension name extension for the shiny input parameter
1781
#' @param input shiny input
1782
#'
1783
#' @importFrom magick image_flip image_flop image_rotate
1784
#'
1785
#' @noRd
1786
transformImageReverse <- function(image, extension, input){
1787
1788
  # flip flop image and keypoints
1789
  input_flipflop <- input[[paste0("flipflop_", extension)]]
1790
  if(input_flipflop == "Flip"){
1791
    image <- flipImage(image)
1792
  } else if(input_flipflop == "Flop"){
1793
    image <- flopImage(image)
1794
  }
1795
1796
  # rotate image and keypoints
1797
  input_rotate <- 360 - as.numeric(input[[paste0("rotate_", extension)]])
1798
  image <- rotateImage(image, input_rotate)
1799
1800
  # return image
1801
  image
1802
}
1803
1804
#' transformImageQueryList
1805
#'
1806
#' Apply given transformations to a list of magick image and return shiny reactive
1807
#'
1808
#' @param image_list magick image
1809
#' @param input shiny input
1810
#'
1811
#' @noRd
1812
transformImageQueryList <- function(image_list, input){
1813
1814
  # length of images
1815
  len_register <- length(image_list) - 1
1816
1817
  trans_query_list <- lapply(seq_len(len_register), function(i){
1818
    reactive({
1819
      list(ref = transformImage(image_list[[i]], paste0("ref_image",i), input),
1820
           query = transformImage(image_list[[i+1]], paste0("query_image",i+1), input))
1821
    })
1822
  })
1823
1824
  ####
1825
  names(trans_query_list) <- paste0(seq_len(length(image_list)-1),"-",2:length(image_list)) # REMOVE LATER, or decide not to
1826
  ####
1827
1828
  return(trans_query_list)
1829
}
1830
1831
#' getRcppWarpImage
1832
#'
1833
#' Warping a query image given a homography image
1834
#'
1835
#' @param ref_image reference image
1836
#' @param query_image query image
1837
#' @param mapping a list of the homography matrices and TPS keypoints 
1838
#'
1839
#' @importFrom magick image_read image_data
1840
#' 
1841
#' @export
1842
getRcppWarpImage <- function(ref_image, query_image, mapping){
1843
  
1844
  # ref image
1845
  if(inherits(ref_image, "Image_Array")){
1846
    ref_image <- as.array(ref_image)
1847
    ref_image <- array(as.raw(ref_image), dim = dim(ref_image))
1848
  } else {
1849
    ref_image <- magick::image_data(ref_image, channels = "rgb")
1850
  }
1851
  
1852
  # query image
1853
  if(inherits(query_image, "Image_Array")){
1854
    query_image <- as.array(query_image)
1855
    query_image <- array(as.raw(query_image), dim = dim(query_image))
1856
  } else {
1857
    query_image <- magick::image_data(query_image, channels = "rgb")
1858
  }
1859
  
1860
  # warp image
1861
  query_image <- warpImage(ref_image = ref_image, 
1862
                           query_image = query_image, 
1863
                           mapping = mapping,
1864
                           width1 = dim(ref_image)[2], height1 = dim(ref_image)[3],
1865
                           width2 = dim(query_image)[2], height2 = dim(query_image)[3])
1866
  magick::image_read(query_image)
1867
}
1868
1869
####
1870
# Manual Image Registration ####
1871
####
1872
1873
#' initiateMappings
1874
#'
1875
#' Initiate shiny reactive values for registration matrices
1876
#'
1877
#' @param len_images the number of query images
1878
#' @param input shiny input
1879
#' @param output shiny output
1880
#' @param session shiny session
1881
#'
1882
#' @noRd
1883
initiateMappings <- function(len_images, input, output, session){
1884
1885
  # initiate matrices
1886
  matrix_list <- lapply(seq_len(len_images), function(i) return(NULL))
1887
  names(matrix_list) <- seq_len(len_images)
1888
1889
  # return matrices as reactive values
1890
  do.call("reactiveValues", matrix_list)
1891
}
1892
1893
#' getManualRegisteration
1894
#'
1895
#' Manual registration of images using manually entered keypoints
1896
#'
1897
#' @param registration_mapping_list a list of mapping matrices used for registering VoltRon objects
1898
#' @param spatdata_list a list of Spatial data object of the query images
1899
#' @param image_list the list of query images
1900
#' @param keypoints_list a list of keypoints x,y coordinates for query image
1901
#' @param centre center image index
1902
#' @param register_ind query image indices
1903
#' @param input shiny input
1904
#' @param output shiny output
1905
#' @param session shiny session
1906
#'
1907
#' @import ggplot2
1908
#' @importFrom magick image_write image_join image_read image_resize
1909
#' @importFrom shiny reactiveValuesToList
1910
#'
1911
#' @noRd
1912
getManualRegisteration <- function(registration_mapping_list, spatdata_list, image_list, keypoints_list,
1913
                                   centre, register_ind, input, output, session){
1914
1915
  # the number of registrations
1916
  len_register <- length(image_list) - 1
1917
1918
  # Registration events
1919
  observeEvent(input$register, {
1920
1921
    # get key points as list
1922
    keypoints_list <- shiny::reactiveValuesToList(keypoints_list)
1923
    
1924
    # Manual Registration
1925
    if(!input$automatictag){
1926
1927
      # waiter start
1928
      withProgress(message = paste0('Manual Registration (', input$Method, ')'), value = 0, {
1929
1930
        # Check keypoints
1931
        checkKeypoints(keypoints_list)
1932
  
1933
        # Register keypoints
1934
        aligned_image_list <- list()
1935
        for(i in register_ind){
1936
  
1937
          # Increment the progress bar, and update the detail text.
1938
          incProgress(1/length(register_ind), detail = paste("Registering Image", i, sep = " "))
1939
  
1940
          # get a sequential mapping between a query and reference image
1941
          results <- computeManualPairwiseTransform(image_list, keypoints_list, query_ind = i, ref_ind = centre, input = input)
1942
  
1943
          # save transformation mapping
1944
          registration_mapping_list[[paste0(i)]] <- results$mapping
1945
  
1946
          # save matches
1947
          aligned_image_list[[i]] <- results$aligned_image
1948
        }
1949
1950
      })
1951
1952
      # Plot registered images
1953
      lapply(register_ind, function(i){
1954
        output[[paste0("plot_query_reg",i)]] <- renderImage({
1955
1956
          # get image list
1957
          image_view_list <- list(rep(resize_Image(image_list[[centre]], geometry = "400x"),5),
1958
                                  rep(resize_Image(aligned_image_list[[i]], geometry = "400x"),5))
1959
1960
          # make slide show
1961
          image_view_list <- image_view_list %>%
1962
            magick::image_join() %>%
1963
            magick::image_write(tempfile(fileext='gif'), format = 'gif')
1964
          list(src = image_view_list, contentType = "image/gif")
1965
        }, deleteFile = TRUE)
1966
      })
1967
1968
      # Output summary
1969
      output[["summary"]] <- renderUI({
1970
        str1 <- paste0(" Registration Summary:")
1971
        str2 <- paste0("# of Images: ", length(image_list))
1972
        str3 <- paste0("# of Registrations: ", len_register)
1973
        all_str <- c(str1, str2, str3)
1974
        shiny::HTML(paste(all_str, collapse = '<br/>'))
1975
      })
1976
    }
1977
  })
1978
}
1979
1980
#' computeManualPairwiseTransform
1981
#'
1982
#' Computing transformation matrix of manual registration
1983
#'
1984
#' @param image_list the list of images
1985
#' @param keypoints_list the list of keypoint matrices
1986
#' @param query_ind the index of the query image
1987
#' @param ref_ind the index of the reference image
1988
#' @param input input
1989
#'
1990
#' @noRd
1991
computeManualPairwiseTransform <- function(image_list, keypoints_list, query_ind, ref_ind, input){
1992
1993
  # determine the number of transformation to map from query to the reference
1994
  indices <- query_ind:ref_ind
1995
  mapping_mat <- rep(indices,c(1,rep(2,length(indices)-2),1))
1996
  mapping_mat <- matrix(mapping_mat,ncol=2,byrow=TRUE)
1997
1998
  # reference and target landmarks/keypoints
1999
  mapping <- list()
2000
  aligned_image <- image_list[[query_ind]]
2001
  for(kk in seq_len(nrow(mapping_mat))){
2002
    cur_map <- mapping_mat[kk,]
2003
    ref_image <- image_list[[cur_map[2]]]
2004
    if(which.min(cur_map) == 1){
2005
      key_ind <- paste0(cur_map[1], "-", cur_map[2])
2006
      keypoints <- keypoints_list[[key_ind]]
2007
      target_landmark <- as.matrix(keypoints[["ref"]][,c("x","y")])
2008
      reference_landmark <- as.matrix(keypoints[["query"]][,c("x","y")])
2009
    } else {
2010
      key_ind <- paste0(cur_map[2], "-", cur_map[1])
2011
      keypoints <- keypoints_list[[key_ind]]
2012
      reference_landmark <- as.matrix(keypoints[["ref"]][,c("x","y")])
2013
      target_landmark <- as.matrix(keypoints[["query"]][,c("x","y")])
2014
    }
2015
2016
    if(which.max(cur_map) == 1){
2017
      ref_label = "ref"
2018
      query_label = "query"
2019
    } else {
2020
      ref_label = "query"
2021
      query_label = "ref"
2022
    }
2023
2024
    # get registered image (including all channels)
2025
    reg <- getRcppManualRegistration(aligned_image, ref_image, target_landmark, reference_landmark, 
2026
                                         method = input$Method)
2027
    
2028
    # return transformation matrix and images
2029
    mapping[[kk]] <- list(reg$transmat[[1]], 
2030
                          list(reference = reg$transmat[[2]][[1]],
2031
                               query = reg$transmat[[2]][[2]]))
2032
    aligned_image <- reg$aligned_image
2033
  }
2034
2035
  return(list(mapping = mapping, 
2036
              aligned_image = aligned_image))
2037
}
2038
2039
#' getRcppManualRegistration
2040
#'
2041
#' Manual registration workflow with Rcpp
2042
#'
2043
#' @param query_image query image
2044
#' @param ref_image reference image
2045
#' @param query_landmark query landmark points
2046
#' @param reference_landmark refernece landmark points
2047
#' @param method the automated registration method, either TPS or Homography+TPS
2048
#'
2049
#' @importFrom magick image_read image_data
2050
#'
2051
#' @export
2052
getRcppManualRegistration <- function(query_image, ref_image, query_landmark, reference_landmark, 
2053
                                      method = "TPS") {
2054
  
2055
  # ref image
2056
  if(inherits(ref_image, "Image_Array")){
2057
    ref_image <- as.array(ref_image)
2058
    ref_image <- array(as.raw(ref_image), dim = dim(ref_image))
2059
  } else {
2060
    ref_image <- magick::image_data(ref_image, channels = "rgb")
2061
  }
2062
  
2063
  # query image
2064
  if(inherits(query_image, "Image_Array")){
2065
    query_image <- as.array(query_image)
2066
    query_image <- array(as.raw(query_image), dim = dim(query_image))
2067
  } else {
2068
    query_image <- magick::image_data(query_image, channels = "rgb")
2069
  }
2070
  
2071
  reference_landmark[,2] <- dim(ref_image)[3] - reference_landmark[,2]
2072
  query_landmark[,2] <- dim(query_image)[3] - query_landmark[,2]
2073
  reg <- manual_registeration_rawvector(ref_image = ref_image, query_image = query_image,
2074
                                        reference_landmark = reference_landmark, query_landmark = query_landmark,
2075
                                        width1 = dim(ref_image)[2], height1 = dim(ref_image)[3],
2076
                                        width2 = dim(query_image)[2], height2 = dim(query_image)[3], 
2077
                                        method = method)
2078
  return(list(transmat = reg[[1]], 
2079
              aligned_image = magick::image_read(reg[[2]])))
2080
}
2081
2082
####
2083
# Automated Image Registration ####
2084
####
2085
2086
#' getManualRegisteration
2087
#'
2088
#' Manual registeration of images using manually entered keypoints
2089
#'
2090
#' @param registration_mapping_list a list of mapping matrices used for registering VoltRon objects
2091
#' @param spatdata_list a list of Spatial data object of the query images
2092
#' @param image_list the list of query images
2093
#' @param channel_names the list of channel names for each image
2094
#' @param centre center image index
2095
#' @param register_ind query image indices
2096
#' @param input shiny input
2097
#' @param output shiny output
2098
#' @param session shiny session
2099
#'
2100
#' @importFrom magick image_info image_ggplot image_write image_join image_resize
2101
#' @importFrom grid rasterGrob
2102
#' @importFrom ggplot2 ggplot coord_fixed annotation_raster annotation_custom
2103
#'
2104
#' @noRd
2105
getAutomatedRegisteration <- function(registration_mapping_list, spatdata_list, image_list, channel_names, centre, register_ind,
2106
                                      input, output, session){
2107
2108
  # the number of registrations
2109
  len_register <- length(image_list) - 1
2110
2111
  # Registration events
2112
  observeEvent(input$register, {
2113
    
2114
    # Automated registration
2115
    if(input$automatictag){
2116
2117
      # waiter start
2118
      withProgress(message = paste0('Automated Registration (', input$Method,')'), value = 0, {
2119
2120
        # Register keypoints
2121
        dest_image_list <- list()
2122
        overlayed_image_list <- list()
2123
        aligned_image_list <- list()
2124
        alignment_image_list <- list()
2125
        for(i in register_ind){
2126
  
2127
          # Increment the progress bar, and update the detail text.
2128
          incProgress(1/length(register_ind), detail = paste("Registering Image", i, sep = " "))
2129
  
2130
          # get a sequential mapping between a query and reference image
2131
          results <- computeAutomatedPairwiseTransform(image_list, channel_names, query_ind = i, ref_ind = centre, input)
2132
  
2133
          # save transformation matrix
2134
          registration_mapping_list[[paste0(i)]] <- results$mapping
2135
  
2136
          # destination image
2137
          dest_image_list[[i]] <- results$dest_image
2138
  
2139
          # save aligned images
2140
          aligned_image_list[[i]] <- results$aligned_image
2141
  
2142
          # save alignment
2143
          overlayed_image_list[[i]] <- results$overlay_image
2144
  
2145
          # save matches
2146
          alignment_image_list[[i]] <- results$alignment_image
2147
        }
2148
2149
      })
2150
2151
      # Plot registered images
2152
      lapply(register_ind, function(i){
2153
        output[[paste0("plot_query_reg",i)]] <- renderImage({
2154
2155
          # get images
2156
          image_view_list <- list(rep(magick::image_resize(dest_image_list[[i]], geometry = "400x"),5),
2157
                                  rep(magick::image_resize(overlayed_image_list[[i]], geometry = "400x"),5))
2158
2159
          # make slide show
2160
          image_view_list <- image_view_list %>%
2161
            magick::image_join() %>%
2162
            magick::image_write(tempfile(fileext='gif'), format = 'gif')
2163
          list(src = image_view_list, contentType = "image/gif")
2164
        }, deleteFile = TRUE)
2165
      })
2166
2167
      # Plot Alignment
2168
      lapply(register_ind, function(i){
2169
        cur_alignment_image <- alignment_image_list[[i]]
2170
        output[[paste0("plot_alignment",i)]] <- renderPlot({
2171
          magick::image_ggplot(cur_alignment_image)
2172
        })
2173
      })
2174
2175
      # Output summary
2176
      output[["summary"]] <- renderUI({
2177
        str1 <- paste0(" Registration Summary:")
2178
        str2 <- paste0("# of Images: ", length(image_list))
2179
        str3 <- paste0("# of Registrations: ", len_register)
2180
        all_str <- c(str1, str2, str3)
2181
        shiny::HTML(paste(all_str, collapse = '<br/>'))
2182
      })
2183
    }
2184
  })
2185
}
2186
2187
#' computeAutomatedPairwiseTransform
2188
#'
2189
#' Computing the registration matrix necessary for automated registration
2190
#'
2191
#' @param image_list the list of images
2192
#' @param channel_names the list of channel names for each image
2193
#' @param query_ind the index of the query image
2194
#' @param ref_ind the index of the reference image
2195
#' @param input input
2196
#'
2197
#' @noRd
2198
computeAutomatedPairwiseTransform <- function(image_list, channel_names, query_ind, ref_ind, input){
2199
2200
  # determine the number of transformation to map from query to the reference
2201
  indices <- query_ind:ref_ind
2202
  mapping_mat <- rep(indices,c(1,rep(2,length(indices)-2),1))
2203
  mapping_mat <- matrix(mapping_mat,ncol=2,byrow=TRUE)
2204
2205
  # reference and target landmarks/keypoints
2206
  mapping <- list()
2207
  query_image <- image_list[[query_ind]]
2208
  for(kk in seq_len(nrow(mapping_mat))){
2209
    cur_map <- mapping_mat[kk,]
2210
    ref_image <- image_list[[cur_map[2]]]
2211
2212
    # compute and get transformation matrix
2213
    if(which.max(cur_map) == 1){
2214
      ref_label = "ref"
2215
      query_label = "query"
2216
    } else {
2217
      ref_label = "query"
2218
      query_label = "ref"
2219
    }
2220
2221
    # get channels 
2222
    query_image <- query_image[[input[[paste0("channel_", query_label, "_image", cur_map[1])]]]]
2223
    ref_image <- ref_image[[input[[paste0("channel_", ref_label, "_image", cur_map[2])]]]]
2224
    
2225
    # scale parameters
2226
    query_scale <- input[[paste0("scale_", query_label, "_image", cur_map[1])]]
2227
    ref_scale <- input[[paste0("scale_", ref_label, "_image", cur_map[2])]]
2228
2229
    # scale images
2230
    query_image <- resize_Image(query_image, geometry = magick::geometry_size_percent(100*query_scale))
2231
    ref_image <- resize_Image(ref_image, geometry = magick::geometry_size_percent(100*ref_scale))
2232
2233
    # register images with OpenCV
2234
    reg <- getRcppAutomatedRegistration(ref_image = ref_image, query_image = query_image,
2235
                                        GOOD_MATCH_PERCENT = as.numeric(input$GOOD_MATCH_PERCENT), MAX_FEATURES = as.numeric(input$MAX_FEATURES),
2236
                                        invert_query = input[[paste0("negate_", query_label, "_image", cur_map[1])]] == "Yes",
2237
                                        invert_ref = input[[paste0("negate_", ref_label, "_image", cur_map[2])]] == "Yes",
2238
                                        flipflop_query = input[[paste0("flipflop_", query_label, "_image", cur_map[1])]],
2239
                                        flipflop_ref = input[[paste0("flipflop_", ref_label, "_image", cur_map[2])]],
2240
                                        rotate_query = input[[paste0("rotate_", query_label, "_image", cur_map[1])]],
2241
                                        rotate_ref = input[[paste0("rotate_", ref_label, "_image", cur_map[2])]],
2242
                                        matcher = input$Matcher, method = input$Method)
2243
2244
    # update transformation matrix
2245
    reg[[1]][[1]] <- solve(diag(c(ref_scale,ref_scale,1))) %*% reg[[1]][[1]] %*% diag(c(query_scale,query_scale,1))
2246
2247
    # return transformation matrix and images
2248
    mapping[[kk]] <- reg[[1]]
2249
    dest_image <- reg$dest_image
2250
    aligned_image <- reg$aligned_image
2251
    alignment_image <- reg$alignment_image
2252
    overlay_image <- reg$overlay_image
2253
  }
2254
2255
  return(list(mapping = mapping, 
2256
              dest_image = dest_image, 
2257
              aligned_image = aligned_image, 
2258
              alignment_image = alignment_image, 
2259
              overlay_image = overlay_image))
2260
}
2261
2262
#' getRcppAutomatedRegistration
2263
#'
2264
#' Automated registration workflos with Rcpp
2265
#'
2266
#' @param ref_image reference image
2267
#' @param query_image query image
2268
#' @param GOOD_MATCH_PERCENT the percentage of good matching keypoints, used by "Brute force" method
2269
#' @param MAX_FEATURES maximum number of detected features, i.e. keypoints, used by "Brute force" method
2270
#' @param invert_query invert query image?
2271
#' @param invert_ref invert reference image
2272
#' @param flipflop_query flip or flop the query image
2273
#' @param flipflop_ref flip or flop the reference image
2274
#' @param rotate_query rotation of query image
2275
#' @param rotate_ref rotation of reference image
2276
#' @param matcher the matching method for landmarks/keypoints FLANN or BRUTE-FORCE
2277
#' @param method the automated registration method, Homography or Homography+TPS
2278
#'
2279
#' @importFrom magick image_read image_data
2280
#'
2281
#' @export
2282
getRcppAutomatedRegistration <- function(ref_image, query_image,
2283
                                         GOOD_MATCH_PERCENT = 0.15, MAX_FEATURES = 500,
2284
                                         invert_query = FALSE, invert_ref = FALSE,
2285
                                         flipflop_query = "None", flipflop_ref = "None",
2286
                                         rotate_query = "0", rotate_ref = "0", 
2287
                                         matcher = "FLANN", method = "Homography") {
2288
  ref_image_rast <- magick::image_data(ref_image, channels = "rgb")
2289
  query_image_rast <- magick::image_data(query_image, channels = "rgb")
2290
  reg <- automated_registeration_rawvector(ref_image = ref_image_rast, query_image = query_image_rast,
2291
                                           width1 = dim(ref_image_rast)[2], height1 = dim(ref_image_rast)[3],
2292
                                           width2 = dim(query_image_rast)[2], height2 = dim(query_image_rast)[3],
2293
                                           GOOD_MATCH_PERCENT = GOOD_MATCH_PERCENT, MAX_FEATURES = MAX_FEATURES,
2294
                                           invert_query = invert_query, invert_ref = invert_ref,
2295
                                           flipflop_query = flipflop_query, flipflop_ref = flipflop_ref,
2296
                                           rotate_query = rotate_query, rotate_ref = rotate_ref,
2297
                                           matcher = matcher, method = method)
2298
  
2299
  # check for null keypoints
2300
  if(suppressWarnings(all(lapply(reg[[1]][[2]], is.null)))){
2301
    reg[[1]] <- list(reg[[1]][[1]], NULL)
2302
  }
2303
  
2304
  return(list(transmat = reg[[1]],
2305
              dest_image = magick::image_read(reg[[2]]),
2306
              aligned_image = magick::image_read(reg[[3]]),
2307
              alignment_image = magick::image_read(reg[[4]]),
2308
              overlay_image = magick::image_read(reg[[5]])))
2309
}
2310
2311
####
2312
# Non-interactive Image Registration ####
2313
####
2314
2315
#' getNonInteractiveRegistration
2316
#'
2317
#' Non-interactive registration of spatial data 
2318
#'
2319
#' @param obj_list a list of VoltRon objects
2320
#' @param centre the index of the central reference image/spatialdata
2321
#' @param register_ind the indices of query images/spatialdatasets
2322
#' @param mapping_parameters mapping parameters
2323
#' @param image_list the list of query/ref images (with main channel)
2324
#' @param image_list_full the list of query/ref images (with all channels)
2325
#' @param channel_names the list of channel names for each image
2326
#' 
2327
#' @noRd
2328
getNonInteractiveRegistration <- function(obj_list, 
2329
                                          centre, 
2330
                                          register_ind, 
2331
                                          mapping_parameters = NULL,
2332
                                          image_list = NULL,
2333
                                          image_list_full = NULL, 
2334
                                          channel_names = NULL){
2335
  
2336
  # check mapping parameters 
2337
  if(is.null(mapping_parameters)){
2338
    stop("'mapping_parameters' is not provided, please run registerSpatialData once and save contents of 'mapping_parameters' for later use.")
2339
    
2340
  }
2341
  
2342
  # Register images
2343
  registration_mapping_list <- list()
2344
  for(i in register_ind){
2345
    
2346
    # Increment the progress bar, and update the detail text.
2347
    message("Registering Image ", i)
2348
2349
    # get a sequential mapping between a query and reference image
2350
    results <- switch(mapping_parameters$automatictag, 
2351
           "auto" = {
2352
             computeAutomatedPairwiseTransform(image_list = image_list_full, 
2353
                                               channel_names = channel_names, 
2354
                                               query_ind = i, 
2355
                                               ref_ind = centre, 
2356
                                               input = mapping_parameters)
2357
           }, 
2358
           "manual" = {
2359
             checkKeypoints(mapping_parameters$keypoints)
2360
             computeManualPairwiseTransform(image_list = image_list, 
2361
                                            keypoints_list = mapping_parameters$keypoints, 
2362
                                            query_ind = i, 
2363
                                            ref_ind = centre, 
2364
                                            input = mapping_parameters)
2365
           })
2366
    
2367
    # save transformation matrix
2368
    registration_mapping_list[[paste0(i)]] <- results$mapping
2369
  }
2370
  
2371
  # return the list of registered voltron objects
2372
  return(
2373
    list(keypoints = mapping_parameters$keypoints,
2374
         mapping_parameters = mapping_parameters,
2375
         registered_spat = getRegisteredObjectNonShiny(obj_list,
2376
                                                       registration_mapping_list,
2377
                                                       register_ind,
2378
                                                       centre,
2379
                                                       input = mapping_parameters,
2380
                                                       reg_mode = ifelse(mapping_parameters$automatictag, "auto", "manual"),
2381
                                                       image_list = image_list)) 
2382
  )
2383
}
2384
2385