# for some reason, not including the below results in Error in (function (name, val, shinysession) : No handler registered for type date_tree:jsTreeR.list library(jsTreeR) # increase max upload to 10mb options( shiny.maxRequestSize = 10*1024^2 ) if( Sys.which("convert") == "" ) { stop( "`convert` not found, please make sure ImageMagick is installed." ) } #for some reason I can't see params in s3_to_tmp pars <- params #some styling maps color.map <- c( IS_SAME = '#5CB85CFF', UNSURE = '#F0AD4EFF', NOT_SAME = '#D9534F50', HAS_ENCOUNTER = '#21908CFF', HAS_IMAGE = '#FFC107FF', VIRTUAL = '#F2F2F2FF', Event = '#440154FF', Encounter = '#21908CFF', Image = '#FFC107FF' ) color.map.80 <- c( Event = '#693376FF', Encounter = '#4DA6A3FF', Image = '#FDEB50FF' ) color.map.50 <- c( Event = '#A180A9FF', Encounter = '#90C7C5FF', Image = '#FEF392FF' ) font.color.map <- c( Event = '#FFFFFFFF', Encounter = '#FFFFFFFF', Image = '#343434FF' ) font.color.map.50 <- c(Image = '#999999FF') font.size.map <- c( IS_SAME = 0, UNSURE = 0, NOT_SAME = 0, HAS_ENCOUNTER = 10, HAS_IMAGE = 10, VIRTUAL = 0 ) font.align.map <- c( IS_SAME = NULL, UNSURE = NULL, NOT_SAME = NULL, HAS_ENCOUNTER = "top", HAS_IMAGE = "top", VIRTUAL = NULL ) physics.map <- c( IS_SAME = T, UNSURE = F, NOT_SAME = T, HAS_ENCOUNTER = F, HAS_IMAGE = F, VIRTUAL = F ) spring.map <- c( IS_SAME = 200, UNSURE = 300, NOT_SAME = 300, HAS_ENCOUNTER = 95, HAS_IMAGE = 95, VIRTUAL = 300 ) chosen.map <- c( IS_SAME = T, UNSURE = T, NOT_SAME = T, HAS_ENCOUNTER = F, HAS_IMAGE = F, VIRTUAL = T, Image = T, Encounter = F, Event = F ) smooth.map <- c( IS_SAME = F, UNSURE = F, NOT_SAME = F, HAS_ENCOUNTER = F, HAS_IMAGE = F, VIRTUAL = F ) dashes.map <- c( IS_SAME = F, UNSURE = F, NOT_SAME = F, HAS_ENCOUNTER = F, HAS_IMAGE = F, VIRTUAL = F ) tags$style( HTML( " #manual_query { background-color: white; border: 1px solid #cccccc; border-radius: 4px; } #manual_query:focus { border-color: #66afe9; outline: 0; box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px rgb(102 175 233 / 60%); } #upload_status { overflow-y:scroll; max-height: 50px; display: flex; flex-direction: column-reverse; } .fin_icon_path { cursor: pointer; pointer-events: bounding-box; } .invert_icon { fill: red; stroke: none; } " ) ) #script to forward keydown to shiny input #custom handler for simulating on click graph event # (visSetSelection doesn't seem to trigger the on click defined in the visNetwork) #custom handlers for updating to and from img indexes tags$script(src='js/script.js') # create connection to local neo4j database #TODO: store connection as react and catch errors? con <- neo4j_api$new( url = pars$neo4j_url, user = pars$neo4j_user, password = pars$neo4j_pass ) #expose tempdir to shiny server addResourcePath( prefix = "img", directoryPath = tempdir() ) addResourcePath( prefix = "svg", directoryPath = system.file("svg", package = "finmatchr" ) ) addResourcePath( prefix = "js", directoryPath = system.file("js", package = "finmatchr" ) ) #addResourcePath( prefix = "data", directoryPath = system.file("data", package = "finmatchr" ) ) task_file <- '/Users/dylanirion/Documents/GWS Abundance/data/task.json' #task_results_file <- '/Users/dylanirion/Documents/GWS Abundance/data/task_results.json' #task_results_file <- '/Users/dylanirion/Documents/GWS Abundance/data/2010_results.json' task_results_file <- '/Users/dylanirion/Documents/GWS Abundance/data/2007-2010_encounters_results.json' #task_results_file <- '/Users/dylanirion/Documents/GWS Abundance/data/2007-2010_encounters_results2.json' #task_results_file <- '/Users/dylanirion/Documents/GWS Abundance/data/2007-2010_individuals_results.json' unconnected_folders_file <- '/Users/dylanirion/Documents/GWS Abundance/data/unconnected_folders.json' all_folders_file <- '/Users/dylanirion/Documents/GWS Abundance/data/all_folders.json' #setup reactive values react <- reactiveValues( run = NULL, style = NULL, draw = NULL, update = NULL, copy = NULL, valid_img_count = NULL, unreviewed_img_count = NULL, date_list = NULL, upload_status = NULL, temp_count = 0, temp_folders = NULL, run_match = NULL, active_tab = "Browse", task_result = NULL, match_from_throb = NULL, match_to_throb = NULL, continue_query = NULL, continue_update = NULL) # TODO: read options from json to persist? # TODO: change style maps to options? settings <- reactiveValues( aggregate_by = "Duplicate/Cropped", update_single_all = "All", show_nodes = c( "unusable" = T, "Encounter" = F, "Event" = F ), show_edges = c( "IS_SAME" = T, "UNSURE" = T, "NOT_SAME" = F ), zoom_to_fin = T ) payload <- reactiveValues( query = NULL, graph = NULL, original_graph = NULL, from = NULL, to = NULL, from_meta = NULL, to_meta = NULL, edge_meta = NULL, old_from = NULL, old_to = NULL, match_from = NULL, match_to = NULL, match_edge_meta = NULL ) #function to embed image as svg and return svgPanZoom #TODO: add an svg icon when annotation is present draw_img <- function( node ) { path <- paste0( "img/", node %>% pull(uuid), ".jpg" ) if( ifelse( !"has_annot" %in% colnames(node) | is.na(node$has_annot) , F, node$has_annot ) & settings$zoom_to_fin ) { size <- node %>% pull(image_size) %>% pluck(1) x <- node %>% pull(x) %>% pluck(1) y <- node %>% pull(y) %>% pluck(1) svgPanZoom( paste0( "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>", "<image width = '", size[2], "' height = '", size[1], "' xlink:href='", path, "' />", "<polyline id = 'fin' points= '", paste( x, y, sep = ", ", collapse = " "), "' data-min = '", paste(min(x), min(y), sep=",") , "' data-max = '", paste(max(x), max(y), sep=",") , "' fill='none' stroke = 'red' display = 'none' />", "</svg>" ), controlIconsEnabled = TRUE ) %>% #TODO: calculate which is larger, dy, dx, and scale accordingly #TODO: stroke outside of icon onRender( "function(el, x) { var svg = el.getElementsByTagName('svg')[0]; svg.append(Object.assign(document.createElementNS('http://www.w3.org/2000/svg','g'), {innerHTML:\"<path class='fin_icon_path' d='m50,5a45,45 0 0 0 -45,45a45,45 0 0 0 45,45a45,45 0 0 0 45,-45a45,45 0 0 0 -45,-45zm6.37305,14.29297l0.86718,1.54101c4.59453,8.17276 6.60367,20.51569 9.65625,30.69141c1.5263,5.08786 3.30745,9.607 5.58985,12.6875c2.2824,3.0805 4.86276,4.73363 8.58984,4.625l0.08789,2.99805c-4.72367,0.13767 -8.42592,-2.24315 -11.08789,-5.83594c-2.66197,-3.59279 -4.48496,-8.38717 -6.05273,-13.61328c-2.91342,-9.71178 -5.02557,-20.84588 -8.61328,-28.28906c-10.83666,9.18762 -15.49541,18.30032 -19.2168,26.60937c-3.905,8.719 -7.01568,16.74422 -15.78516,22.13867l-1.57226,-2.55468c7.91381,-4.86811 10.67857,-12.01017 14.61914,-20.8086c3.94057,-8.79842 9.02872,-19.06452 21.53906,-29.08398l1.37891,-1.10547z' stroke-width='3.5' stroke='red' fill='none'/>\"})); var image = svg.getElementsByTagName('image')[0]; var fin = svg.getElementById('fin'); var icon = svg.getElementsByClassName('fin_icon_path')[0]; icon.parentNode.setAttribute('transform', 'translate(247 0) scale(0.25)'); icon.addEventListener('click', function(e) { e.preventDefault(); e.currentTarget.classList.toggle('invert_icon'); fin.setAttribute('display', fin.getAttribute('display') === 'none' ? '' : 'none'); }); var mat = svg.getElementsByClassName('svg-pan-zoom_viewport')[0].transform.baseVal.consolidate().matrix; var height = parseInt(image.getAttribute('height') ); this.zoomWidget.zoomAtPointBy( height / ( 50 + parseInt(fin.dataset.max.split(',')[1] ) - parseInt( fin.dataset.min.split(',')[1] ) ), {x: mat.a * ( parseInt( fin.dataset.min.split(',')[0] ) + 75 + (parseInt( fin.dataset.max.split(',')[0] ) - parseInt( fin.dataset.min.split(',')[0] ) ) / 2 ) + mat.e, y: mat.d * ( parseInt( fin.dataset.min.split(',')[1] ) - 75 + ( parseInt( fin.dataset.max.split(',')[1] ) - parseInt( fin.dataset.min.split(',')[1] ) ) / 2 ) + mat.f}); }") } else { svgPanZoom( paste0( "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'><image width = '750' height = '500' xlink:href='", path, "'/></svg>" ), controlIconsEnabled = TRUE ) } } #LISTENERS #"active_tab" listener # pases on value to react active_tab <- observeEvent( input$active_tab, { react$active_tab = input$active_tab }, ignoreNULL = F, ignoreInit = T ) ##TEMP!!! (SEE ALSO REACT$TEMP_COUNT CUSTOM KEYDOWN JS AND BUTTON FOR THIS ACTION) query_fix_edges_action <- observeEvent(input$query_fix_edges, { #if not first click, update json file if(react$temp_count != 0) { #write_json(react$temp_folders[-1], all_folders_file) write_json(react$temp_folders[-1], unconnected_folders_file) } #react$temp_folders<-read_json(all_folders_file, simplifyVector=T) react$temp_folders<-read_json(unconnected_folders_file, simplifyVector=T) updateActionButton(session, "query_fix_edges", label = paste0("Query Next Folder [",length(react$temp_folders),"]") ) react$draw <- payload$graph <- payload$from <- payload$to <- payload$old_from <- payload$old_to <- payload$edge_id <- NULL payload$query <-paste0("MATCH (img1:Image)<-[:HAS_IMAGE]-(:Folder {path: \"",str_replace_all(react$temp_folders[1],"(\\')", "\\\\\\1"),"\"}) ", "WITH img1 ", "OPTIONAL MATCH (img1)<-[rel:IS_SAME]->(img2:Image) ", #"WHERE rel.basis IN ['duplicate', 'cropped'] ", "WITH COLLECT(id(img1)) + COLLECT(id(img2)) AS ids, COLLECT(img1) + COLLECT(img2) AS imgs, COLLECT(rel) AS r ", "UNWIND imgs AS img ", "WITH img, ids, r ", "OPTIONAL MATCH (img)<-[rel1:HAS_IMAGE]-(enc:Encounter) ", "WITH img, ids, enc, COLLECT(img) + COLLECT(enc) AS n, r + COLLECT(rel1) AS r ", "OPTIONAL MATCH (enc)<-[rel:HAS_ENCOUNTER]-(evt:Event) ", "WITH img, ids, n + COLLECT(evt) AS nodes, r + COLLECT(rel) AS relationships ", "OPTIONAL MATCH (img)-[r]->(b) ", "WHERE id(b) IN ids ", "WITH img, nodes, relationships + COLLECT(r) as relationships ", "OPTIONAL MATCH (img)-[rel: HAS_ANNOTATION]-(a: Annotation) ", "WITH nodes + COLLECT(a) AS nodes, relationships + COLLECT(rel) AS relationships ", "RETURN nodes, relationships" ) react$temp_count <- react$temp_count + 1 react$run <- ifelse( is.null( react$run ), 0, react$run + 1 ) },ignoreInit=T) ### #"run_manual_query" button click # sets "query" payload from text input # triggers "run" with increment manual_query_action <- observeEvent( input$run_manual_query, { react$draw <- payload$graph <- payload$from <- payload$to <- payload$old_from <- payload$old_to <- payload$edge_id <- NULL payload$query <- input$manual_query react$run <- ifelse( is.null( react$run ), 0, react$run + 1 ) }, ignoreInit = T ) #"date_tree" click # sets "query" payload # triggers "run" with increment query_date_action <- observeEvent( input$date_tree_selected, { react$draw <- payload$graph <- payload$from <- payload$to <- payload$old_from <- payload$old_to <- payload$edge_id <- NULL if( req(length(input$date_tree_selected) > 0 ) ) { payload$query <- paste0( "MATCH (img:Image) WHERE ", paste0("(", paste0( lapply( input$date_tree_selected, function(d) d$data$query ), collapse = ") OR (" ), ")"), " ", "WITH img ", "WITH COLLECT(id(img)) AS ids, COLLECT(img) AS imgs ", "UNWIND imgs AS img ", "WITH img, ids ", "OPTIONAL MATCH (img)<-[rel:HAS_IMAGE]-(enc:Encounter) ", "WITH img, ids, enc, COLLECT(img) + COLLECT(enc) AS n, COLLECT(rel) AS rels ", "OPTIONAL MATCH (enc)<-[rel:HAS_ENCOUNTER]-(evt:Event) ", "WITH img, ids, n + COLLECT(evt) AS nodes, rels + COLLECT(rel) AS rels ", "OPTIONAL MATCH (img)-[rel]->(b) ", "WHERE id(b) IN ids ", "WITH img, nodes, rels + COLLECT(rel) as rels ", "OPTIONAL MATCH (img: Image)-[rel: HAS_ANNOTATION]-(a: Annotation) ", "WITH nodes + COLLECT(a) AS nodes, rels + COLLECT(rel) AS relationships ", "RETURN nodes, relationships" ) } react$run <- ifelse( is.null( react$run ), 0, react$run + 1 ) }, ignoreInit = T ) #"run" reactive # executes "query" payload # processes response as "graph" payload # triggers "style" with increment run_query <- observeEvent( react$run, { payload$from_meta <- NULL payload$to_meta <- NULL payload$edge_meta <- NULL # query db with progress bar withProgress(message = 'Querying Database...', value = 0, { # increment progress bar incProgress(0.5) # query data from neo4j if( payload$query != '' ) { graph <- payload$query %>% cypher( con, "graph" ) } else { graph <- list() } #@todo: move all this into a function if( length(graph) ) { # add potentially empty property columns node_cols <- tibble( unusable = NA_real_, reviewed = NA_real_, date = NA_character_, camera_date = NA_character_, region = NA_character_, trailing = NA_character_, leading = NA_character_, image_size = NA_character_, x = NA_character_, y = NA_character_, tip = NA_character_ ) edge_cols <- tibble( id = NA_character_ , from = NA_character_, to = NA_character_, basis = NA_character_, reviewed = NA_real_, label = NA_character_, score = NA_real_, chosen = NA_real_, hidden = NA_real_ ) # unnest nodes graph$nodes <- graph %>% pluck("nodes") %>% unnest_wider( col = "properties" ) %>% add_column( !!!node_cols[!names( node_cols ) %in% names(.)] ) %>% mutate( label = unlist(label) %>% .[ . != "DUMMY" ], trailing = map( trailing, ~c( unlist(.) ) ), leading = map( leading, ~c( unlist(.) ) ), image_size = map( image_size, ~c( unlist(.) ) ), x = map( x, ~c( unlist(.) ) ), y = map( y, ~c( unlist(.) ) ), tip = map( tip, ~c( unlist(.) ) ) ) if(!is.null(graph$relationships)) { graph$nodes <- graph$nodes %>% left_join( { graph$relationships %>% filter( type == "HAS_ANNOTATION" ) %>% select(startNode, endNode) %>% mutate( has_annot = T ) }, by = c( "id" = "startNode" ) ) %>% left_join( {select(., id, image_size, trailing, tip, leading, x, y, )}, by = c( "endNode" = "id" ) ) %>% select( -trailing.x, -image_size.x, -leading.x, -x.x, -y.x, -tip.x, -endNode ) %>% rename( image_size = image_size.y, trailing = trailing.y, tip = tip.y, leading = leading.y, x = x.y, y = y.y ) %>% filter( label != "Annotation" ) } else { graph$nodes <- graph$nodes %>% mutate( has_annot = F ) } graph$nodes <- graph$nodes %>% distinct( id, .keep_all = T ) # unnest relationships if( !is.null( graph$relationships ) ) { graph$relationships <- graph$relationships %>% unnest_relationships() %>% filter( type != "HAS_ANNOTATION" ) %>% #adds "value" column?? select_if(!names(.) %in% c('value')) %>% rename( from = startNode, to = endNode, label = type ) %>% add_column( !!!edge_cols[!names( edge_cols ) %in% names(.)] ) #pseudonodes from relationships (nested tibble) if( settings$aggregate_by == "Duplicate/Cropped" ) { agg_filter = parse_expr("label == 'IS_SAME' & basis %in% c( 'duplicate', 'cropped' ) & reviewed == T") } else if( settings$aggregate_by == "Encounter" ) { agg_filter = parse_expr("label == 'IS_SAME' & reviewed == T & from_date == to_date") } else if( settings$aggregate_by == "Individual" ) { agg_filter = parse_expr("label == 'IS_SAME' & reviewed == T") } else { agg_filter = parse_expr("FALSE") } igraph_object<- graph_from_data_frame( d = graph$relationships %>% left_join( graph$nodes %>% select( id, date ), by=c( "from" = "id" ) ) %>% rename( from_date = date ) %>% left_join( graph$nodes %>% select( id, date ), by=c( "to" = "id" ) ) %>% rename( to_date = date ) %>% filter( !!agg_filter ) %>% select( from, to, everything() ), directed = F, vertices = graph$nodes ) comp <- groups( components( igraph_object ) ) %>% map_df( ~ tibble( id = as.character(md5(paste0( sort( . ), collapse = ", ") ) ), members = list(.) ) ) graph$nodes <- graph$nodes %>% mutate( group = comp[components( igraph_object )$membership, "id"] %>% pull(id) ) %>% nest( data = !group ) %>% select( id = group, data ) %>% mutate( label = map_chr( data, ~unique( .$label ) ) ) #drop the relationships we grouped (if there are any) #TODO: group these instead so we know which two nodes exactly have the linking relationship #TODO: drop UNSURE when cgrouped with IS_SAME or NOT_SAME if( nrow(graph$relationships) ) { graph$relationships <- graph$relationships %>% rowwise() %>% mutate( keep = map2_lgl( from, to, ~ !any( sapply( comp$members, function(e) is.element( .x, e ) & is.element( .y, e ) ) ) ), from = comp[which( sapply( comp$members, function(e) is.element( from, e ) ) ), "id"] %>% pull(id), to = comp[which( sapply( comp$members, function(e) is.element( to, e ) ) ), "id"] %>% pull(id) ) %>% ungroup() %>% filter( keep ) %>% #something like this to identify groups with multiple labels in remove UNSURE? group_by(from, to) %>% mutate(keep = all(label[1]==label)) %>% ungroup() %>% filter( keep | label != "UNSURE" ) %>% select( -keep ) } } else { graph$nodes <- graph$nodes %>% mutate( group = uuid ) %>% nest( data = !group ) %>% select( id = group, data ) %>% mutate( label = map_chr( data, ~unique( .$label ) ) ) graph$relationships <- tibble() %>% add_column( !!!edge_cols[!names( edge_cols ) %in% names(.)] ) } #remove 'unusable' nodes (but re-add later) unusable <- graph$nodes %>% mutate( unusable = map_lgl( data, ~all( .$unusable == T ) ) ) %>% filter( unusable == T ) %>% select( -unusable ) graph$nodes <- graph$nodes %>% mutate( unusable = map_lgl( data, ~all( .$unusable == T ) ) ) %>% filter( unusable != T | is.na(unusable) ) %>% select( -unusable ) if( graph$nodes %>% filter( label == "Image" ) %>% nrow() > 1 ) { #create virtual relationships for any that aren't present #TODO: don't create virtual if path is already present between two nodes virtual_rels <- t( combn( graph$nodes %>% filter( label == "Image" ) %>% pull(id), 2 ) ) %>% `colnames<-`( c( "from", "to" ) ) %>% as_tibble() %>% mutate( label = "VIRTUAL", basis = NA_character_, reviewed = F ) #if no real relationships, just add them all if( is.null( graph$relationships ) ) { graph$relationships <- virtual_rels } else { #otherwise we need to join only those that don't exist graph$relationships <- bind_rows( graph$relationships, anti_join( virtual_rels, bind_rows( graph$relationships %>% filter( label %in% c( "IS_SAME", "UNSURE", "NOT_SAME" ) ), graph$relationships %>% filter( label %in% c( "IS_SAME", "UNSURE", "NOT_SAME" ) ) %>% rename( "from" = "to", "to" = "from" ) ), by = c( "from", "to" ) ) ) } } #rewrite edge ids graph$relationships <- graph$relationships %>% rowwise() %>% mutate( id = as.character( md5(paste0( label, basis, sort( c( from, to ) ), collapse = ", ") ) ) ) %>% ungroup() %>% distinct(id, .keep_all = T) #re-add unusable nodes graph$nodes <- bind_rows( graph$nodes, unusable ) #sort edges if( nrow( graph$relationships ) > 0 ) { graph$relationships <- graph$relationships %>% rowwise %>% mutate( sort = paste0( sort( c( from, to ) ), collapse = ", ") ) %>% ungroup() %>% arrange(sort) %>% select( -sort ) } } payload$graph <- graph #trigger styling react$style <- ifelse( is.null( react$style ), 0, react$style + 1 ) }) }) #"style" reactive # styles and update "graph" payload # triggers "draw" on first drawing, or "update" with increment style_results <- observeEvent( react$style, { graph <- payload$graph if( length(graph) ) { graph$nodes <- graph$nodes %>% mutate( shape = "circle", color.background = recode( label, !!!color.map.80 ), color.border = recode( label, !!!color.map ), font.color = recode( label, !!!font.color.map ), chosen = recode( label, !!!chosen.map ), hidden = F, #title = case_when( label == 'Event' ~ paste0( '<p><b>', uuid, '</b><br>', date, '</p>' ), label == 'Encounter' ~ paste0( '<p><b>', uuid, '</b><br>', date, '</p>' ), label == 'Image' ~ paste0( '<p><b>', uuid, '</b><br>', source_file, '<br>', camera_date, '<br>', date, '</p>' ) ) ) %>% # override colors for unusable rowwise() %>% mutate(color.background = case_when( label == "Image" & all(data %>% pull(unusable) == TRUE) ~ color.map.50["Image"], TRUE ~ color.background ), color.border = case_when( label == "Image" & all(data %>% pull(unusable) == TRUE) ~ color.map.80["Image"], TRUE ~ color.border ), font.color = case_when( label == "Image" & all(data %>% pull(unusable) == TRUE) ~ font.color.map.50["Image"], TRUE ~ font.color )) %>% ungroup() if( !settings$show_nodes["unusable"] ) { graph$nodes <- graph$nodes %>% rowwise() %>% mutate( hidden = case_when( label == "Image" & all(data %>% pull(unusable) == TRUE) ~ T, TRUE ~ hidden ) ) %>% ungroup() } if( !any(settings$show_nodes[c("Encounter", "Event")]) ) { graph$nodes <- graph$nodes %>% mutate( hidden = case_when( label %in% c("Encounter", "Event")[!settings$show_nodes[c("Encounter", "Event")]] ~ T, TRUE ~ hidden ) ) } if( nrow(graph$relationships) > 0 ) { graph$relationships <- graph$relationships %>% #filter( label != "NOT_SAME" ) %>% mutate( color.color = recode( label, !!!color.map ), dashes = recode( label, !!!dashes.map ), font.size = recode( label, !!!font.size.map ), font.align = recode( label, !!!font.align.map ), physics = recode( label, !!!physics.map ), length = recode( label, !!!spring.map ), chosen = recode( label, !!!chosen.map ), smooth.enabled = recode( label, !!!smooth.map ), hidden = recode( label, !!!!settings$show_edges, .default = F ), hidden = case_when( label == 'VIRTUAL' ~ ifelse( is.null(settings$show_edges['UNSURE']), T, !settings$show_edges['UNSURE']), T ~ hidden ) ) %>% # override dashes for unreviewed rowwise() %>% mutate(dashes = case_when( reviewed != TRUE | is.na(reviewed) & !label %in% c("HAS_IMAGE", "HAS_ENCOUNTER", "HAS_FOLDER") ~ TRUE, TRUE ~ dashes )) %>% ungroup() # if we hid any nodes, hide the edges too if( !settings$show_nodes["unusable"] | !any(settings$show_nodes[c("Encounter", "Event")]) ) { graph$relationships <- graph$relationships %>% left_join(graph$nodes %>% select(id, hidden ), by = c( "from" = "id" ) ) %>% left_join(graph$nodes %>% select(id, hidden ), by = c( "to" = "id" ) ) %>% rowwise() %>% mutate( hidden = any( hidden.x, hidden.y, hidden ) ) %>% ungroup %>% select( -hidden.x, -hidden.y ) } } } payload$graph <- graph #if we haven't drawn the graph yet, do so, otherwise update it if( is.null( react$draw ) ) { react$draw <- T } else { react$update <- ifelse( is.null( react$update ), 0, react$update + 1 ) } }) #"draw" reactive # displays "graph" payload # adds click actions to trigger inputs # triggers finishedDrawing reactive # returns visNetwork draw_results <- eventReactive( react$draw, { graph <- payload$graph if( length(graph) ) { react$copy <- ifelse( is.null( react$copy ), 0, react$copy + 1 ) visNetwork( graph$nodes, graph$relationships, height = '100%', submain = paste0( "Aggregating by ", settings$aggregate_by, ", Updating ", ifelse( settings$update_single_all == "All" & ( settings$aggregate_by == "Duplicate/Cropped" | settings$aggregate_by == "Encounter" ), "All", "Single" ), "<br>", nrow( graph$relationships %>% filter( reviewed == T ) ), "/", nrow( graph$relationships %>% filter( chosen == T ) ), " Edges Reviewed, ", nrow( graph$nodes$data %>% bind_rows() %>% filter( label == "Image" & ( unusable == T | reviewed == T ) ) ), "/", nrow( graph$nodes$data %>% bind_rows() %>% filter( label == "Image" ) ), " images reviewed" ) ) %>% visEvents( type = "on", click = paste0( "function(p) { ", " keysEnabled = true;", " graphSelected = true;", # is it possible to check node label here? " if( typeof p.edges[0] === 'undefined' && typeof p.nodes[0] !== 'undefined' ) { ", " if( this.body.data.nodes.get(p.nodes[0]).label == \"Image\" ) { ", " selectEdge(this.body.data.nodes.get(p.nodes[0]).id, ", " null, ", " null, ", " 'EDIT SINGLE' ", " );", " }", " } else if( typeof p.nodes[0] === 'undefined' && typeof p.edges[0] !== 'undefined' ) { ", " if( [\"IS_SAME\", \"UNSURE\", \"NOT_SAME\", \"VIRTUAL\" ].includes(this.body.data.edges.get(p.edges[0]).label) ) { ", " selectEdge(this.body.data.edges.get(p.edges[0]).from, ", " this.body.data.edges.get(p.edges[0]).to, ", " this.body.data.edges.get(p.edges[0]).label, ", " this.body.data.edges.get(p.edges[0]).id ", " );", " }", " }", "}" ), selectEdge = paste0( "function() { keysEnabled = true; graphSelected = true; }" ), deselectEdge = paste0( "function() { keysEnabled = false; graphSelected = false; }" ) ) %>% visInteraction( dragNodes = F, navigationButtons = T, selectConnectedEdges = F ) } }, ignoreInit = T ) #"update" reactive # displays "graph" payload # updates VisNetworkProxy update_results <- observeEvent( react$update, { graph <- payload$graph if( length(graph) ) { n <- NULL e <- NULL #update the graph visNetworkProxy( "graph" ) %>% visUpdateNodes(graph$nodes) %>% visUpdateEdges( graph$relationships ) %>% visRemoveEdges( payload$original_graph$relationships$id[which( !payload$original_graph$relationships$id %in% graph$relationships$id )] ) %>% visRemoveNodes( payload$original_graph$nodes$id[which( !payload$original_graph$nodes$id %in% graph$nodes$id )] ) %>% visSetTitle( submain = paste0( "Aggregating by ", settings$aggregate_by, ", Updating ", ifelse( settings$update_single_all == "All" & ( settings$aggregate_by == "Duplicate/Cropped" | settings$aggregate_by == "Encounter" ), "All", "Single" ), "<br>", nrow( graph$relationships %>% filter( reviewed == T ) ), "/", nrow( graph$relationships %>% filter( chosen == T ) ), " Edges Reviewed, ", nrow( graph$nodes$data %>% bind_rows() %>% filter( label == "Image" & ( unusable == T | reviewed == T ) ) ), "/", nrow( graph$nodes$data %>% bind_rows() %>% filter( label == "Image" ) ), " images reviewed" ) ) #then decide if we are selecting a node or edge if( is.null(payload$old_to) & !is.null(payload$old_from) ) { #selecting just a node, find the old one (if it exists) n <- graph$nodes %>% mutate( keep = map_lgl( data, ~ payload$old_from %in% .$id ) ) %>% filter( keep ) %>% select( id ) %>% pull(id) if( length(n) ) { n <- which( graph$nodes$id == n ) } else { n <- NULL e <- min( which( graph$relationships$chosen == T ) ) } } else if( !is.null(payload$old_from) & !is.null(payload$old_to) ) { #selecting an edge, if there is only 1 left, select it if( nrow( graph$relationships %>% filter( chosen == T ) ) == 1 ) { e <- min( which( graph$relationships$chosen == T ) ) } else if( nrow( graph$relationships %>% filter( chosen == T ) ) == 0 ) { n <- min( which( graph$nodes$chosen == T ) ) e <- NULL } else { # otherwise try to find the original if( length( which( graph$relationships$id == input$edge_id ) ) ) { e <- which( graph$relationships$id == input$edge_id ) } else if( !is.null(payload$old_from) & !is.null(payload$old_to) ) { # or try to find the new edge a <- graph$nodes %>% mutate( keep = map_lgl( data, ~ payload$old_from %in% .$id ) ) %>% filter( keep ) %>% select( id ) %>% pull(id) b <- graph$nodes %>% mutate( keep = map_lgl( data, ~ payload$old_to %in% .$id ) ) %>% filter( keep ) %>% select( id ) %>% pull(id) if( length(a) & length(b) ) { # edge was consolidated into a node if( a == b ) { n <- which( graph$nodes$id == a ) } else { e <- which( graph$relationships$id == graph$relationships %>% filter( ( from == a | to == a ) & ( from == b | to == b ) ) %>% slice(1) %>% pull(id) ) # edge no longer exists, but one of the nodes is still active if( !length(e) ) { n <- which( ( graph$nodes$id == a | graph$nodes$id == b ) & sapply( graph$nodes$data, function(l) any( ifelse( is.na(l$unusable), F, l$unusable ) == F) ) ) } } # don't think I actually want this case #} else if( !length(a) & !length(b) ) { # #if somehow we can't find anything, just select the first edge # e <- min( which( graph$relationships$chosen == T ) ) } else { #otherwise just select the remaining original node n <- ifelse( length(a), which( graph$nodes$id == a ), which( graph$nodes$id == b ) ) } } else { e <- min( which( graph$relationships$chosen == T ) ) } } } if( !is.null(n) ) { visNetworkProxy( "graph" ) %>% visSetSelection( nodesId = graph$nodes$id[n], highlightEdges = F ) session$sendCustomMessage( "simSelect", list( from = graph$nodes$id[n], to = NULL, label = NULL, id = "EDIT SINGLE" ) ) } else if( !is.null(e) ) { visNetworkProxy( "graph" ) %>% visSetSelection( edgesId = graph$relationships$id[e] ) session$sendCustomMessage( "simSelect", list( from = graph$relationships$from[e], to = graph$relationships$to[e], label = graph$relationships$label[e], id = graph$relationships$id[e] ) ) } session$sendCustomMessage( "setFromImgIDX", ifelse( is.null(payload$old_from), 1, input$from_img_idx ) ) session$sendCustomMessage( "setToImgIDX", ifelse( is.null(payload$old_to), 1, input$to_img_idx ) ) } else { visNetworkProxy( "graph" ) %>% visRemoveEdges( payload$original_graph$relationships$id ) %>% visRemoveNodes( payload$original_graph$nodes$id ) %>% visSetTitle( submain = "No remaining nodes" ) } }) #"copy" reactive # copies/converts images to tempdir on new graph draw start_copy <- observeEvent( react$copy, { nodes <- payload$graph$nodes %>% filter( label == "Image" ) %>% select( -id, -label ) %>% unnest( cols = c(data) ) withProgress( message = paste0( 'Copying Images...[1/', nrow(nodes), ']' ), value = 0, { for( i in 1:nrow( nodes ) ) { # increment progress bar setProgress( i / nrow(nodes), message = paste0( 'Copying Images...[', i, '/', nrow(nodes), ']' ) ) s3_to_tmp( nodes$source_file[i], nodes$uuid[i], pars ) } }) }, ignoreInit = T) #"edge_id" (input) reactive # sets from/to/edge _meta payloads watch_graph_click <- observeEvent( input$edge_id, { if( input$edge_id != 'EDIT SINGLE' ) { if( !input$edge_label %in% c( 'HAS_ENCOUNTER', 'HAS_IMAGE' ) ) { payload$from <- payload$graph$nodes %>% filter( id == input$edge_from_id ) %>% select( -id, -label ) %>% unnest( cols = c(data) ) payload$to <- payload$graph$nodes %>% filter( id == input$edge_to_id ) %>% select( -id, -label ) %>% unnest( cols = c(data) ) payload$edge_meta <- payload$graph$relationships %>% filter( id == input$edge_id ) } } else { payload$from <- payload$graph$nodes %>% filter( id == input$edge_from_id ) %>% select( -id, -label ) %>% unnest( cols = c(data) ) payload$to <- NULL payload$edge_meta <- NULL } }) #"from" reactive # sets from viewer img index from <- observeEvent( payload$from, { if( !is.null(payload$from) & !is.null(payload$old_from) ) { if( payload$old_from %in% payload$from$id ) { session$sendCustomMessage( "setFromImgIDX", input$from_img_idx ) } else { session$sendCustomMessage( "setFromImgIDX", 1 ) } } else { session$sendCustomMessage( "setFromImgIDX", 1 ) } }, ignoreNULL = F ) # "from_img_idx" (input) reactive # populates from viewer # updates input field values from meta # return svgPanZoom draw_from <- eventReactive( input$from_img_idx, { if( !is.null(payload$from ) ) { payload$from_meta <- payload$from %>% slice( input$from_img_idx ) payload$old_from <- payload$from_meta$id img <- payload$from_meta output$from_id <- renderText({ paste0( img %>% pull(uuid), " [", input$from_img_idx, "/", nrow(payload$from), "]" ) }) output$from_path <- renderText({ img %>% pull(source_file) }) output$from_exif_date <- renderText({ img %>% pull(camera_date) }) updateDateInput( session, "from_date", label = NULL, value = img %>% pull(date) ) updateCheckboxInput( session, "from_unusable", label = NULL, value = ifelse( is.na( img %>% .$unusable ), F, img %>% pull(unusable) ) ) updateCheckboxInput( session, "from_reviewed", label = NULL, value = ifelse( is.na( img %>% .$reviewed ), F, img %>% pull(reviewed) ) ) updateTextInput( session, "from_region", label = NULL, value = img %>% pull(region) ) return( draw_img( img ) ) } else { payload$old_from <- NULL output$from_id <- renderText({ "" }) output$from_path <- renderText({ "" }) output$from_exif_date <- renderText({ "" }) updateDateInput( session, "from_date", label = NULL, value = NA ) updateCheckboxInput( session, "from_unusable", label = NULL, value = F ) updateCheckboxInput( session, "from_reviewed", label = NULL, value = F ) updateTextInput( session, "from_region", label = NULL, value = "" ) return(NULL) } }) #"to" reactive # sets to viewer img index to <- observeEvent( payload$to, { if( !is.null(payload$to) & !is.null(payload$old_to) ) { if( payload$old_to %in% payload$to$id ) { session$sendCustomMessage( "setToImgIDX", input$to_img_idx ) } else { session$sendCustomMessage( "setToImgIDX", 1 ) } } else { session$sendCustomMessage( "setToImgIDX", 1 ) } }, ignoreNULL = F ) # "to_img_idx" (input) reactive # populates to viewer # updates input field values from meta # return svgPanZoom draw_to <- eventReactive( input$to_img_idx, { if( !is.null(payload$to ) ) { payload$to_meta <- payload$to %>% slice( input$to_img_idx ) payload$old_to <- payload$to_meta$id img <- payload$to_meta output$to_id <- renderText({ paste0( img %>% pull(uuid), " [", input$to_img_idx, "/", nrow(payload$to), "]" ) }) output$to_path <- renderText({ img %>% pull(source_file) }) output$to_exif_date <- renderText({ img %>% pull(camera_date) }) updateDateInput( session, "to_date", label = NULL, value = img %>% pull(date) ) updateCheckboxInput( session, "to_unusable", label = NULL, value = ifelse( is.na( img %>% .$unusable ), F, img %>% pull(unusable) ) ) updateCheckboxInput( session, "to_reviewed", label = NULL, value = ifelse( is.na( img %>% .$reviewed ), F, img %>% pull(reviewed) ) ) updateTextInput( session, "to_region", label = NULL, value = img %>% pull(region) ) return( draw_img( img ) ) } else { output$to_id <- renderText({ "" }) payload$old_to <- NULL output$to_path <- renderText({ "" }) output$to_exif_date <- renderText({ "" }) updateDateInput( session, "to_date", label = NULL, value = NA ) updateCheckboxInput( session, "to_unusable", label = NULL, value = F ) updateCheckboxInput( session, "to_reviewed", label = NULL, value = F ) updateTextInput( session, "to_region", label = NULL, value = "" ) return(NULL) } }) #"edge_meta" reactive # populates edge viewer # updates input values from meta draw_classification <- observeEvent( payload$edge_meta, { if( !is.null(payload$edge_meta ) ) { updateRadioButtons( session, "label", label = NULL, choices = NULL, selected = ifelse( payload$edge_meta$label == "VIRTUAL", "UNSURE", payload$edge_meta$label ) ) updateSelectInput( session, "basis", label = NULL, choices = NULL, selected = ifelse( is.na( payload$edge_meta$basis ), "", payload$edge_meta$basis ) ) updateCheckboxInput( session, "reviewed", label = NULL, value = ifelse( is.na( payload$edge_meta$reviewed ), F, payload$edge_meta$reviewed ) ) output$score <- renderText({ ifelse( is.na( payload$edge_meta$score ), "", paste0("Score: ", round(as.numeric(payload$edge_meta$score), 2)) ) }) } else { updateRadioButtons( session, "label", label = NULL, choices = NULL, selected = "UNSURE" ) updateSelectInput( session, "basis", label = NULL, choices = NULL, selected = "" ) updateCheckboxInput( session, "reviewed", label = NULL, value = F ) } }, ignoreNULL = F ) #"save_from" (input) reactive # updates node in database if any property values have changed # triggers re-execution of "query" payload # and update of graph update_from <- observeEvent( input$save_from, { if( !is.null(payload$from_meta) ) { if( input$from_region != ifelse( is.na( payload$from_meta$region ), F, payload$from_meta$region ) | ifelse( length(input$from_date) == 0, F, as.character(input$from_date) ) != ifelse( is.na( payload$from_meta$date ), F, as.character(payload$from_meta$date) ) | input$from_unusable != ifelse( is.na( payload$from_meta$unusable ), F, payload$from_meta$unusable ) | input$from_reviewed != ifelse( is.na( payload$from_meta$reviewed ), F, payload$from_meta$reviewed ) ) { withProgress( message = "Updating Database...", value = 0, { payload$original_graph <- payload$graph #Condition update on setting if( settings$update_single_all == "All" & ( settings$aggregate_by == "Duplicate/Cropped" | settings$aggregate_by == "Encounter" ) ) { update_node( payload$from$uuid, input$from_region, ifelse( length(input$from_date) != 0, as.character(input$from_date), "null" ), ifelse( input$from_unusable == F, "null", input$from_unusable ), ifelse( input$from_reviewed == F, "null", input$from_reviewed ), con ) } else{ update_node( payload$from_meta$uuid, input$from_region, ifelse( length(input$from_date) != 0, as.character(input$from_date), "null" ), ifelse( input$from_unusable == F, "null", input$from_unusable ), ifelse( input$from_reviewed == F, "null", input$from_reviewed ), con ) } react$run <- ifelse( is.null( react$run ), 0, react$run + 1 ) }) } } }) #"save_to" (input) reactive # updates node in database if any properties have changed # triggers re-execution of "query" payload # and update of graph update_to <- observeEvent( input$save_to, { if( !is.null(payload$to_meta) ) { if( input$to_region != ifelse( is.na( payload$to_meta$region ), F, payload$to_meta$region ) | ifelse( length(input$to_date) == 0, F, as.character(input$to_date) ) != ifelse( is.na( payload$to_meta$date ), F, as.character(payload$to_meta$date) ) | input$to_unusable != ifelse( is.na( payload$to_meta$unusable ), F, payload$to_meta$unusable ) | input$to_reviewed != ifelse( is.na( payload$to_meta$reviewed ), F, payload$to_meta$reviewed ) ) { withProgress( message = "Updating Database...", value = 0.5, { payload$original_graph <- payload$graph #Condition update on setting if( settings$update_single_all == "All" & ( settings$aggregate_by == "Duplicate/Cropped" | settings$aggregate_by == "Encounter" ) ) { update_node( payload$to$uuid, input$to_region, ifelse( length(input$to_date) != 0, as.character(input$to_date), "null" ), ifelse( input$to_unusable == F, "null", input$to_unusable ), ifelse( input$to_reviewed == F, "null", input$to_reviewed ), con ) } else{ update_node( payload$to_meta$uuid, input$to_region, ifelse( length(input$to_date) != 0, as.character(input$to_date), "null" ), ifelse( input$to_unusable == F, "null", input$to_unusable ), ifelse( input$to_reviewed == F, "null", input$to_reviewed ), con ) } react$run <- ifelse( is.null( react$run ), 0, react$run + 1 ) }) } } }) #"classify_label" (input) reactive # updates edge in database # triggers re-execution of "query" payload # and update of graph update_classification <- observeEvent( input$classify_label, { withProgress( message = "Updating Database...", value = 0.5, { payload$original_graph <- payload$graph update_edge( payload$from$uuid[input$from_img_idx], payload$to$uuid[input$to_img_idx], input$label, input$basis, input$reviewed, con, payload ) react$run <- ifelse( is.null( react$run ), 0, react$run + 1 ) }) }) #"keydown" (input) reactive # space bar triggers # L/R arrows navigate edges (skipping to next unreviewed if present) # simulates graph select on prev/next arrow keydown (this then populates edge viewer) # 'x','c' navigate 'from' images for grouped sets # ',','.' navigate 'to' images for grouped sets keydown <- observeEvent( input$keys, { if( ifelse( is.null( input$keys$`32` ), F, input$keys$`32` ) ) { # spacebar #TODO: DISABLE SPACE BAR UNTIL BUTTON IS PRESSED? if(react$active_tab == "Browse" ) { session$sendCustomMessage( "nextFolder", list( val = 'bar' ) ) session$sendCustomMessage( "simSelect", list( from = NULL, to = NULL, label = NULL, id = NULL ) ) } else if (react$active_tab == "Process Task Results") { session$sendCustomMessage( "nextTaskResult", list( val = 'bar' ) ) } } else if( ( ifelse( is.null( input$keys$`37` ), F, input$keys$`37` ) | ifelse( is.null( input$keys$`39` ), F, input$keys$`39` ) ) & !is.null(payload$graph$relationships) & ifelse( is.null(payload$graph$relationships), F, length( payload$graph$relationships %>% filter( chosen == T & hidden != T ) ) ) ) { #L/R arrows #TODO: make this cycle through nodes if they are selected instead relationships <- payload$graph$relationships %>% filter( chosen == T & hidden != T ) i <- ifelse( !length(input$edge_id) | ifelse( is.null(input$edge_id), F, input$edge_id ) == "EDIT SINGLE", 1, which( relationships$id == input$edge_id ) ) unreviewed_i <- which( relationships$reviewed != T | is.na( relationships$reviewed ) ) if( ifelse( is.null( input$keys$`37` ), F, input$keys$`37` ) ) { # left arrow if( length(unreviewed_i) > 0 ) { i <- ifelse( i >= max(unreviewed_i), min(unreviewed_i), unreviewed_i[which( unreviewed_i >= i + 1 )][1] ) } else { i <- ifelse( i == 1, length( relationships$id ), i - 1 ) } } else if( ifelse( is.null( input$keys$`39` ), F, input$keys$`39` ) ) { # right arrow if( length(unreviewed_i) > 0 ) { i <- ifelse( i <= min(unreviewed_i), max(unreviewed_i), rev(unreviewed_i[which( unreviewed_i <= i - 1 )])[1] ) } else { i <- ifelse( i == length( relationships$id ), 1, i + 1 ) } } if(react$active_tab == "Browse" ) { visNetworkProxy( "graph" ) %>% visSetSelection( edgesId = relationships$id[i] ) #can't get on click to fire through visSetSelection, so doing manually session$sendCustomMessage( "simSelect", list( from = relationships$from[i], to = relationships$to[i], label = relationships$label[i], id = relationships$id[i] ) ) } } else if( ( ifelse( is.null( input$keys$`88` ), F, input$keys$`88` ) | ifelse( is.null( input$keys$`67` ), F, input$keys$`67` ) ) & !is.null(payload$graph) & !is.null(payload$from) & react$active_tab == "Browse" ) { nodes <- payload$from j <- input$from_img_idx if( ifelse( is.null( input$keys$`88` ), F, input$keys$`88` ) ) { # x j <- ifelse( j == 1, length( nodes$id ), j - 1 ) } else if( ifelse( is.null( input$keys$`67` ), F, input$keys$`67` ) ) { # c j <- ifelse( j == length( nodes$id ), 1, j + 1 ) } session$sendCustomMessage( "setFromImgIDX", j ) } else if( ( ifelse( is.null( input$keys$`88` ), F, input$keys$`88` ) | ifelse( is.null( input$keys$`67` ), F, input$keys$`67` ) ) & !is.null(payload$graph) & !is.null(payload$match_from) & react$active_tab == "Process Task Results" ) { nodes <- payload$match_from j <- input$match_from_img_idx if( ifelse( is.null( input$keys$`88` ), F, input$keys$`88` ) ) { # x j <- ifelse( j == 1, length( nodes$id ), j - 1 ) } else if( ifelse( is.null( input$keys$`67` ), F, input$keys$`67` ) ) { # c j <- ifelse( j == length( nodes$id ), 1, j + 1 ) } session$sendCustomMessage( "setMatchFromImgIDX", j ) } else if( ( ifelse( is.null( input$keys$`188` ), F, input$keys$`188` ) | ifelse( is.null( input$keys$`190` ), F, input$keys$`190` ) ) & !is.null(payload$graph) & !is.null(payload$to) & react$active_tab == "Browse" ) { nodes <- payload$to j <- input$to_img_idx if( ifelse( is.null( input$keys$`188` ), F, input$keys$`188` ) ) { # < j <- ifelse( j == 1, length( nodes$id ), j - 1 ) } else if( ifelse( is.null( input$keys$`190` ), F, input$keys$`190` ) ) { # > j <- ifelse( j == length( nodes$id ), 1, j + 1 ) } session$sendCustomMessage( "setToImgIDX", j ) } else if( ( ifelse( is.null( input$keys$`188` ), F, input$keys$`188` ) | ifelse( is.null( input$keys$`190` ), F, input$keys$`190` ) ) & !is.null(payload$graph) & !is.null(payload$match_to) & react$active_tab == "Process Task Results" ) { nodes <- payload$match_to j <- input$match_to_img_idx if( ifelse( is.null( input$keys$`188` ), F, input$keys$`188` ) ) { # < j <- ifelse( j == 1, length( nodes$id ), j - 1 ) } else if( ifelse( is.null( input$keys$`190` ), F, input$keys$`190` ) ) { # > j <- ifelse( j == length( nodes$id ), 1, j + 1 ) } session$sendCustomMessage( "setMatchToImgIDX", j ) } else if( ifelse( is.null( input$keys$`83` ), F, input$keys$`83` ) ) { #s if(react$active_tab == "Browse" ) { updateSelectInput( session, "label", label = NULL, choices = NULL, selected = "IS_SAME" ) } else if (react$active_tab == "Process Task Results") { updateSelectInput( session, "match_label", label = NULL, choices = NULL, selected = "IS_SAME" ) } } else if( ifelse( is.null( input$keys$`85` ), F, input$keys$`85` ) ) { #u if(react$active_tab == "Browse" ) { updateSelectInput( session, "label", label = NULL, choices = NULL, selected = "UNSURE" ) } else if (react$active_tab == "Process Task Results") { updateSelectInput( session, "match_label", label = NULL, choices = NULL, selected = "UNSURE" ) } } else if( ifelse( is.null( input$keys$`78` ), F, input$keys$`78` ) ) { #n if(react$active_tab == "Browse" ) { updateSelectInput( session, "label", label = NULL, choices = NULL, selected = "NOT_SAME" ) } else if (react$active_tab == "Process Task Results") { updateSelectInput( session, "match_label", label = NULL, choices = NULL, selected = "NOT_SAME" ) } } }) #"refresh" (input) reactives # updates "Database Statistics" section # populates and triggers folder_list db_stats <- observeEvent( input$refresh, { withProgress( message = "Calculating Statistics...", value = 0, { "CALL gds.graph.create.cypher('valid-images', 'MATCH (n:Image) WHERE COALESCE(n.unusable, false) <> true AND COALESCE(n.reviewed, false) = true RETURN id(n) AS id', 'MATCH (n:Image) WHERE COALESCE(n.unusable, false) <> true AND COALESCE(n.reviewed, false) = true WITH n MATCH (n:Image)-[r:IS_SAME]-(m:Image) WHERE (r.basis = \\'duplicate\\' OR r.basis = \\'cropped\\') AND COALESCE(r.reviewed, false) = true RETURN id(n) AS source, id(m) AS target',{validateRelationships: false})" %>% cypher( con, "row" ) img_count_query <- paste0( "CALL gds.wcc.stream('valid-images') ", "YIELD nodeId, componentId ", "WITH componentId, count(*) AS size ", "RETURN count(componentId) AS c" ) react$valid_img_count <- img_count_query %>% cypher( con, "row" ) %>% pluck("c") %>% pull(value) "CALL gds.graph.drop('valid-images') YIELD graphName" %>% cypher( con, "row" ) setProgress( value = 1/3 ) unreviewed_img_count_query <- paste0( "MATCH (img:Image) ", "WHERE COALESCE(img.unusable, false) <> true AND COALESCE(img.reviewed, false) <> true ", "WITH COUNT(img) AS c ", "RETURN c" ) react$unreviewed_img_count <- unreviewed_img_count_query %>% cypher( con, "row" ) %>% pluck("c") %>% pull(value) setProgress( value = 2/3 ) date_query <- paste0( "MATCH (i:Image) WHERE COALESCE(i.date, false) <> false WITH DISTINCT i.date AS dates RETURN dates ORDER BY dates") dates <- date_query %>% cypher( con, "row" ) %>% pluck("dates") %>% mutate( year = year( value ), month = month( value, label = T ), n_month = month( value ), day = day( value ) ) date_list <- list() for( i in 1:length(unique(dates$year)) ) { date_list[[i]]<-list() date_list[[i]]$text <- unique(dates$year)[i] date_list[[i]]$data <- list( query = paste0("img.date.year = ", unique(dates$year)[i] ) ) date_list[[i]]$children <- list() tmp1 <- dates %>% filter( year == unique(dates$year)[i] ) for( j in 1:length(unique(tmp1$month)) ) { date_list[[i]]$children[[j]] <- list() date_list[[i]]$children[[j]]$text <- as.character(unique(tmp1$month)[j]) date_list[[i]]$children[[j]]$data <- list( query = paste0( "img.date.year = ", unique(dates$year)[i], " AND img.date.month = ", unique(tmp1$n_month)[j] ) ) date_list[[i]]$children[[j]]$children <- list() tmp2 <- tmp1 %>% filter( month == unique(tmp1$month)[j] ) for( k in 1:nrow(tmp2) ) { date_list[[i]]$children[[j]]$children[[k]] <- list() date_list[[i]]$children[[j]]$children[[k]]$text <- tmp2 %>% pull(day) %>% .[k] date_list[[i]]$children[[j]]$children[[k]]$data <- list( query = paste0( "img.date = date('", tmp2 %>% pull( value ) %>% .[k], "')" ) ) } } } react$date_list <- date_list }) }, ignoreNULL = F, ignoreInit = T ) #"date_list" reactive # populates date jstree draw_date_tree <- eventReactive( react$date_list, { jstree( react$date_list, checkboxes = T ) }) #"options" (input) reactive # shows modal draw_options_modal <- observeEvent( input$options, { showModal( modalDialog( title = "Options", radioButtons( "aggregate_by", "Aggregate By", choices = c("None","Duplicate/Cropped","Encounter","Individual"), selected = settings$aggregate_by, inline = T ), radioButtons( "update_single_all", "Update [Single/All] Aggregated Nodes (Duplicate/Cropped/Encounter only)", choices = c("Single","All"), selected = settings$update_single_all, inline = T ), checkboxGroupInput( "show_nodes", "Show Nodes", choices = list( "Unusable" = "unusable", "Encounter" = "Encounter", "Event" = "Event"), selected = names(settings$show_nodes)[which(settings$show_nodes)], inline = TRUE ), checkboxGroupInput( "show_edges", "Show Edges", choices = list( "IS SAME" = "IS_SAME", "UNSURE" = "UNSURE", "NOT SAME" = "NOT_SAME" ), selected = names(settings$show_edges)[which(settings$show_edges)], inline = TRUE ), checkboxInput( "zoom_to_fin", "Zoom to fin", value = settings$zoom_to_fin ), actionButton( "refresh_graph", label = "Refresh Graph" ), easyClose = TRUE, footer = NULL ) ) }) #"aggregate_by" (input) reactive # updates setting # triggers run if a query is present #TODO: WARN IF CHANGE WILL CREATE BIG GRAPH! update_aggregate_by <- observeEvent( input$aggregate_by, { if( !is.null(payload$query) & !settings$aggregate_by == input$aggregate_by ) { settings$aggregate_by <- input$aggregate_by react$draw <- NULL react$run <- ifelse( is.null( react$run ), 0, react$run + 1 ) } else { settings$aggregate_by <- input$aggregate_by } }, ignoreNULL = T, ignoreInit = T ) #"update_single_all" (input) reactive # updates setting # triggers draw update_update_single_all <- observeEvent( input$update_single_all, { if( !is.null(payload$query) & !settings$update_single_all == input$update_single_all) { settings$update_single_all <- input$update_single_all react$draw <- ifelse( is.null( react$draw ), NULL, react$draw + 1 ) } else { settings$update_single_all <- input$update_single_all } }, ignoreNULL = T, ignoreInit = T ) #"show_nodes" (input) reactive # updates setting # triggers style update_show_nodes <- observeEvent( input$show_nodes, { if( !is.null(payload$query) & !setequal( input$show_nodes, names(settings$show_nodes)[which(settings$show_nodes)] ) ) { settings$show_nodes[input$show_nodes] <- T settings$show_nodes[!names(settings$show_nodes) %in% input$show_nodes] <- F react$style <- ifelse( is.null( react$style ), NULL, react$style + 1 ) } else { settings$show_nodes[input$show_nodes] <- T settings$show_nodes[!names(settings$show_nodes) %in% input$show_nodes] <- F } }, ignoreNULL = F, ignoreInit = T ) #"show_edges" (input) reactive # updates setting # triggers style update_show_edges <- observeEvent( input$show_edges, { if( !is.null(payload$query) & !setequal( input$show_edges, names(settings$show_edges)[which(settings$show_edges)] ) ) { settings$show_edges[input$show_edges] <- T settings$show_edges[!names(settings$show_edges) %in% input$show_edges] <- F react$style <- ifelse( is.null( react$style ), NULL, react$style + 1 ) } else { settings$show_edges[input$show_edges] <- T settings$show_edges[!names(settings$show_edges) %in% input$show_edges] <- F } }, ignoreNULL = F, ignoreInit = T ) #"zoom_to_fin" (input) reactive # updates setting # triggers draw update_zoom_to_fin <- observeEvent( input$zoom_to_fin, { if( !is.null(payload$query) & !settings$zoom_to_fin == input$zoom_to_fin ) { settings$zoom_to_fin <- input$zoom_to_fin react$draw <- ifelse( is.null( react$draw ), NULL, react$draw + 1 ) } else { settings$zoom_to_fin <- input$zoom_to_fin } }, ignoreNULL = T, ignoreInit = T ) #"refresh_graph" (input) reactive # triggers draw refresh_graph <- observeEvent( input$refresh_graph, { if( !is.null(payload$query) ) { react$draw <- NULL react$run <- ifelse( is.null( react$run ), NULL, react$run + 1 ) } }, ignoreNULL = T, ignoreInit = T ) # OUTPUTS output$valid_images <- renderText({ react$valid_img_count }) output$unreviewed_images <- renderText({ react$unreviewed_img_count }) output$date_tree <- renderJstree({ draw_date_tree() }) output$graph <- renderVisNetwork({ draw_results() }) output$from_image <- renderSvgPanZoom({ draw_from() }) output$to_image <- renderSvgPanZoom({ draw_to() })
#Statistics div( "Valid Images: ", textOutput( "valid_images", inline = T ) ) div( "Unreviewed Images: ", textOutput( "unreviewed_images", inline = T ) ) actionLink( "refresh", label = "Refresh" )
#Sidebar Inputs textAreaInput( "manual_query", "Query") br() actionButton( "run_manual_query", label = "Run Query" ) hr() actionButton( "query_fix_edges", label = "Query Next Folder [????]" ) hr() wellPanel( jstreeOutput( "date_tree" ), style = " overflow-y:scroll; height: 15vh; padding: 0px 19px; background-color: white; border: 1px solid #cccccc; border-radius: 4px " ) actionButton( "options", label = "Options" )
#withSpinner()? visNetworkOutput( "graph" )
fillRow( height = 650, flex = c( 2, 1, 2 ), fillCol( flex = c( 1, 1, 1, 1, 1, 7, 1 ), fillRow( flex = c( 1, 3 ), "Image UUID: ", textOutput( "from_id", inline = T ) ), fillRow( flex = c( 1, 3 ), "Image path: ", textOutput( "from_path", inline = T ) ), fillRow( flex = c( 1, 3 ), "EXIF date: ", textOutput( "from_exif_date", inline = T ) ), fillRow( flex = c( 1, 3 ), "Date: ", dateInput( "from_date", label = NULL, value = NA ) ), fillRow( flex = c( 1, 3 ), "Region: ", textInput( "from_region", label = NULL, value = NULL ) ), svgPanZoomOutput( "from_image" ), fillRow( flex = c( 2, 2, 2 ), checkboxInput( "from_unusable", "Unusable" ), checkboxInput( "from_reviewed", "Reviewed" ), actionButton( "save_from", "Update" ) ) ), fillCol( flex = c(0.5,2,1,0.25,0.5,1,1), fillRow( flex = c( 1, 3, 1 ), "", "Classification", "" ), fillRow( flex = c( 1, 3, 1 ), "", radioButtons( "label", label = NULL, choices = c( "IS SAME" = "IS_SAME", "UNSURE" = "UNSURE", "NOT SAME" = "NOT_SAME" ), selected = "UNSURE" ), "" ), fillRow( flex = c( 1, 5, 1 ), "", selectInput( "basis", "Basis", choices = c( "", "notch match" = "notch match", "pigment match" = "pigment match", "burst image" = "burst image", "tag presence" = "tag presence", "duplicate" = "duplicate", "cropped" = "cropped", "service" = "service" ) ), "" ), fillRow(), fillRow( flex = c( 1, 3, 1), "", textOutput( "score", inline = T ), ""), fillRow( flex = c( 1, 3, 1 ), "", checkboxInput( "reviewed", "Reviewed" ), "" ), fillRow( flex = c( 1, 3, 1 ), "", actionButton( "classify_label", "Submit" ), "" ) ), fillCol( flex = c( 1, 1, 1, 1, 1, 7, 1 ), fillRow( flex = c( 1, 3 ), "Image UUID: ", textOutput( "to_id", inline = T ) ), fillRow( flex = c( 1, 3 ), "Image path: ", textOutput( "to_path", inline = T ) ), fillRow( flex = c( 1, 3 ), "EXIF date: ", textOutput( "to_exif_date", inline = T ) ), fillRow( flex = c( 1, 3 ), "Date: ", dateInput( "to_date", label = NULL, value = NA ) ), fillRow( flex = c( 1, 3 ), "Region: ", textInput( "to_region", label = NULL, value = NULL ) ), svgPanZoomOutput( "to_image" ), fillRow( flex = c( 2, 2, 2 ), checkboxInput( "to_unusable", "Unusable" ), checkboxInput( "to_reviewed", "Reviewed" ), actionButton( "save_to", "Update" ) ) ) )
#LISTENERS #"active_tab" react # sets "query" payload # triggers "query_match_result" read_match_result <- observeEvent( react$active_tab, { if( react$active_tab == "Process Task Results" ) { # re-trigger to refresh react$task_result <- NULL react$task_result <- read_json(task_results_file)[[1]] } }, ignoreNULL = F, ignoreInit = T ) throb_from_query <- observeEvent( react$task_result, { payload$match_edge_meta <- payload$match_from <- payload$match_to <- NULL react$match_from_throb = T react$match_to_throb = T react$continue_query <- ifelse( is.null( react$continue_query ), 0, react$continue_query + 1 ) }, ignoreNULL = T, ignoreInit = T) #"continue_match" reactive # executes query # triggers "review_match" with increment run_match_query <- observeEvent( react$continue_query, { # query db with progress bar withProgress(message = 'Querying Database...', value = 0, { match <- react$task_result #this check is relatively time consuming is_connected <- paste0("MATCH (img1:Image {d_uuid: '", match$from, "'}), (img2:Image {d_uuid: '", match$to, "'}) WHERE COALESCE(img1.unusable, false) = false AND COALESCE(img2.unusable, false) = false RETURN COUNT(img1) = 0 OR COUNT(img2) = 0 AS valid") %>% cypher(con, type="row") %>% pluck("valid") %>% pull(value) | paste0( "MATCH p=allShortestPaths((img1:Image {d_uuid: '", match$from, "'})-[:IS_SAME|NOT_SAME*]-(img2:Image{d_uuid: '", match$to, "'})) RETURN count(nodes(p)) AS c") %>% cypher(con, type="row") %>% pluck("c") %>% pull(value) > 0 | paste0( "MATCH (img1:Image {d_uuid: '", match$from, "'}) ", "WHERE COALESCE(img1.unusable, false) <> true ", "OPTIONAL MATCH (img1)-[r:IS_SAME]-(j:Image) WHERE r.basis IN ['duplicate', 'cropped'] ", "WITH COLLECT(img1) + COLLECT(j) AS imgs1 ", "MATCH (img2:Image {d_uuid: '", match$to,"'}) ", "WHERE COALESCE(img2.unusable, false) <> true ", "OPTIONAL MATCH (img2)-[r:IS_SAME]-(j:Image) WHERE r.basis IN ['duplicate', 'cropped'] ", "WITH imgs1, COLLECT(img2) + COLLECT(j) AS imgs2 ", "UNWIND imgs1 AS img1 UNWIND imgs2 AS img2 ", "MATCH (img1)-[r:UNSURE]-(img2) ", "RETURN COUNT(DISTINCT r) AS c") %>% cypher(con, type="row") %>% pluck("c") %>% pull(value) > 0 incProgress(0.25) #TODO: adjustable LIMIT 5? if(!is_connected) { query <- paste0( "MATCH (img1:Image {d_uuid: '", match$from, "'}) ", "WHERE COALESCE(img1.unusable, false) <> true ", "OPTIONAL MATCH (img1)-[rel1:IS_SAME*1..5]-(same1: Image) ", "WHERE ALL(rel in rel1 WHERE NOT rel.basis IN ['duplicate', 'cropped']) ", "AND COALESCE(same1.unusable, false) <> true ", "WITH img1, rel1, same1 LIMIT 5 ", "WITH COLLECT(img1) + COLLECT(same1) AS imgs, COLLECT(rel1) AS rels ", "WITH imgs, rels ", "MATCH (img2:Image {d_uuid:'", match$to,"'}) ", "WHERE COALESCE(img2.unusable, false) <> true ", "OPTIONAL MATCH (img2)-[rel2:IS_SAME*1..5]-(same2: Image) ", "WHERE ALL( rel IN rel2 WHERE NOT rel.basis IN ['duplicate', 'cropped']) ", "AND COALESCE(same2.unusable, false) <> true ", "WITH imgs, rels, img2, rel2, same2 LIMIT 5 ", "WITH imgs + COLLECT(img2) + COLLECT(same2) AS imgs, rels + COLLECT(rel2) AS rels ", # had something like this to get most connected nodes #"WITH img1, img2, rels1, same1, SIZE((same1)-[:IS_SAME]-(:Image)) as cnt ", #"WITH img1, img2, rels1, same1 ORDER BY cnt DESC LIMIT 5 ", "UNWIND imgs AS img ", "OPTIONAL MATCH (img)-[rel: HAS_ANNOTATION]-(a: Annotation) ", "WITH COLLECT(id(img)) AS ids, imgs + COLLECT(a) AS nodes, rels + COLLECT(rel) AS rels ", "OPTIONAL MATCH (img)-[r]-(j:Image) ", "WHERE id(img) IN ids AND id(j) IN ids WITH nodes, rels + COLLECT(r) AS rels ", "RETURN nodes, rels" ) # query data from neo4j graph <- query %>% cypher( con, "graph" ) #@todo: move all this into a function if( length(graph) ) { incProgress(0.5) # add potentially empty property columns node_cols <- tibble( trailing = NA_character_, leading = NA_character_, image_size = NA_character_, x = NA_character_, y = NA_character_, tip = NA_character_ ) edge_cols <- tibble( id = NA_character_ , from = NA_character_, to = NA_character_, label = NA_character_, chosen = NA_character_, hidden = NA_character_ ) # unnest nodes graph$nodes <- graph %>% pluck("nodes") %>% unnest_wider( col = "properties" ) %>% add_column( !!!node_cols[!names( node_cols ) %in% names(.)] ) %>% mutate( label = unlist(label) %>% .[ . != "DUMMY" ], trailing = map( trailing, ~c( unlist(.) ) ), leading = map( leading, ~c( unlist(.) ) ), image_size = map( image_size, ~c( unlist(.) ) ), x = map( x, ~c( unlist(.) ) ), y = map( y, ~c( unlist(.) ) ), tip = map( tip, ~c( unlist(.) ) ) ) if(!is.null(graph$relationships)) { graph$nodes <- graph$nodes %>% left_join( { graph$relationships %>% filter( type == "HAS_ANNOTATION" ) %>% select(startNode, endNode) %>% mutate( has_annot = T ) }, by = c( "id" = "startNode" ) ) %>% left_join( {select(., id, image_size, trailing, tip, leading, x, y, )}, by = c( "endNode" = "id" ) ) %>% select( -trailing.x, -image_size.x, -leading.x, -x.x, -y.x, -tip.x, -endNode ) %>% rename( image_size = image_size.y, trailing = trailing.y, tip = tip.y, leading = leading.y, x = x.y, y = y.y ) %>% filter( label != "Annotation" ) } else { graph$nodes <- graph$nodes %>% mutate( has_annot = F ) } graph$nodes <- graph$nodes %>% distinct( id, .keep_all = T ) # unnest relationships if( !is.null( graph$relationships ) ) { graph$relationships <- graph$relationships %>% unnest_relationships() %>% filter( type != "HAS_ANNOTATION" ) %>% #adds "value" column?? select_if(!names(.) %in% c('value')) %>% rename( from = startNode, to = endNode, label = type ) %>% add_column( !!!edge_cols[!names( edge_cols ) %in% names(.)] ) #pseudonodes from relationships (nested tibble) igraph_object<- graph_from_data_frame( d = graph$relationships %>% left_join( graph$nodes %>% select( id, date ), by=c( "from" = "id" ) ) %>% rename( from_date = date ) %>% left_join( graph$nodes %>% select( id, date ), by=c( "to" = "id" ) ) %>% rename( to_date = date ) %>% filter( label == 'IS_SAME' ) %>% select( from, to, everything() ), directed = F, vertices = graph$nodes ) comp <- groups( components( igraph_object ) ) %>% map_df( ~ tibble( id = as.character(md5(paste0( sort( . ), collapse = ", ") ) ), members = list(.) ) ) graph$nodes <- graph$nodes %>% mutate( group = comp[components( igraph_object )$membership, "id"] %>% pull(id) ) %>% nest( data = !group ) %>% select( id = group, data ) %>% mutate( label = map_chr( data, ~unique( .$label ) ) ) #drop the relationships we grouped (if there are any) if( nrow(graph$relationships) ) { graph$relationships <- graph$relationships %>% rowwise() %>% mutate( keep = map2_lgl( from, to, ~ !any( sapply( comp$members, function(e) is.element( .x, e ) & is.element( .y, e ) ) ) ), from = comp[which( sapply( comp$members, function(e) is.element( from, e ) ) ), "id"] %>% pull(id), to = comp[which( sapply( comp$members, function(e) is.element( to, e ) ) ), "id"] %>% pull(id) ) %>% ungroup() %>% filter( keep ) %>% select( -keep ) } } else { graph$nodes <- graph$nodes %>% mutate( group = uuid ) %>% nest( data = !group ) %>% select( id = group, data ) %>% mutate( label = map_chr( data, ~unique( .$label ) ) ) graph$relationships <- tibble() %>% add_column( !!!edge_cols[!names( edge_cols ) %in% names(.)] ) } } if( length(graph$nodes) ) { incProgress(1) payload$graph <- graph react$copy <- ifelse( is.null( react$copy ), 0, react$copy + 1 ) payload$match_from <- graph$nodes %>% slice(1) %>% select( -id, -label ) %>% unnest( cols = c(data) ) payload$match_to <- graph$nodes %>% slice(2) %>% select( -id, -label ) %>% unnest( cols = c(data) ) payload$match_edge_meta <- list(score = react$task_result$score) } else { result_list <- read_json(task_results_file)[-1] write_json( do.call(bind_rows, result_list), task_results_file) react$task_result <- result_list[[1]] } } else { result_list <- read_json(task_results_file)[-1] write_json( do.call(bind_rows, result_list), task_results_file) react$task_result <- result_list[[1]] } }) }) #"match_from" reactive # sets from viewer img index match_from <- observeEvent( payload$match_from, { if( !is.null(payload$match_from) ) { session$sendCustomMessage( "setMatchFromImgIDX", 1 ) } }, ignoreNULL = F, ignoreInit = T ) # "match_from_img_idx" (input) reactive # populates from viewer # updates input field values # return svgPanZoom draw_match_from <- eventReactive( input$match_from_img_idx, { if( !is.null(payload$match_from ) & !is.null(input$match_from_img_idx) ) { img <- payload$match_from %>% slice( input$match_from_img_idx ) output$match_from_id <- renderText({ paste0( img %>% pull(uuid), " [", input$match_from_img_idx, "/", nrow(payload$match_from), "]" ) }) react$match_from_throb = F return( draw_img( img ) ) } else { output$match_from_id <- renderText({ "" }) return(NULL) } }, ignoreNULL = F) #"to" reactive # sets to viewer img index match_to <- observeEvent( payload$match_to, { if( !is.null(payload$match_to) ) { session$sendCustomMessage( "setMatchToImgIDX", 1 ) } }, ignoreNULL = F, ignoreInit = T ) # "match_to_img_idx" (input) reactive # populates to viewer # updates input field values # return svgPanZoom draw_match_to <- eventReactive( input$match_to_img_idx, { if( !is.null(payload$match_to ) & !is.null(input$match_to_img_idx) ) { img <- payload$match_to %>% slice( input$match_to_img_idx ) output$match_to_id <- renderText({ paste0( img %>% pull(uuid), " [", input$match_to_img_idx, "/", nrow(payload$match_to), "]" ) }) react$match_to_throb = F return( draw_img( img ) ) } else { output$match_to_id <- renderText({ "" }) return(NULL) } }, ignoreNULL = F) #"match_edge_meta" reactive # populates edge viewer # updates input values from meta draw_match_classification <- observeEvent( payload$match_edge_meta, { if( !is.null(payload$match_edge_meta ) ) { updateRadioButtons( session, "match_label", label = NULL, choices = NULL, selected = "UNSURE" ) output$match_score <- renderText({ ifelse( is.na( payload$match_edge_meta$score ), "", paste0("Score: ", round(as.numeric(payload$match_edge_meta$score), 2)) ) }) } else { updateRadioButtons( session, "match_label", label = NULL, choices = NULL, selected = "UNSURE" ) } }, ignoreNULL = F ) throb_from_update <- observeEvent( input$match_classify_label, { react$match_from_throb = T react$match_to_throb = T react$continue_update <- ifelse( is.null( react$continue_update ), 0, react$continue_update + 1 ) }, ignoreInit = T) #"match_classify_label" (input) reactive # updates edge in database # triggers next task result update_match_classification <- observeEvent( react$continue_update, { withProgress( message = "Updating Database...", value = 0.5, { update_edge( payload$match_from$uuid[1], payload$match_to$uuid[1], input$match_label, 'service', T, con, payload ) payload$match_edge_meta <- payload$match_from <- payload$match_to <- NULL result_list <- read_json(task_results_file)[-1] write_json( do.call(bind_rows, result_list), task_results_file) if(length(result_list)) { react$task_result <- result_list[[1]] } else { showNotification( "No more tasks!", duration = NULL, type = "warning") } }) }) throb_from <- observeEvent( react$match_from_throb, { if(react$match_from_throb) { session$sendCustomMessage( "throbMatchFrom", T ) } }, ignoreInit = T ) throb_to <- observeEvent( react$match_to_throb, { if(react$match_to_throb) { session$sendCustomMessage( "throbMatchTo", T ) } }, ignoreInit = T ) output$match_from_image <- renderSvgPanZoom({ draw_match_from() }) output$match_to_image <- renderSvgPanZoom({ draw_match_to() })
fillRow( height = 650, flex = c( 2, 1, 2 ), fillCol( flex = c( 1, 7 ), fillRow( flex = c( 1, 3 ), "Image UUID: ", textOutput( "match_from_id", inline = T ) ), svgPanZoomOutput( "match_from_image" ) ), fillCol( flex = c(0.5,2,0.25,0.5,1), fillRow( flex = c( 1, 3, 1 ), "", "Classification", "" ), fillRow( flex = c( 1, 3, 1 ), "", radioButtons( "match_label", label = NULL, choices = c( "IS SAME" = "IS_SAME", "UNSURE" = "UNSURE", "NOT SAME" = "NOT_SAME" ), selected = "UNSURE" ), "" ), fillRow(), fillRow( flex = c( 1, 3, 1), "", textOutput( "match_score", inline = T ), ""), fillRow( flex = c( 1, 3, 1 ), "", actionButton( "match_classify_label", "Submit" ), "" ) ), fillCol( flex = c( 1, 7 ), fillRow( flex = c( 1, 3 ), "Image UUID: ", textOutput( "match_to_id", inline = T ) ), svgPanZoomOutput( "match_to_image" ) ) )
#"upload_photos" (input) reactive upload <- observeEvent( input$upload_photos, { req(input$upload_photos) ## change all these to progress bar? react$upload_status <- paste( "Found", nrow(input$upload_photos), "valid photos...\n" ) if( !is.null( pars$api_url ) ) { exists <- exists_in_database( pars$api_url, uuid_from_file( input$upload_photos$datapath ) ) react$upload_status <- paste0( react$upload_status, paste( sum( !exists ), "image(s) not in database...\n" ) ) for( i in 1:nrow( input$upload_photos[!exists,] ) ) { react$upload_status <- paste0( react$upload_status, paste0( "Uploading ", input$upload_photos[!exists, "name"][i], "...\n" ) ) ## GET PRESIGNED URLS ## ACTUALLY DO UPLOAD ## ACTUALLY DO REGISTRATION OF IMAGES ## STORE NODE IN NEO } } else { react$upload_status <- "Error: api_url not specified." } }, ignoreNULL = T, ignoreInit = T ) # OUTPUTS output$upload_status <- renderText({ react$upload_status })
fileInput( "upload_photos", "Choose Photos", multiple = TRUE, accept = c( "image/jpeg", "image/png" ) )
verbatimTextOutput( "upload_status" )
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.