inst/analysis_app/files_module.R

# Files module - UI and server logic to define file paths

# Widget to choose a single file with optional validation and error reporting.
browse_file_module_ui = function(id, header, button) {
  ns = shiny::NS(id)
  shiny::tagList(
    shiny::h4(header),
    shiny::actionButton(ns('browse'), button),
    shiny::br(), shiny::br(),
    shiny::uiOutput(ns('selected_file')),
    shiny::h4(shiny::textOutput(ns('error')), style='color: maroon')
  )
}

# Server function for file browser widget with optional validation.
# @param default_dir reactiveVal containing the starting directory.
# If provided, validator is a function that takes a path and returns a
# (possibly empty) error string.
browse_file_module = function(input, output, session, default_dir,
                              caption='Select a file',
                              validator=NULL, allow_multiple=FALSE) {
  the_path = shiny::reactiveVal()

  # Handle click on the browse button by opening a file
  shiny::observeEvent(input$browse, {
    default = default_dir()
    if (default != '') default = paste0(default, '\\*.txt')

    shiny::req(input$browse)
    path = phenoptrReports::choose_files(
      default=default,
      caption=caption,
      filters = c("Text files (*.txt)", "*.txt"),
      multi=FALSE)

    shiny::req(path)
    path = normalizePath(path, winslash='/', mustWork=FALSE)

    # Update default path
    default_dir(dirname(path))

    if (is.null(validator)) {
      error = ''
    } else {
      error = validator(path)
    }
    output$error = shiny::renderText(error)

    if (error!='') {
      if (!allow_multiple) the_path(NULL)
    } else if (allow_multiple) {
      the_path(c(the_path(), path))
    } else the_path(path)
  })

  output$selected_file = shiny::renderUI({
    req(the_path())
    if (allow_multiple) {
      shiny::HTML(paste0('Selected file(s):<br>',
                         paste(the_path(), collapse='<br>')))
    } else {
      paste('Selected file:', the_path())
    }
  })

  the_path
}

files_module_ui = function(id) {
  ns = shiny::NS(id)

  intro = shiny::tagList(
    shiny::p('Use this panel to select input files and an output directory.',
    shiny::HTML('&nbsp;&nbsp;'),
    shiny::a('Online Help',
      href="https://akoyabio.github.io/phenoptrReports/articles/analysis.html")))
  shiny::tabPanel(
    'Files',
     intro,
     shiny::div(id='well_input', shiny::wellPanel(
       browse_file_module_ui(ns('data_file'),
                             'Select a consolidated data file', 'Browse...'),
       shiny::hr(),

       shiny::h4('Select the output directory'),
       shiny::br(),

       shiny::actionButton(ns('browse_output'), 'Browse...'),
       shiny::br(), shiny::br(),
       shiny::textOutput(ns('output_dir')),
       shiny::hr(),

       browse_file_module_ui(ns('summary_file'),
         'Optional: Select a cell seg summary file to compute cell densities.',
         'Browse...'),
       shiny::hr(),

       browse_file_module_ui(ns('score_file'),
         'Optional: Select scoring file(s) to compute H-Score.',
         'Browse...')
     ))
  )
}

files_module = function(input, output, session) {
  # Data container. Will contain
  # input_path - Path to consolidated file
  # summary_path - Path to summary file, if given
  # score_path - Path to score file, if given
  # output_dir - Path to directory for results

  output_dir = shiny::reactiveVal()
  default_dir = shiny::reactiveVal('')

  input_path = shiny::callModule(browse_file_module, 'data_file',
     default_dir, caption='Select a consolidated data file',
     validator =function(path) {
       # Read and validate the file. We just need the header here
       d = purrr::possibly(readr::read_tsv, otherwise=NULL)(path, n_max=5)
       if (is.null(d)) {
         browse_error = 'Error reading file.'
       } else if ('Phenotype' %in% names(d) ||
                  (sum(stringr::str_detect(names(d), 'Phenotype '))==0)) {
         browse_error = 'Please select a consolidated data file.'
       }else if (!'Slide ID' %in% names(d) ||
                 !'Tissue Category' %in% names(d)) {
         browse_error =
           'This analysis requires a data file with "Slide ID" and "Tissue Category" columns.'
       } else {
         browse_error = ''
       }
       browse_error
     })

  summary_path = shiny::callModule(browse_file_module, 'summary_file',
    default_dir, caption='Select a summary data file',
    validator =function(path) {
      if (!endsWith(path, 'cell_seg_data_summary.txt')) {
        'Please select a cell seg summary file.'
      } else ''
    })


  score_path = shiny::callModule(browse_file_module, 'score_file',
    default_dir, caption='Select score data file(s)',
    validator = function(path) {
      if (!endsWith(path, 'score_data.txt')) {
        'Please select a score data file.'
      } else ''
    },
    allow_multiple=TRUE)

  # Handle the browse_output button by selecting a folder
  shiny::observeEvent(input$browse_output, {
    shiny::req(input$browse_output)
    output_path = phenoptrReports::choose_directory(
      default=default_dir(),
      caption='Select an output folder'
    )
    shiny::req(output_path)
    output_path = normalizePath(output_path, winslash='/', mustWork=FALSE)
    output$output_dir = shiny::renderText(output_path)
    output_dir(output_path)
    default_dir(output_path)
  })

  # Return a list of reactives
  list(
    input_path=input_path,
    summary_path=summary_path,
    score_path=score_path,
    output_dir=output_dir
  )
}

files_module_test = function() {
  ui = shiny::navbarPage('Test', files_module_ui('test'),
                  shiny::br(), shiny::br(),
                  shiny::p('Results',
                  shiny::textOutput('results'))
  )

  server = function(input, output, session) {
    the_data = shiny::callModule(files_module, 'test')

    observe({
      text = purrr::map(the_data, ~.())
      output$results = shiny::renderText(paste(text, collapse='\n'))
      print(text)})

    session$onSessionEnded(function() {
      shiny::stopApp()
    })
  }

  shiny::shinyApp(ui, server)
}

#shiny::runApp(files_module_test())
akoyabio/phenoptrReports documentation built on Jan. 17, 2022, 6:22 p.m.