Search code examples
rshinytesseractflexdashboard

R Shiny busy spinner in flexdashboard OCR


I'm building a little app for OCR.

depending on the image, sometimes teseract takes some time to compute. While it's taking its time, I'd like to add a little spinner saying loading or computing algo.

Here's a simplified extract of my code:

---
title : app demo
author : yeshipants
output : 
  flexdashboard::flex_dashboard:
    orientation: rows
    self_contained : TRUE
    source_code: embed
runtime: shiny
---

```{r setup}
knitr::opts_chunk$set(cache = FALSE)
```


```{r loadPackages}
setwd("C:/Users/y_bho/Desktop/Training/OCR")
library(magick)
library(tesseract)
```

Column {.sidebar data-width=350}
-------------------------------------
### Input & Parameters
```{r inputImages, cache = TRUE}

selectInput("imagesToChoose", 
            label = "Choose an image to process",
            choices = c("Language example 1", 
                        "Language example 2",
                        "Jounal example"),
            selected = "Language example 1") 
```

Row {.tabset}
-------------------------------------  

### Image
```{r displayImage, cache = FALSE}    
renderImage({      
  if (input$imagesToChoose == "Language example 1"){
    list(src = "images/receipt.png", height = 240, width = 300)
  }
  else if(input$imagesToChoose == "Language example 2"){
    list(src = "images/french.JPG", height = 240, width = 300)
  }
  else if(input$imagesToChoose == "Jounal example"){
    list(src = "images/journal.jpg", height = 240, width = 300)
  }    
}, deleteFile = FALSE)

```

### OCR
```{r}
# load the dictionary

imageInput <- reactive({
 if (input$imagesToChoose == "Language example 1"){
    x = "images/receipt.png"
  }
  else if(input$imagesToChoose == "Language example 2"){
    x = "images/french.JPG"
  }
  else if(input$imagesToChoose == "Jounal example"){
    x = "images/journal.jpg"
  }
  return(x)
})

eng <- tesseract("eng")

text <- reactive({
  tesseract::ocr(imageInput(), engine = eng)
})

renderPrint({
  cat(text())
})

```

Basically between the user choosing different images, I want to display "loading" until tesseract is working on the reactive function at the bottom of the code.

I've seen the busy indicator busyIndicator(wait = 1000) in this repo but, first, the package isn't downloading, and second, i don't know where to place it, especially within a flexdashboard.

EDIT

All the while preserving the output obtained from cat(text()). Example; if I want to perform an OCR on the below receipt :

receiptImage

I'd need this output (to capture line by line information):

receiptOutput

output


Solution

  • Here is an example of a flexdashboard with a busy indicator spinner:

    ---
    title: "Untitled"
    output: 
      flexdashboard::flex_dashboard:
        orientation: rows
        includes: 
          after_body: "busy.html"
    runtime: shiny
    ---
    
    ```{r setup, include=FALSE}
    library(flexdashboard)
    ```
    
    Column {.sidebar}
    -----------------------------------------------------------------------
    
    Waiting time between eruptions and the duration of the eruption for the
    Old Faithful geyser in Yellowstone National Park, Wyoming, USA.
    
    ```{r}
    selectInput("n_breaks", label = "Number of bins:",
                choices = c(10, 20, 35, 50), selected = 20)
    
    sliderInput("bw_adjust", label = "Bandwidth adjustment:",
                min = 0.2, max = 2, value = 1, step = 0.2)
    ```
    
    Column
    -----------------------------------------------------------------------
    
    ### Geyser Eruption Duration
    
    ```{r}
    plotOutput("plot")
    output[["plot"]] <- renderPlot({
      Sys.sleep(5) # simulates a time-consuming task
      hist(faithful$eruptions, probability = TRUE, breaks = as.numeric(input$n_breaks),
           xlab = "Duration (minutes)", main = "Geyser Eruption Duration")
      dens <- density(faithful$eruptions, adjust = input$bw_adjust)
      lines(dens, col = "blue")
    })
    ```
    

    The file busy.html, in the same folder:

    <style>
      .busy { 
        position: fixed;
        z-index: 1000;
        top: 50%;
        left: 50%;
        margin-top: -100px;
        margin-left: -50px;
        display: none;
        background-color: rgba(230,230,230,.8);
        text-align: center;
        padding-top: 20px;
        padding-left: 30px;
        padding-bottom: 40px;
        padding-right: 30px;
        border-radius: 5px;
      }
    </style>
    
    <script>
      $(document).ready(function(){
        $("#plot").on("shiny:recalculating", function(e){
          $(".busy").show();
        }).on("shiny:recalculated", function(e){
          $(".busy").hide();
        });
      });
    </script>
    
    <div class = "busy">
      <img src = "https://loading.io/spinners/comets/lg.comet-spinner.gif"/>
    </div>
    

    enter image description here

    So for your case, I would try something like that (I have not tried):

    ```{r}
    # load the dictionary
    imageInput <- reactive({
     if (input$imagesToChoose == "Language example 1"){
        x = "images/receipt.png"
      }
      else if(input$imagesToChoose == "Language example 2"){
        x = "images/french.JPG"
      }
      else if(input$imagesToChoose == "Jounal example"){
        x = "images/journal.jpg"
      }
      return(x)
    })
    
    output[["tesseract"]] <- renderPrint({
      eng <- tesseract("eng")
      tesseract::ocr(imageInput(), engine = eng)
    })
    
    textOutput("tesseract")
    ```
    

    and in busy.html, replace the script with:

    <script>
      $(document).ready(function(){
        $("#tesseract").on("shiny:recalculating", function(e){
          $(".busy").show();
        }).on("shiny:recalculated", function(e){
          $(".busy").hide();
        });
      });
    </script>
    

    (and don't forget after_body: "busy.html" in the YAML header).


    EDIT

    I have tried your flexdashboard now. If you want to use the reactive conductor text:

    eng <- tesseract("eng")
    
    text <- reactive({
      tesseract::ocr(imageInput(), engine = eng)
    })
    
    output[["tesseract"]] <- renderPrint({
      cat(text())
    })
    textOutput("tesseract")
    

    then it's better to do:

    <script>
      $(document).ready(function(){
        $("#tesseract").on("shiny:outputinvalidated", function(e){
          $(".busy").show();
        }).on("shiny:recalculated", function(e){
          $(".busy").hide();
        });
      });
    </script>