Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Thank you for your interest in contributing to `ClassiPyR`! This document provid

- R (>= 4.0.0)
- devtools package for development
- Python with `scipy` (required for .mat file operations)
- Python with `scipy` (required for saving .mat annotation files)

### Setting Up the Development Environment

Expand All @@ -26,7 +26,7 @@ Thank you for your interest in contributing to `ClassiPyR`! This document provid
devtools::load_all()
```

4. Set up Python environment (required for .mat file support):
4. Set up Python environment (required for saving .mat annotation files):
```r
library(iRfcb)
ifcb_py_install(envname = "./venv")
Expand Down
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ LazyData: true
Imports:
shiny,
shinyjs,
shinyFiles,
bslib,
iRfcb,
dplyr,
Expand Down
5 changes: 5 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ export(create_empty_changes_log)
export(create_new_classifications)
export(filter_to_extracted)
export(get_config_dir)
export(get_file_index_path)
export(get_sample_paths)
export(get_settings_path)
export(init_python_env)
export(is_valid_sample_name)
export(load_class_list)
export(load_file_index)
export(load_from_classifier_mat)
export(load_from_csv)
export(load_from_mat)
export(read_roi_dimensions)
export(rescan_file_index)
export(run_app)
export(sanitize_string)
export(save_file_index)
export(save_sample_annotations)
export(save_validation_statistics)
importFrom(DT,renderDT)
Expand All @@ -26,4 +30,5 @@ importFrom(iRfcb,ifcb_get_mat_variable)
importFrom(jsonlite,fromJSON)
importFrom(reticulate,py_available)
importFrom(shiny,shinyApp)
importFrom(shinyFiles,shinyDirButton)
importFrom(shinyjs,useShinyjs)
14 changes: 14 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
- ✎✓ = Has both (can switch between modes)
- * = Unannotated

### File Index Cache
- Disk-based file index cache for faster app startup on subsequent launches
- Avoids expensive recursive directory scans when folder contents haven't changed
- Sync button in sidebar to manually refresh the file index
- Cache age indicator shows when folders were last scanned
- `rescan_file_index()` function for headless use (e.g. cron jobs)
- Cache stored in platform-appropriate config directory alongside settings
- Auto-sync option (enabled by default) to control whether app scans on startup

### Image Gallery
- Paginated image display (50/100/200/500 images per page)
- Images grouped by class on consecutive pages for efficient review
Expand Down Expand Up @@ -49,12 +58,16 @@
- Save validation statistics as CSV (in `validation_statistics/` subfolder)
- Organize output PNGs by class folder (for CNN training)
- Auto-save when navigating between samples
- Support for non-standard folder structures via direct ADC path resolution
- Graceful handling of empty (0-byte) ADC files

### Settings & Persistence
- Configurable folder paths via settings modal
- Cross-platform web-based folder browser (shinyFiles)
- Settings persisted between sessions (`.classipyr_settings.json`)
- Class list file path remembered and auto-loaded on startup
- Annotator name tracking for statistics
- Cache invalidation when folder paths change in settings

### User Interface
- Clean, modern interface using bslib (Flatly theme)
Expand All @@ -66,6 +79,7 @@
- Requires Python with scipy for MAT file writing (optional - only for ifcb-analysis compatibility)
- Uses iRfcb package for IFCB data handling
- Session cache preserves work when switching samples
- File index cache reduces startup time by avoiding redundant folder scans
- Security: Input validation, XSS prevention, path traversal protection

