Search code examples
rimageshinysliderpng

How to get the x y coordinates from a line of a png image and use it in a shiny app


This is a follow-up question to this Color an area with a sliderInput in a shiny app

Suppose I have this image:

enter image description here

How could I apply this solution by @ismirsehregal to this picture. I think I have to put the x and y from the esophagus to the code, but I don't know how to get the x and y of the esophagues (green in the picture):

Code from Color an area with a sliderInput in a shiny app

library(shiny)
library(plotly)
library(shinyWidgets)

DF <- data.frame(
    x = c(cos(seq(0.01, 10, 0.01)) * 1000:1 + 1000, cos(seq(0.01, 10, 0.01)) * 1000:1 + 1500),
    y = rep(1:1000, 2),
    id = c(rep("trace_1", 1000), rep("trace_2", 1000))
  )

ui <- fluidPage(
  br(),
  column(
    2,
    noUiSliderInput(
      inputId = "noui2",
      label = "Slider vertical:",
      min = 0,
      max = 1000,
      step = 50,
      value = c(100, 400),
      margin = 100,
      orientation = "vertical",
      direction = c("rtl"),
      width = "100px",
      height = "350px"
    )
  ),
  column(4, plotlyOutput("plot")),
  verbatimTextOutput(outputId = "res2")
)

server <- function(input, output, session) {
  output$res2 <- renderPrint(input$noui2)
  
  plotDF <- reactive({
    plotDF <- DF[DF$y %in% input$noui2[1]:input$noui2[2], ]
    plotDF$id <- paste0("filtered_", plotDF$id)
    plotDF
  })
  
  output$plot <- renderPlotly({
    fig <- plot_ly(
        rbind(DF, plotDF()),
        x = ~ x,
        y = ~ y,
        split = ~ id,
        type = "scatter",
        mode = "lines",
        color = I("black"),
        fillcolor = 'red',
        showlegend = FALSE
      ) |> style(fill = 'tonexty', traces = 2)
  })
}

shinyApp(ui, server)

enter image description here


Solution

  • The following approach doesn't meet the title of your question, but it shows the procedure I mentioned in your previous post.

    You will need to save a modified png file (transparent esophagus - edited with gimp's "fuzzy select tool") in your apps www folder for this to work (please find it below).

    I'm now using plotlyProxyInvoke to update the data without re-rendering the plot:

    library(shiny)
    library(plotly)
    library(shinyWidgets)
    
    slider_min <- 0
    slider_max <- 45
    lower_slider_value <- 5
    upper_slider_value <- 18
    x_position_trace_1 <- 40
    x_position_trace_2 <- 50
    
    DF <- data.frame(
      x = c(rep(x_position_trace_1, 2), rep(x_position_trace_2, 2)),
      y = rep(c(lower_slider_value, upper_slider_value), 2),
      id = c(rep("trace_1", 2), rep("trace_2", 2))
    )
    
    ui <- fluidPage(
      br(),
      column(
        2,
        noUiSliderInput(
          inputId = "noui2",
          label = "Slider vertical:",
          min = slider_min,
          max = slider_max,
          step = 1L,
          value = c(lower_slider_value, upper_slider_value),
          margin = 1,
          orientation = "vertical",
          width = "100px",
          height = "350px"
        )
      ),
      column(4, plotlyOutput("myPlot", height = "800px")),
      verbatimTextOutput(outputId = "res2")
    )
    
    server <- function(input, output, session) {
      output$res2 <- renderPrint(input$noui2)
      
      output$myPlot <- renderPlotly({
        fig <- plot_ly(
          DF,
          x = ~ x,
          y = ~ y,
          split = ~ id,
          type = "scatter",
          mode = "lines",
          color = I("white"),
          fillcolor = 'red',
          showlegend = FALSE
        ) |> layout(
          images = list(
            list(
              source =  "/esophagus.png",
              xref = "x",
              yref = "y",
              x = 0,
              y = -16,
              sizex = 93,
              sizey = 93,
              sizing = "stretch",
              opacity = 1,
              layer = "above"
            )
          ),
          plot_bgcolor  = "rgba(0, 0, 0, 0)",
          paper_bgcolor = "rgba(0, 0, 0, 0)",
          xaxis = list(
            zerolinecolor = '#ffff',  
            zerolinewidth = 2,  
            gridcolor = 'ffff',
            range = list(0, 100)),  
          yaxis = list(
            zerolinecolor = '#ffff',  
            zerolinewidth = 2,  
            gridcolor = 'ffff',
            range = list(80, -20)
            # or autorange = "reversed"
            )  
        ) |> style(fill = 'tonexty', traces = 2)
      })
      
      myPlotProxy <- plotlyProxy("myPlot", session)
      
      observe({
        plotlyProxyInvoke(myPlotProxy, "restyle", list(x = list(rep(x_position_trace_1, 2), rep(x_position_trace_2, 2)), y = list(input$noui2, input$noui2)), list(0, 1))
      })
    }
    
    shinyApp(ui, server)
    

    result


    Image for the www folder - save as "esophagus.png".

    To visualize the transparent area (grey) open the image in a new browser tab (chrome):

    transparent_esophagus


    Edit: Here is another lightweight approach without using {plotly}.

    This isn't perfectly aligned yet and it might make sense to work wit % instead of px, however it shows the principle:

    We can simply provide the esophagus image with a red background image and modify the css properties background-size and background-position-y:

    library(shiny)
    library(shinyjs)
    library(shinyWidgets)
    
    ui <- fluidPage(
      useShinyjs(),
      br(),
      column(
        2,
        noUiSliderInput(
          inputId = "noui2",
          label = "Slider vertical:",
          min = 0,
          max = 1000,
          step = 50,
          value = c(100, 400),
          margin = 100,
          orientation = "vertical",
          direction = c("rtl"),
          width = "100px",
          height = "350px"
        )
      ),
      column(
        4,
        tags$img(
          id = "esophagus",
          height = 1000,
          width = "100%",
          src = "/esophagus.png",
          style = "background-image: url(red_background.png); background-repeat: no-repeat; background-size: 100% 30%; background-position-y: 40%;"
        )
      ),
      verbatimTextOutput(outputId = "res2")
    )
    
    server <- function(input, output, session) {
      output$res2 <- renderPrint(input$noui2)
      
      observeEvent(input$noui2, {
        runjs(paste0('$("#esophagus").css("background-size", "100% ', input$noui2[2] - input$noui2[1], 'px");'))
        runjs(paste0('$("#esophagus").css("background-position-y", "', 1000 - input$noui2[2], 'px");'))
      })
    }
    
    shinyApp(ui, server)
    

    Save as "red_background.png" in your www folder:

    red_background