3

I am writing a large Shiny Dashboard script to collect data from files uploaded by users. Some of those files are images. The script get the file through a fileInput in the ui session. Usually, users upload high resolution images, but I do not need to store such files, so the script reduces the size to height = 200 in order to direct it to outputImage. It sends the files to Google Drive (no problem with that), but I would like to send the low resolution files. I tryed to read them from output$showphotos1, but Part of the script:

    ui <- dashboardPage(
               fileInput("loadphotos", label="Carregar fotos", multiple=T),
                          actionButton("do", "Carregar"),
                          imageOutput("showphotos1", height="200px"),
                          imageOutput("showphotos2", height="200px"),
                          imageOutput("showphotos3", height="200px")
    )
    
    server <- function(input, output, session) {
      observeEvent(input$do, {
        lst <- NULL
        for(i in 1:length(input$loadphotos[,1])) {
          lst[[i]] <- input$loadphotos[[i, 'datapath']]
        }
        output$showphotos1 <- renderImage({list(src=lst[[1]], height="200")})
        output$showphotos2 <- renderImage({list(src=lst[[2]], height="200")})
        output$showphotos3 <- renderImage({list(src=lst[[3]], height="200")})

#        drive_upload(output$showphotos1$datapath, 
#               as_id("https://drive.google.com/drive/u/1/folders/1qj0eeee...")
#       This gives an error: "Error in $.shinyoutput: Reading from shinyoutput object     
#       is not allowed." So I used the lines bellow, that uploads large files from     
#       the input:

         drive_upload(input$loadphotos,
                as_id("https://drive.google.com/drive/u/1/folders/1qj0eeee...")
        })
    }

I would like to store the smaller files (200px) that are in output$showphotos instead of the larger ones from input@loadphotos. I am not fluent in R and would appreciate if some one could give me simple solutions for it. Suggestions to avoid code repetitions for each image file are also welcome.

1 Answers1

2

We can use magick::image_scale() to resize the images and then save them in the working directory (or maybe create temp files) because drive_update takes a path as media argument.

Version avoiding code repetitions:

library(shiny)
library(magick)
library(tidyverse)
library(googledrive)

n_showphotos <- 3

ui <- fluidPage(
    fileInput("loadphotos", label = "Carregar fotos", multiple = TRUE),
    actionButton("do", "Carregar"),
    tagList(
    map(str_c('showphotos', 1:n_showphotos), ~imageOutput(.x, height = '200px')))
)

server <- function(input, output, session) {
    
    observeEvent(input$do, {
        

        lst <- NULL
        for (i in 1:length(input$loadphotos[,1])) {
            lst[[i]] <- input$loadphotos[[i, 'datapath']]
        }
        
        lst %>%
            map2(str_c('showphotos', 1:length(.)),~ { output[[.y]] <- renderImage({list(src = .x, height="200")},deleteFile = FALSE) })
        
        #a list with all the images but resized to 200
        #"x200" to resize by height
        images_resized <- lst %>% 
            map(~image_scale(image = image_read(.x), "200"))
        
        #images will be located in the project directory or home folder (getwd() to get working directory if in doubt)
        images_resized %>%
            walk2(str_c('image', 1:length(.)), ~ image_write(.x, path = str_c(.y, '.png'), format = "png"))
        
        
        #        drive_upload(image1.png, 
        #               as_id("https://drive.google.com/drive/u/1/folders/1qj0eeee...")
   
    })
    
}

shinyApp(ui, server)

With code repetition:

library(tidyverse)
library(googledrive)
library(shiny)
library(magick)

ui <- fluidPage(
    fileInput("loadphotos", label="Carregar fotos", multiple=T),
    actionButton("do", "Carregar"),
    imageOutput("showphotos1", height="200px"),
    imageOutput("showphotos2", height="200px"),
    imageOutput("showphotos3", height="200px")
)

server <- function(input, output, session) {
    observeEvent(input$do, {
        lst <- NULL
        req(input$loadphotos)
        for(i in 1:length(input$loadphotos[,1])) {
            lst[[i]] <- input$loadphotos[[i, 'datapath']]
        }
        output$showphotos1 <- renderImage({list(src=lst[[1]], height="200")})
        output$showphotos2 <- renderImage({list(src=lst[[2]], height="200")})
        output$showphotos3 <- renderImage({list(src=lst[[3]], height="200")})
        
        
        images_resized <- NULL
        for (i in 1:length(lst)) {
            
           image_scale(image = image_read(lst[[i]]), '200') %>% 
           image_write(path = str_c('image', i, '.png'), format = "png")
            
        }
        
        #image1.png ... image3.png are available in the working directory.
        
        #        drive_upload(image1, 
        #               as_id("https://drive.google.com/drive/u/1/folders/1qj0eeee...")

        
        })
}

shinyApp(ui, server)

Edit: Adjust ui based on the number of images uploaded by the user.

library(shiny)
library(magick)
library(tidyverse)
library(googledrive)

ui <- fluidPage(
    fileInput("loadphotos", label = "Carregar fotos", multiple = TRUE),
    actionButton("do", "Carregar"),
    uiOutput('images_outputs')
)

server <- function(input, output, session) {
    
    observeEvent(input$do, {
        
        lst <- NULL
        for (i in 1:length(input$loadphotos[,1])) {
            lst[[i]] <- input$loadphotos[[i, 'datapath']]
        }
        
        output$images_outputs <- renderUI({
            tagList(
                map(str_c('showphotos', 1:length(lst)), ~imageOutput(.x, height = '200px')))
        })
        
        lst %>%
            map2(str_c('showphotos', 1:length(.)),~ { output[[.y]] <- renderImage({list(src = .x, height="200")},deleteFile = FALSE) })
        
        #a list with all the images but resized to 200
        #"x200" to resize by height
        images_resized <- lst %>% 
            map(~image_scale(image = image_read(.x), "200"))
        
        #images will be located in the project directory or home folder (getwd() to get working directory if in doubt)
        images_resized %>%
            walk2(str_c('image', 1:length(.)), ~ image_write(.x, path = str_c(.y, '.png'), format = "png"))
        
        
        #        drive_upload(image1.png, 
        #               as_id("https://drive.google.com/drive/u/1/folders/1qj0eeee...")
        
    })
    
}

shinyApp(ui, server)

note: It may be needed to adjust the maximum file size accepted by shiny using options(shiny.maxRequestSize={size}) as shown here

jpdugo17
  • 6,816
  • 2
  • 11
  • 23
  • Jee, I had to study a bit because of the shortcuts, but it was a very nice solution. Thank you for adding two versions of the code, so I could compare them to understand the shortcuts as well. "Two rabbits with a single hit!" ;) – Vitor Hugo Moreau Jun 25 '21 at 17:48
  • jpdugo17, I would have another question. Tell me if it is better to open it as new question. If I want the number of uploaded images to be set by the users? I mean, instead of ```n_showphotos <- 3``` it would be set by the number of images uploaded by the user. I couldn't do that because ```input$loadphotos``` just is set after the user upload the images. R gives me an error "object input not found". – Vitor Hugo Moreau Jun 25 '21 at 23:24
  • Maybe yes, this question is mainly about reducing the images sizes. To dynamically change the amount of images displayed based on user input would require restructuring the app a bit. Also, multiple solutions can be found using either `renderUI` or `insertUI`. – jpdugo17 Jun 26 '21 at 17:35