## Development
Expand Down
11 changes: 6 additions & 5 deletions R/run_app.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
#'
#' Launches the ClassiPyR Shiny app for manual image classification and validation of IFCB data.
#' This app relies on the iRfcb package for reading IFCB data files and requires
#' Python (via reticulate) for reading and writing MATLAB .mat files.
#' Python (via reticulate) for saving MATLAB .mat files.
#'
#' @param venv_path Optional path to a Python virtual environment. If NULL (default),
#' the app will use any saved venv path from settings, or fall back to a 'venv'
#' folder in the current working directory. Set this to specify a custom location
#' for the Python virtual environment used by iRfcb.
#' @param venv_path Optional path to a Python virtual environment. When specified,
#' this path takes priority over any saved venv path in settings, both for Python
#' initialization at startup and in the Settings UI. If NULL (default), the app
#' uses any saved venv path from settings, or falls back to a 'venv' folder in
#' the current working directory.
#' @param reset_settings If TRUE, deletes saved settings before starting the app.
#' Useful for troubleshooting or starting fresh. Default is FALSE.
#' @param launch.browser If TRUE (default), opens the app in the system's default
Expand Down
29 changes: 22 additions & 7 deletions R/sample_loading.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,38 @@ NULL
#' Reads a classification CSV file and returns a data frame with classifications.
#' Class names are processed to truncate trailing numbers (matching iRfcb behavior).
#'
#' The CSV file must contain the following columns:
#' \describe{
#' \item{file_name}{Image filename including the `.png` extension
#' (e.g., `D20230101T120000_IFCB134_00001.png`).}
#' \item{class_name}{Predicted class name (e.g., `Diatom`).}
#' }
#'
#' An optional column may also be included:
#' \describe{
#' \item{score}{Classification confidence value between 0 and 1.}
#' }
#'
#' The CSV file must be named after the sample it describes
#' (e.g., `D20230101T120000_IFCB134.csv`) and placed inside the Classification
#' Folder configured in the app (subfolders are searched recursively).
#'
#' @param csv_path Path to classification CSV file
#' @return Data frame with classifications (columns depend on CSV content)
#' @return Data frame with classifications. Expected columns: `file_name`,
#' `class_name`, and optionally `score`.
#' @export
#' @examples
#' \dontrun{
#' # Load classifications from a CSV file
#' classifications <- load_from_csv("/path/to/classifications.csv")
#' classifications <- load_from_csv("/path/to/D20230101T120000_IFCB134.csv")
#' head(classifications)
#' }
load_from_csv <- function(csv_path) {
classifications <- utils::read.csv(csv_path, stringsAsFactors = FALSE)

# Truncate trailing numbers from class names
classifications$class_name <- sapply(
classifications$class_name,
iRfcb:::truncate_folder_name
)
# Strip trailing 3-digit suffix from class names (e.g., "Diatom_001" -> "Diatom")
# This matches iRfcb behavior where class folders may include numeric suffixes
classifications$class_name <- sub("_\\d{3}$", "", classifications$class_name)

classifications
}
Expand Down
17 changes: 12 additions & 5 deletions R/sample_saving.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ NULL
#' @param temp_png_folder Path to temporary folder with extracted PNG images
#' @param output_folder Output folder path for MAT files
#' @param png_output_folder PNG output folder path (organized by class)
#' @param roi_folder ROI folder path (for ADC file location)
#' @param roi_folder ROI folder path (for ADC file location, used as fallback)
#' @param class2use_path Path to class2use file
#' @param annotator Annotator name for statistics
#' @param adc_folder Direct path to the ADC folder. When provided, this is used
#' instead of constructing the path via \code{\link{get_sample_paths}}.
#' This supports non-standard folder structures.
#' @return TRUE on success, FALSE on failure
#' @export
#' @examples
Expand Down Expand Up @@ -47,7 +50,8 @@ save_sample_annotations <- function(sample_name,
png_output_folder,
roi_folder,
class2use_path,
annotator = "Unknown") {
annotator = "Unknown",
adc_folder = NULL) {

if (is.null(sample_name) || is.null(classifications) || is.null(class2use_path)) {
return(FALSE)
Expand Down Expand Up @@ -84,13 +88,16 @@ save_sample_annotations <- function(sample_name,
output_folder = png_output_folder
)

# Find ADC folder
paths <- get_sample_paths(sample_name, roi_folder)
# Find ADC folder: use provided path, or fall back to get_sample_paths()
if (is.null(adc_folder)) {
paths <- get_sample_paths(sample_name, roi_folder)
adc_folder <- paths$adc_folder
}

# Run annotation - save MAT to output folder directly
ifcb_annotate_samples(
png_folder = temp_annotate_folder,
adc_folder = paths$adc_folder,
adc_folder = adc_folder,
class2use_file = class2use_path,
output_folder = output_folder,
sample_names = sample_name,
Expand Down
Loading