I'm currently working on image processing application using R Shiny It uploads an image using file upload and then I need to read the image to do the image processing operations. server.R file is as follows.
library(shiny)
library(EBImage)
library(imager)
library(jpeg)
function(input, output) {
observe({
file_path <- input$files
if (is.null(file_path))
return(NULL)
file_path$datapath <- gsub("\\\\", "/", file_path$datapath)
img <- readImage(file_path$datapath)
equalized <- equalize(img,range = c(0, 1), levels = 256)
output$text <- renderText({
file_path$datapath
})
output$img <- renderImage({
list(src = file_path$datapath,
contentType = "image/jpg",
width = "50%",
height = "auto",
alt = "This is alternate text")
})
})
}
But this gives me the following error.
Warning: Error in readImage: Please supply at least one filename.
Stack trace (innermost first):
57: readImage
56: observerFunc
I managed to plot an equalized image using raster method. Here are some tips/tricks:
You put everything inside an observer which is a pretty bad idea, so I got rid of that.
Use req() when checking whether a file is uploaded, UI is rendered, etc instead of an if statement. `
if (is.null(file_path)) return(NULL)
There is no need to assign input$files to a variable, you can call input$files$datapath. Also gsub() is not needed in this case.
file_path <- input$files
file_path$datapath <- gsub("\\\\", "/", file_path$datapath)
equalized is calculated, but you don't use it anywhere.
Solution
Checking with req() whether a file is uploaded.
Get the extension of the file (splitting by ., getting the last element)
Plot the equalized image using the display() function with method = "raster".
Print datapath which points to a temp dir/file
See:
library(shiny)
library(EBImage)
library(imager)
library(jpeg)
ui <- fluidPage(
fileInput("files", "Upload a file"),
plotOutput("img"),
textOutput("txt")
)
server <- function(input, output) {
output$img <- renderPlot({
req(input$files)
st <- strsplit(input$files$name, split = "[.]")[[1]]
extension <- st[length(st)]
display(equalize(readImage(input$files$datapath, type = extension), range = c(0, 1), levels = 256), method = "raster")
})
output$txt <- renderText({
input$files$datapath
})
}
shinyApp(ui, server)
Using EBImage we can load an image into Rshiny and use it for further processing. Below code allows the user to upload an image and then same is displayed on shiny screen back.
library(shiny)
library(EBImage)
upload_image <- list()
ui <- fluidPage(
fileInput("file1", "Upload an Image"),
plotOutput("img")
)
server <- function(input, output) {
output$img <- renderPlot({
req(input$file1)
upload_image[[1]] <- readImage(input$file1$datapath)
plot(upload_image[[1]])
})
}
shinyApp(ui , server)
Related
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.
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
I have an R shiny app that gets a .csv import from a user and searches the imported data across a built-in data frame, then gives the % match in the output. The UI is very simple, with a few different inputs (import .csv, a slider, and some radio buttons). What I want is to be able to take the reactive table output and print this to a .csv that the user can download to their machine. The server side of the app looks something like this:
server <- function(input, output){
rvals <- reactiveValues()
observeEvent(input$file_1,{
req(input$file_1)
rvals$csv <<- read.csv(input$file_1$datapath, header = TRUE)
#some data processing here
})
output$contents <- renderTable({
if(input$select == 1){
x <- function
}else if(input$select == 2){
x <- function
}else if(input$select == 3){x <- function}
#some more data processing and formatting here
return(x)
},digits = 4)
}
I would like to have the data table x be able to become a .csv that can be downloaded by clicking a download button. In the server, I added the following code, but when I try to download the data it just downloads a blank file and says "SERVER ERROR" in my downloads manager on my machine.
output$downloadData <- downloadHandler(
filename = "thename.csv",
content = function(file){
write.csv(x, file)
}
In the console I also get the error message:
Warning: Error in is.data.frame: object 'x' not found [No stack trace available]
The object you create inside the expression of renderTable is not available outside of it. Instead you could assign it to the reactive values you set up. Below is a working example (note that I have tried to replicate your code so the data will not be available until you click on "Upload CSV", which here just calls mtcars).
library(shiny)
ui = fluidPage(
sidebarPanel(
actionButton(inputId = "uploadCsv", label = "Upload CSV:", icon = icon("upload")),
selectInput(inputId = "preProc", label = "Pre-processing", choices = c("Mean"=1,"Sum"=2)),
downloadButton("downloadData", label = "Download table")
),
mainPanel(
h4("My table:"),
tableOutput("contents")
)
)
server <- function(input, output) {
rvals <- reactiveValues(
csv=NULL,
x=NULL
)
observeEvent(input$uploadCsv,{
rvals$csv <- mtcars # using example data since I don't have your .csv
# rvals$csv <- read.csv(input$file_1$datapath, header = TRUE)
#some data processing here
})
output$contents <- renderTable({
# Assuing the below are functions applied to your data
req(
input$preProc,
!is.null(rvals$csv)
)
if(input$preProc == 1){
rvals$x <- data.frame(t(colMeans(mtcars)))
}else {
rvals$x <- data.frame(t(colSums(mtcars)))
}
return(rvals$x)
},digits = 4)
output$downloadData <- downloadHandler(
filename = "myFile.csv",
content = function(file){
write.csv(rvals$x, file)
}
)
}
shinyApp(ui,server)
EventReactive already outputs a reactive value, you don't need to create an extra reactiveVal, see example below :
library(shiny)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("Test"),
mainPanel(
actionButton("show", "Download"),
textOutput("result")
)
)
server <- function(input, output) {
csvfile <- eventReactive(req(input$show), ignoreNULL = T, {
"Content of file"
})
output$result <- reactive(
paste("result : ",csvfile()))
}
# Run the application
shinyApp(ui = ui, server = server)
I would also avoid to use <<-operator in a reactive expression.
I have the shiny app below which makes image recognition. I upload the image below and it is displayed as you will see. The issue is that I want to use the name of this file ("DJI_0104.jpg") inside the stack() function in order to create a stack of the image. Then I plot the result (textures). Basically I am looking for a way to use only the name of the uploaded image -like in R104() which works-. You can find the .jpg here
library(shiny)
library(base64enc)
library(raster)
library(glcm)
options(shiny.maxRequestSize = 30*1024^2)
ui <- fluidPage(
fileInput("upload", "Upload image", accept = "image/png"),
uiOutput("image"),
plotOutput("textures")
)
server <- function(input, output){
base64 <- reactive({
inFile <- input[["upload"]]
if(!is.null(inFile)){
dataURI(file = inFile$datapath, mime = "image/png")
}
})
output[["image"]] <- renderUI({
if(!is.null(base64())){
tags$div(
tags$img(src= base64(), width="100%"),
style = "width: 400px;"
)
}
})
#That does not work
R105 <- reactive({
stack(base64())
})
#That works
R104 <- reactive({
stack("DJI_0104.jpg")
})
textures <- reactive({
glcm(raster(R105(), layer=3))
})
output$textures<-renderPlot({
#Create a stack of the image
plot(textures())
})
}
shinyApp(ui, server)
you can find more details here, but here is a solution: in the base64() reactive, inFile is built after the output of fileInput(), output which possesses 4 fields: name, size, type and datapath (see link above).
As Stéphane Laurent underlined, using inFile$name shall make your shinyApp work.
Best.
So I want to have a Shiny page which:
A) Allows the user to upload a .xls file;
B) Offers that file back to the user for download as a .csv file;
C) Prints the head of the file in the Shiny app to ensure that it was properly read.
Here is the code I am using:
# Want to read xls files with readxl package
library(readxl)
library(shiny)
## Only run examples in interactive R sessions
if (interactive()) {
ui <- fluidPage(
fileInput("file1", "Choose File", accept = ".xls"),
tags$hr(),
uiOutput("downloader"),
htmlOutput("confirmText", container = tags$h3),
tableOutput("listContents")
)
server <- function(input, output) {
theOutput <- reactiveValues(temp = NULL, df = NULL, msg = NULL, fn = NULL)
observeEvent(input$file1, {
theOutput$fn <- paste('data-', Sys.Date(), '.csv', sep='')
theOutput$temp <- read_xls(input$file1$datapath)
theOutput$msg <- paste("File Contents:")
theOutput$df <- write.csv(theOutput$temp,
file = theOutput$fn,
row.names = FALSE)
})
output$confirmText <- renderText({
theOutput$msg
})
output$listContents <- renderTable({
head(theOutput$temp)
})
output$downloader <- renderUI({
if(!is.null(input$file1)) {
downloadButton("theDownload", label = "Download")
}
})
output$theDownload <- downloadHandler(
filename = theOutput$fn,
content = theOutput$df
)
}
shinyApp(ui, server)
}
The Shiny page renders correctly, it accepts the upload with no problems, it prints out the head of the .csv with no problems, and it creates a properly formatted "data-{today's date}.csv" file in the same directory as the app.R file.
Problem is, when I hit the download button I get the error message:
Warning: Error in download$func: attempt to apply non-function
[No stack trace available]
Can someone tell me what I am doing wrong?
Thanks to the comments above, this is the solution I found (with my comments added, to show where the code changed):
library(readxl)
library(shiny)
if (interactive()) {
ui <- fluidPage(
fileInput("file1", "Choose File", accept = ".xls"),
tags$hr(),
uiOutput("downloader"),
htmlOutput("confirmText", container = tags$h3),
tableOutput("listContents")
)
server <- function(input, output) {
theOutput <- reactiveValues(temp = NULL, msg = NULL)
observeEvent(input$file1, {
# Do not try to automate filename and the write.csv output here!
theOutput$temp <- read_xls(input$file1$datapath)
theOutput$msg <- paste("File Contents:")
})
output$confirmText <- renderText({
theOutput$msg
})
output$listContents <- renderTable({
head(theOutput$temp)
})
output$downloader <- renderUI({
if(!is.null(input$file1)) {
downloadButton("theDownload", label = "Download")
}
})
output$theDownload <- downloadHandler(
# Filename and content need to be defined as functions
# (even if, as with filename here, there are no inputs to those functions)
filename = function() {paste('data-', Sys.Date(), '.csv', sep='')},
content = function(theFile) {write.csv(theOutput$temp, theFile, row.names = FALSE)}
) }
shinyApp(ui, server) }
The fact that content takes an argument (named here "theFile"), which is not called anywhere else, is what was throwing me off.
I have tried to apply the answers given in both this question on saving plots, and this question on downloading reactive outputs without success. I am not sure if my reactive function is outputting the wrong type of data or if my downloadHandler() is not written properly.
Also, the linked questions pass function()s to reactive() which I am warned is deprecated, so I have avoided it here. (The behavior did not change, though.)
ui.R:
library(shiny)
# Define UI for application
shinyUI(pageWithSidebar(
# Application title
headerPanel("App"),
sidebarPanel(
downloadButton("savemap", label="Download Map (Hires)")
),
mainPanel(
tabsetPanel(
tabPanel("Map",
plotOutput("myworld", height="650px",width="750px",
clickId="plotclick")
)
)
)
))
server.R
library(shiny)
library(maps)
library(mapdata)
library(rworldmap)
library(gridExtra)
shinyServer(function(input, output) {
theworld <- reactive({
myplot <- map("world2", wrap=TRUE, plot=TRUE,
resolution=2)
})
output$myworld <- renderPlot({
print(theworld())
})
output$savemap <- downloadHandler(
filename = function() {
paste('fuzzymap-', Sys.Date(), '.png', sep="")
},
content = function(file) {
# function to actually write the image file
# https://stackoverflow.com/questions/14810409/save-plots-made-in-a-shiny-app?rq=1
png(file)
print(theworld())
dev.off()
})
})
The map in the reactive function is plotted sucessfully at startup. The download prompt is generated and a png file downloads, but it contains no data. Additionally, the following non-fatal error is returned, but a search turned up no leads:
Error opening file: 2
Error reading: 9
I think you're confused about what map() returns. You call it for its
side-effect of drawing a plot, not it's return value. Rather than making
it a reactive value, just keep it as an anonymous function.
Here's a simplified version that works for me:
library(shiny)
library(maps)
ui <- bootstrapPage(
plotOutput("myworld", height = 450, width = 750),
downloadButton("savemap", "Download map")
)
theworld <- function() {
map("world2", wrap = TRUE, resolution = 2)
}
server <- function(input, output) {
output$myworld <- renderPlot({
theworld()
})
output$savemap <- downloadHandler(
filename = function() {
paste0("fuzzymap-", Sys.Date(), ".png")
},
content = function(file) {
png(file)
theworld()
dev.off()
}
)
}
runApp(list(server = server, ui = ui))