From c9b8ca5813b86d19c98af5c9fa45112b1e671a65 Mon Sep 17 00:00:00 2001 From: anderstorstensson Date: Wed, 26 Aug 2026 22:10:03 +0200 Subject: [PATCH 1/2] refactor!: remove Python dependency via iRfcb 0.10.0 native MAT support iRfcb 0.10.0 reads and writes MATLAB .mat files with a native R implementation, so ClassiPyR no longer needs Python/reticulate/scipy for any of its functionality. - Require iRfcb >= 0.10.0; drop reticulate from Imports - Deprecate init_python_env() (now a no-op) and run_app(venv_path=) - Remove Python startup initialization, the python_available flag, the "Python not available" warning banner, and the Python guards that blocked SQLite -> .mat and MATLAB ZIP exports - Drop the python_venv_path setting (stale keys in saved settings are ignored by the loader) - Remove Python bootstrap from testthat setup and all scipy skip guards, so MAT read/write tests now run everywhere - Remove Python/scipy setup steps from CI workflows - Update README, CONTRIBUTING and vignettes accordingly devtools::test(): 778 passing, 0 skipped devtools::check(): 0 errors, 0 warnings, 0 notes --- .github/workflows/R-CMD-check.yaml | 18 ---- .github/workflows/test.yml | 11 --- CONTRIBUTING.md | 7 -- DESCRIPTION | 3 +- NAMESPACE | 1 - NEWS.md | 3 + R/database.R | 4 +- R/run_app.R | 19 ++-- R/sample_saving.R | 6 +- R/utils.R | 115 +++++------------------- README.md | 23 +---- inst/app/global.R | 25 ------ inst/app/modules/import_export_server.R | 15 +--- inst/app/modules/init_server.R | 8 -- inst/app/modules/settings_server.R | 3 +- inst/app/modules/ui_outputs_server.R | 16 ---- inst/app/ui.R | 2 - man/export_all_db_to_mat.Rd | 1 - man/export_db_to_mat.Rd | 1 - man/init_python_env.Rd | 38 ++------ man/run_app.Rd | 14 +-- man/save_sample_annotations.Rd | 2 +- tests/testthat/setup.R | 48 +--------- tests/testthat/test-app.R | 2 +- tests/testthat/test-database.R | 20 ----- tests/testthat/test-sample_saving.R | 4 - tests/testthat/test-utils.R | 22 +---- vignettes/class-management.Rmd | 1 - vignettes/faq.Rmd | 62 ++----------- vignettes/getting-started.Rmd | 24 +---- vignettes/user-guide.Rmd | 27 ++---- 31 files changed, 81 insertions(+), 464 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index d1b606f..766207a 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -32,24 +32,6 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 - with: - python-version: '3.x' - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install scipy numpy - - - name: Set RETICULATE_PYTHON (Unix) - if: runner.os != 'Windows' - run: echo "RETICULATE_PYTHON=$(which python)" >> $GITHUB_ENV - - - name: Set RETICULATE_PYTHON (Windows) - if: runner.os == 'Windows' - run: echo "RETICULATE_PYTHON=$((Get-Command python).Source)" >> $env:GITHUB_ENV - shell: pwsh - - uses: r-lib/actions/setup-pandoc@v2 - uses: r-lib/actions/setup-r@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a147a1..784d58a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,17 +19,6 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Setup Python - uses: actions/setup-python@v7 - with: - python-version: "3.12" - - - name: Install Python dependencies and set RETICULATE_PYTHON - run: | - python -m pip install --upgrade pip - pip install scipy numpy - echo "RETICULATE_PYTHON=$(which python)" >> $GITHUB_ENV - - uses: r-lib/actions/setup-r@v2 with: use-public-rspm: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 803e6fb..98a2b5a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,6 @@ 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 saving .mat annotation files) ### Setting Up the Development Environment @@ -26,12 +25,6 @@ Thank you for your interest in contributing to `ClassiPyR`! This document provid devtools::load_all() ``` -4. Set up Python environment (required for saving .mat annotation files): - ```r - library(iRfcb) - ifcb_py_install(envname = "./venv") - ``` - ### Running the App During Development ```r diff --git a/DESCRIPTION b/DESCRIPTION index fcadf32..fe60ed3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,11 +24,10 @@ Imports: shinyFiles, bslib, curl, - iRfcb (>= 0.8.1), + iRfcb (>= 0.10.0), dplyr, DT, jsonlite, - reticulate, DBI, RSQLite Suggests: diff --git a/NAMESPACE b/NAMESPACE index d4b51fc..be2f488 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -86,7 +86,6 @@ importFrom(iRfcb,ifcb_get_mat_variable) importFrom(iRfcb,ifcb_zip_matlab) importFrom(iRfcb,ifcb_zip_pngs) importFrom(jsonlite,fromJSON) -importFrom(reticulate,py_available) importFrom(shiny,shinyApp) importFrom(shinyFiles,shinyDirButton) importFrom(shinyjs,useShinyjs) diff --git a/NEWS.md b/NEWS.md index 280898c..77f4889 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,9 @@ # ClassiPyR (development version) - ClassiPyR now requires R >= 4.4.0 (for base R's `%||%` operator, which the app already relied on). +- **Python is no longer required.** iRfcb 0.10.0 reads and writes MATLAB `.mat` files with a native R implementation, so ClassiPyR now requires `iRfcb >= 0.10.0` and all Python-related code has been removed: `reticulate` is no longer an Import, the app no longer initializes a Python environment at startup, and the "Python not available" warnings and export blocks are gone — `.mat` saving, SQLite → `.mat` export and MATLAB ZIP export work out of the box for everyone. + - `init_python_env()` is deprecated and now a no-op; the `venv_path` argument of `run_app()` is deprecated and ignored (a warning is issued when supplied). The `python_venv_path` entry in saved settings is ignored. + - If you need a Python environment for other iRfcb features (e.g. feature extraction), use `iRfcb::ifcb_py_install()` directly. ## New features diff --git a/R/database.R b/R/database.R index 75a2f0e..f83b93a 100644 --- a/R/database.R +++ b/R/database.R @@ -2,7 +2,7 @@ # # Provides functions to store and retrieve annotations in a local SQLite # database as an alternative to .mat files. SQLite is the default storage -# backend - it works out of the box with no Python dependency. +# backend. #' @importFrom DBI dbConnect dbDisconnect dbWriteTable dbGetQuery dbExecute #' @importFrom RSQLite SQLite @@ -656,7 +656,6 @@ import_mat_to_db <- function(mat_path, db_path, sample_name, #' #' Reads annotations for a single sample from the database and writes a #' MATLAB-compatible annotation file using \code{iRfcb::ifcb_create_manual_file}. -#' Requires Python with scipy. #' #' @param db_path Path to the SQLite database file #' @param sample_name Sample name @@ -781,7 +780,6 @@ import_all_mat_to_db <- function(mat_folder, db_path, #' Bulk export all annotated samples from SQLite to .mat files #' #' Exports every sample in the database to a MATLAB-compatible annotation file. -#' Requires Python with scipy. #' #' @param db_path Path to the SQLite database file #' @param output_folder Folder where .mat files will be written diff --git a/R/run_app.R b/R/run_app.R index 7e01aa9..51b16ab 100644 --- a/R/run_app.R +++ b/R/run_app.R @@ -3,14 +3,11 @@ #' Run the ClassiPyR Shiny Application #' #' 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 saving MATLAB .mat files. +#' This app relies on the iRfcb package for reading and writing IFCB data files, +#' including MATLAB .mat files, entirely in R. #' -#' @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 venv_path Deprecated and ignored. ClassiPyR no longer requires +#' Python; .mat files are read and written natively in R (iRfcb >= 0.10.0). #' @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 @@ -24,9 +21,6 @@ #' # Run with default settings (opens in browser) #' run_app() #' -#' # Run with a specific Python virtual environment -#' run_app(venv_path = "/path/to/my/venv") -#' #' # Run on a specific port #' run_app(port = 3838) #' @@ -56,9 +50,10 @@ run_app <- function(venv_path = NULL, reset_settings = FALSE, launch.browser = T # Capture user's working directory before Shiny changes it options(ClassiPyR.startup_wd = getwd()) - # Set venv path as option for the app to use if (!is.null(venv_path)) { - options(ClassiPyR.venv_path = venv_path) + warning("The venv_path argument is deprecated and ignored: ", + "ClassiPyR no longer requires Python (iRfcb >= 0.10.0 writes ", + ".mat files natively in R).", call. = FALSE) } shiny::runApp(app_dir, launch.browser = launch.browser, ...) diff --git a/R/sample_saving.R b/R/sample_saving.R index 907c648..a56d2c4 100644 --- a/R/sample_saving.R +++ b/R/sample_saving.R @@ -8,7 +8,7 @@ NULL #' Saves the current annotations for a sample. By default annotations are #' stored in a local SQLite database (\code{annotations.sqlite} in the database #' folder). Optionally, a MATLAB-compatible \code{.mat} file can also be -#' written (requires Python + scipy). +#' written. #' #' @param sample_name Sample name (e.g., "D20230101T120000_IFCB134") #' @param classifications Current classifications data frame @@ -105,7 +105,7 @@ save_sample_annotations <- function(sample_name, output_folder = png_output_folder ) - # Save to SQLite (fast, no Python needed) + # Save to SQLite if (save_format %in% c("sqlite", "both")) { # Load class list if not provided c2u <- class2use @@ -116,7 +116,7 @@ save_sample_annotations <- function(sample_name, save_annotations_db(db_path, sample_name, classifications, c2u, annotator) } - # Save to .mat (requires Python + scipy) + # Save to .mat if (save_format %in% c("mat", "both")) { # Find ADC folder: use provided path, or fall back to get_sample_paths() if (is.null(adc_folder)) { diff --git a/R/utils.R b/R/utils.R index 6633db5..9b2af4f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -9,7 +9,6 @@ #' @importFrom bslib bs_theme #' @importFrom DT renderDT #' @importFrom jsonlite fromJSON -#' @importFrom reticulate py_available #' @importFrom dplyr filter #' @importFrom DBI dbConnect dbDisconnect dbGetQuery dbWriteTable dbExecute #' @importFrom RSQLite SQLite @@ -794,101 +793,27 @@ create_empty_changes_log <- function() { ) } -#' Initialize Python environment for iRfcb -#' -#' Checks if Python is already available via reticulate, otherwise tries to -#' use or create a virtual environment. Required for reading and writing -#' MATLAB .mat files. -#' -#' The resolution order is: -#' 1. If Python is already configured via reticulate, use it directly -#' (installs scipy if missing). -#' 2. If \code{venv_path} is provided and the virtual environment exists, -#' activate it. -#' 3. If \code{venv_path} is provided but does not exist, create it via -#' \code{\link[iRfcb]{ifcb_py_install}}. -#' 4. If \code{venv_path} is NULL, default to \code{./venv} in the current -#' working directory for steps 2--3. -#' -#' @param venv_path Optional path to virtual environment. If NULL (default), -#' uses a \code{venv} folder in the current working directory. -#' @return TRUE if Python is available, FALSE otherwise -#' @export -#' @examples -#' \dontrun{ -#' # Initialize with default venv path (./venv) -#' success <- init_python_env() +#' Initialize Python environment for iRfcb (deprecated) #' -#' # Initialize with custom venv path -#' success <- init_python_env("/path/to/my/venv") +#' Deprecated. As of iRfcb 0.10.0, MATLAB .mat files are read and written +#' with a native R implementation, so ClassiPyR no longer requires Python. +#' This function is now a no-op and will be removed in a future release. #' -#' if (success) { -#' message("Python ready for MAT file operations") -#' } -#' } +#' If you need a Python environment for other iRfcb features (e.g. feature +#' extraction), set one up with \code{\link[iRfcb]{ifcb_py_install}}. +#' +#' @param venv_path Ignored. +#' @return FALSE, invisibly +#' @export +#' @keywords internal init_python_env <- function(venv_path = NULL) { - - tryCatch({ - # Determine venv path: use provided path, or working directory default - if (is.null(venv_path) || venv_path == "") { - venv_path <- file.path(getwd(), "venv") - } - - # Set RETICULATE_PYTHON *before* initialization so that py_discover_config() - # resolves to the correct environment. iRfcb's check_python_and_module() - # uses py_discover_config() + py_list_packages() which relies on this. - # Without this, py_discover_config() may return system Python even when - # reticulate is using a virtualenv, causing scipy checks to fail. - .set_reticulate_python <- function(venv) { - if (.Platform$OS.type == "windows") { - py <- file.path(venv, "Scripts", "python.exe") - } else { - py <- file.path(venv, "bin", "python") - } - if (file.exists(py)) { - Sys.setenv(RETICULATE_PYTHON = py) - return(TRUE) - } - FALSE - } - - # Try the provided/configured venv first, then common reticulate defaults - venv_candidates <- unique(c( - venv_path, - path.expand("~/.virtualenvs/r-reticulate"), - path.expand("~/.virtualenvs/iRfcb") - )) - for (candidate in venv_candidates) { - if (reticulate::virtualenv_exists(candidate) && .set_reticulate_python(candidate)) { - break - } - } - - if (reticulate::py_available(initialize = TRUE)) { - # Check if scipy is installed (required for MAT file writing) - if (!reticulate::py_module_available("scipy")) { - message("Installing scipy...") - reticulate::py_install("scipy") - } - message("Python environment ready") - return(TRUE) - } - - # Try to use existing venv - if (reticulate::virtualenv_exists(venv_path)) { - reticulate::use_virtualenv(venv_path, required = TRUE) - message("Using Python environment: ", venv_path) - return(TRUE) - } - - # Create venv via iRfcb - message("Creating Python environment at: ", venv_path) - iRfcb::ifcb_py_install(venv_path) - reticulate::use_virtualenv(venv_path, required = TRUE) - return(TRUE) - - }, error = function(e) { - warning("Failed to initialize Python environment: ", e$message) - return(FALSE) - }) + .Deprecated( + msg = paste( + "init_python_env() is deprecated and does nothing:", + "ClassiPyR no longer requires Python (iRfcb >= 0.10.0 writes .mat", + "files natively in R). For other iRfcb features that use Python,", + "see iRfcb::ifcb_py_install()." + ) + ) + invisible(FALSE) } diff --git a/README.md b/README.md index 38d5853..6b18987 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A Shiny application for manual (human) image classification and validation of Im ## Background -`ClassiPyR` was created to provide a lightweight, standalone annotation and validation tool that is fully compatible with the [ifcb-analysis](https://github.com/hsosik/ifcb-analysis) toolbox and custom classifiers (e.g. a CNN). The primary design goals were user-friendliness and portability—enabling researchers to work with IFCB data without complex setup requirements or dependencies on specific computing environments (other than Python and R). To achieve these goals efficiently, [Claude Code](https://code.claude.com/) was used for development. +`ClassiPyR` was created to provide a lightweight, standalone annotation and validation tool that is fully compatible with the [ifcb-analysis](https://github.com/hsosik/ifcb-analysis) toolbox and custom classifiers (e.g. a CNN). The primary design goals were user-friendliness and portability—enabling researchers to work with IFCB data without complex setup requirements or dependencies on specific computing environments (other than R). To achieve these goals efficiently, [Claude Code](https://code.claude.com/) was used for development. ## Features @@ -21,7 +21,7 @@ A Shiny application for manual (human) image classification and validation of Im - **Live Prediction**: One-click CNN classification via a remote Gradio API using [iRfcb](https://github.com/EuropeanIFCBGroup/iRfcb) - **Multiple Formats**: Load from CSV, HDF5 or MATLAB classifier output - **Flexible Image Source**: Load samples from ROI files or pre-extracted PNG sample folders -- **SQLite Storage**: Annotations stored in a local SQLite database by default - no Python needed +- **SQLite Storage**: Annotations stored in a local SQLite database by default - **Efficient Workflow**: Drag-select, batch relabeling, class filtering - **MATLAB Compatible**: Optional `.mat` export for [ifcb-analysis](https://github.com/hsosik/ifcb-analysis) toolbox - **CNN Training Ready**: Organized PNG output by class @@ -50,24 +50,7 @@ run_app() See the [Getting Started](https://europeanifcbgroup.github.io/ClassiPyR/articles/getting-started.html) guide for detailed setup instructions. -### Python Setup (optional) - -Python is **not required** for the default workflow. Annotations are stored in a local SQLite database that works out of the box. - -Python is only needed if you want to export annotations as MATLAB `.mat` files for use with [ifcb-analysis](https://github.com/hsosik/ifcb-analysis). To set up using `iRfcb`: - -```r -library(iRfcb) - -# Define a path were the venv will be installed -venv_path <- "/path/to/your/venv" - -# Install the venv -ifcb_py_install(venv_path) - -# Launch with Python support -run_app(venv_path = venv_path) -``` +No Python setup is needed: all file formats, including MATLAB `.mat` files for [ifcb-analysis](https://github.com/hsosik/ifcb-analysis), are read and written natively in R (via `iRfcb` >= 0.10.0). ## Documentation diff --git a/inst/app/global.R b/inst/app/global.R index 22e4a26..fb3c4c6 100644 --- a/inst/app/global.R +++ b/inst/app/global.R @@ -14,7 +14,6 @@ suppressPackageStartupMessages({ library(dplyr) library(DT) library(jsonlite) - library(reticulate) library(DBI) library(RSQLite) }) @@ -27,30 +26,6 @@ app_version <- as.character(utils::packageVersion("ClassiPyR")) # 20 samples ≈ 30 MB memory usage MAX_CACHED_SAMPLES <- 20 -# Get Python venv path from: 1) run_app() argument, 2) saved settings, 3) NULL (use default) -.get_venv_path <- function() { - # First check if run_app() was called with venv_path argument - option_path <- getOption("ClassiPyR.venv_path", default = NULL) - if (!is.null(option_path) && nzchar(option_path)) { - return(option_path) - } - - # Otherwise check saved settings - settings_file <- get_settings_path() - if (file.exists(settings_file)) { - tryCatch({ - saved <- jsonlite::fromJSON(settings_file) - if (!is.null(saved$python_venv_path) && nzchar(saved$python_venv_path)) { - return(saved$python_venv_path) - } - }, error = function(e) NULL) - } - NULL -} - -# Initialize Python on app startup with configured venv path -python_available <- init_python_env(venv_path = .get_venv_path()) - # S3 method for dynamic_roots: allows shinyFiles to subscript a function-based # roots object. shinyFiles 0.9.3 internally does roots[selectedRoot] without # checking if roots is a function, so this class bridges the gap. diff --git a/inst/app/modules/import_export_server.R b/inst/app/modules/import_export_server.R index 58aa84d..7a2ccac 100644 --- a/inst/app/modules/import_export_server.R +++ b/inst/app/modules/import_export_server.R @@ -41,12 +41,6 @@ setup_import_export_server <- function(input, output, session, rv, config, type = "error") return() } - if (!python_available) { - showNotification("Python is not available. Export to .mat requires Python with scipy.", - type = "error") - return() - } - showModal(modalDialog( title = "Confirm .mat export", p("This will export all annotated samples from the SQLite database as", @@ -193,7 +187,7 @@ setup_import_export_server <- function(input, output, session, rv, config, pixels_per_micron = config$pixels_per_micron, auto_sync = config$auto_sync, save_format = config$save_format, export_statistics = config$export_statistics, skip_class_png = config$skip_class_png, class2use_path = rv$class2use_path, - python_venv_path = config$python_venv_path, data_source = config$data_source, + data_source = config$data_source, dashboard_url = config$dashboard_url, dashboard_autoclass = config$dashboard_autoclass, dashboard_parallel_downloads = config$dashboard_parallel_downloads, dashboard_sleep_time = config$dashboard_sleep_time, @@ -512,11 +506,6 @@ setup_import_export_server <- function(input, output, session, rv, config, if (config$save_format %in% c("mat", "both")) { manual_folder <- config$output_folder } else { - if (!python_available) { - showNotification("Python with scipy is required to convert SQLite annotations to .mat files.", - type = "error") - return() - } temp_mat_dir <- tempfile("matlab_zip_manual_") dir.create(temp_mat_dir, recursive = TRUE, showWarnings = FALSE) use_temp_mat <- TRUE @@ -598,7 +587,7 @@ setup_import_export_server <- function(input, output, session, rv, config, pixels_per_micron = config$pixels_per_micron, auto_sync = config$auto_sync, save_format = config$save_format, export_statistics = config$export_statistics, skip_class_png = config$skip_class_png, class2use_path = rv$class2use_path, - python_venv_path = config$python_venv_path, data_source = config$data_source, + data_source = config$data_source, dashboard_url = config$dashboard_url, dashboard_autoclass = config$dashboard_autoclass, dashboard_parallel_downloads = config$dashboard_parallel_downloads, dashboard_sleep_time = config$dashboard_sleep_time, diff --git a/inst/app/modules/init_server.R b/inst/app/modules/init_server.R index c26ed5d..7e8cb7b 100644 --- a/inst/app/modules/init_server.R +++ b/inst/app/modules/init_server.R @@ -109,7 +109,6 @@ setup_init_server <- function(input, output, session) { pixels_per_micron = 3.4, auto_sync = TRUE, class2use_path = NULL, - python_venv_path = NULL, save_format = "sqlite", export_statistics = TRUE, skip_class_png = "", @@ -168,12 +167,6 @@ setup_init_server <- function(input, output, session) { # Initialize config from saved settings saved_settings <- load_settings() - # run_app(venv_path=) takes precedence over saved settings - run_app_venv <- getOption("ClassiPyR.venv_path", default = NULL) - if (!is.null(run_app_venv) && nzchar(run_app_venv)) { - saved_settings$python_venv_path <- run_app_venv - } - config <- reactiveValues( csv_folder = saved_settings$csv_folder, roi_folder = saved_settings$roi_folder, @@ -183,7 +176,6 @@ setup_init_server <- function(input, output, session) { use_threshold = saved_settings$use_threshold, pixels_per_micron = saved_settings$pixels_per_micron, auto_sync = saved_settings$auto_sync, - python_venv_path = saved_settings$python_venv_path, save_format = saved_settings$save_format, export_statistics = saved_settings$export_statistics, skip_class_png = saved_settings$skip_class_png, diff --git a/inst/app/modules/settings_server.R b/inst/app/modules/settings_server.R index a2a5b97..7e1bb69 100644 --- a/inst/app/modules/settings_server.R +++ b/inst/app/modules/settings_server.R @@ -136,7 +136,7 @@ setup_settings_server <- function(input, output, session, rv, config, ), selected = config$save_format), tags$small(class = "text-muted", style = "display: block; margin-top: -5px; margin-bottom: 20px;", - "SQLite works out of the box. MAT files require Python and are only needed for ifcb-analysis compatibility."), + "SQLite works out of the box. MAT files are only needed for ifcb-analysis compatibility."), # Output folder — only relevant for MAT/statistics div( @@ -438,7 +438,6 @@ setup_settings_server <- function(input, output, session, rv, config, export_statistics = input$cfg_export_statistics, skip_class_png = input$cfg_skip_class_png, class2use_path = rv$class2use_path, - python_venv_path = config$python_venv_path, data_source = input$cfg_data_source, dashboard_url = input$cfg_dashboard_url, dashboard_autoclass = input$cfg_dashboard_autoclass, diff --git a/inst/app/modules/ui_outputs_server.R b/inst/app/modules/ui_outputs_server.R index ce50822..a43789f 100644 --- a/inst/app/modules/ui_outputs_server.R +++ b/inst/app/modules/ui_outputs_server.R @@ -28,22 +28,6 @@ setup_ui_outputs_server <- function(input, output, session, rv, config, } }) - output$python_warning <- renderUI({ - needs_python <- config$save_format %in% c("mat", "both") - if (!python_available && needs_python) { - div( - class = "alert alert-warning", - style = "margin-top: 10px; padding: 8px; font-size: 12px;", - "Python not available. Saving .mat files will not work. ", - "Switch to SQLite storage format in Settings, or install Python: ", - "run ifcb_py_install() in R console. ", - "MAT files are only needed for ", - tags$a(href = "https://github.com/hsosik/ifcb-analysis", target = "_blank", "ifcb-analysis"), - " compatibility." - ) - } - }) - observe({ session$sendCustomMessage("updatePixelsPerMicron", config$pixels_per_micron) }) diff --git a/inst/app/ui.R b/inst/app/ui.R index 8eb9533..1b17d75 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -568,8 +568,6 @@ ui <- page_sidebar( uiOutput("clear_btn_ui")) ), - uiOutput("python_warning"), - # Help link at bottom of sidebar div( style = "margin-top: 20px; text-align: center;", diff --git a/man/export_all_db_to_mat.Rd b/man/export_all_db_to_mat.Rd index 3f8a366..2c45000 100644 --- a/man/export_all_db_to_mat.Rd +++ b/man/export_all_db_to_mat.Rd @@ -20,7 +20,6 @@ Named list with counts: \code{success}, \code{failed} } \description{ Exports every sample in the database to a MATLAB-compatible annotation file. -Requires Python with scipy. } \examples{ \dontrun{ diff --git a/man/export_db_to_mat.Rd b/man/export_db_to_mat.Rd index 64f7f04..c8facac 100644 --- a/man/export_db_to_mat.Rd +++ b/man/export_db_to_mat.Rd @@ -19,7 +19,6 @@ TRUE on success, FALSE on failure \description{ Reads annotations for a single sample from the database and writes a MATLAB-compatible annotation file using \code{iRfcb::ifcb_create_manual_file}. -Requires Python with scipy. } \examples{ \dontrun{ diff --git a/man/init_python_env.Rd b/man/init_python_env.Rd index 25033b1..3c06460 100644 --- a/man/init_python_env.Rd +++ b/man/init_python_env.Rd @@ -2,43 +2,23 @@ % Please edit documentation in R/utils.R \name{init_python_env} \alias{init_python_env} -\title{Initialize Python environment for iRfcb} +\title{Initialize Python environment for iRfcb (deprecated)} \usage{ init_python_env(venv_path = NULL) } \arguments{ -\item{venv_path}{Optional path to virtual environment. If NULL (default), -uses a \code{venv} folder in the current working directory.} +\item{venv_path}{Ignored.} } \value{ -TRUE if Python is available, FALSE otherwise +FALSE, invisibly } \description{ -Checks if Python is already available via reticulate, otherwise tries to -use or create a virtual environment. Required for reading and writing -MATLAB .mat files. +Deprecated. As of iRfcb 0.10.0, MATLAB .mat files are read and written +with a native R implementation, so ClassiPyR no longer requires Python. +This function is now a no-op and will be removed in a future release. } \details{ -The resolution order is: -1. If Python is already configured via reticulate, use it directly - (installs scipy if missing). -2. If \code{venv_path} is provided and the virtual environment exists, - activate it. -3. If \code{venv_path} is provided but does not exist, create it via - \code{\link[iRfcb]{ifcb_py_install}}. -4. If \code{venv_path} is NULL, default to \code{./venv} in the current - working directory for steps 2--3. -} -\examples{ -\dontrun{ -# Initialize with default venv path (./venv) -success <- init_python_env() - -# Initialize with custom venv path -success <- init_python_env("/path/to/my/venv") - -if (success) { - message("Python ready for MAT file operations") -} -} +If you need a Python environment for other iRfcb features (e.g. feature +extraction), set one up with \code{\link[iRfcb]{ifcb_py_install}}. } +\keyword{internal} diff --git a/man/run_app.Rd b/man/run_app.Rd index 50c9f2e..8801afa 100644 --- a/man/run_app.Rd +++ b/man/run_app.Rd @@ -7,11 +7,8 @@ run_app(venv_path = NULL, reset_settings = FALSE, launch.browser = TRUE, ...) } \arguments{ -\item{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.} +\item{venv_path}{Deprecated and ignored. ClassiPyR no longer requires +Python; .mat files are read and written natively in R (iRfcb >= 0.10.0).} \item{reset_settings}{If TRUE, deletes saved settings before starting the app. Useful for troubleshooting or starting fresh. Default is FALSE.} @@ -27,17 +24,14 @@ This function does not return; it runs the Shiny app } \description{ 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 saving MATLAB .mat files. +This app relies on the iRfcb package for reading and writing IFCB data files, +including MATLAB .mat files, entirely in R. } \examples{ \dontrun{ # Run with default settings (opens in browser) run_app() -# Run with a specific Python virtual environment -run_app(venv_path = "/path/to/my/venv") - # Run on a specific port run_app(port = 3838) diff --git a/man/save_sample_annotations.Rd b/man/save_sample_annotations.Rd index df4a3e3..e942e90 100644 --- a/man/save_sample_annotations.Rd +++ b/man/save_sample_annotations.Rd @@ -72,7 +72,7 @@ TRUE on success, FALSE when there is nothing to save (empty changes Saves the current annotations for a sample. By default annotations are stored in a local SQLite database (\code{annotations.sqlite} in the database folder). Optionally, a MATLAB-compatible \code{.mat} file can also be -written (requires Python + scipy). +written. } \examples{ \dontrun{ diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index 2a133fa..3241f46 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -1,46 +1,4 @@ # Setup for tests - -library(reticulate) - -# Check if we are on CRAN (skip Python setup on CRAN) -on_cran <- !identical(Sys.getenv("NOT_CRAN"), "true") && - nzchar(Sys.getenv("_R_CHECK_PACKAGE_NAME_", "")) - -if (!on_cran) { - # Try to initialize Python for tests - if (!reticulate::py_available(initialize = FALSE)) { - # Prefer the r-reticulate virtualenv (where py_install puts packages) - venv_path <- "~/.virtualenvs/r-reticulate" - if (dir.exists(path.expand(venv_path))) { - tryCatch( - reticulate::use_virtualenv(venv_path, required = FALSE), - error = function(e) message("Could not use virtualenv: ", e$message) - ) - } else { - # Fall back to system Python discovery - python_config <- tryCatch( - reticulate::py_discover_config(), - error = function(e) NULL - ) - if (!is.null(python_config) && !is.null(python_config$python)) { - tryCatch( - reticulate::use_python(python_config$python, required = FALSE), - error = function(e) message("Could not configure Python: ", e$message) - ) - } - } - } - - # Initialize Python if available - if (reticulate::py_available(initialize = TRUE)) { - # Check for scipy (required for MAT file operations) - if (!reticulate::py_module_available("scipy")) { - message("Installing scipy for tests...") - tryCatch({ - reticulate::py_install("scipy") - }, error = function(e) { - message("Could not install scipy: ", e$message) - }) - } - } -} +# +# No Python setup is needed: as of iRfcb 0.10.0, MAT files are read and +# written with a native R implementation. diff --git a/tests/testthat/test-app.R b/tests/testthat/test-app.R index 84e2810..371744a 100644 --- a/tests/testthat/test-app.R +++ b/tests/testthat/test-app.R @@ -43,7 +43,7 @@ test_that("required packages are listed in DESCRIPTION", { expect_true(grepl("dplyr", imports)) expect_true(grepl("DT", imports)) expect_true(grepl("jsonlite", imports)) - expect_true(grepl("reticulate", imports)) + expect_false(grepl("reticulate", imports)) }) test_that("app UI can be created without errors", { diff --git a/tests/testthat/test-database.R b/tests/testthat/test-database.R index 1a4e420..477c514 100644 --- a/tests/testthat/test-database.R +++ b/tests/testthat/test-database.R @@ -459,8 +459,6 @@ test_that("update_annotator validates inputs", { test_that("import_mat_to_db migrates data correctly", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") sample_name <- "D20230101T120000_IFCB134" class2use <- c("unclassified", "Diatom", "Ciliate", "Dinoflagellate") @@ -513,8 +511,6 @@ test_that("import_mat_to_db returns FALSE for missing file", { test_that("export_db_to_mat creates valid .mat file", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") db_dir <- tempfile("db_") dir.create(db_dir) @@ -680,8 +676,6 @@ test_that("export_db_to_png returns FALSE for missing database", { test_that("import_all_mat_to_db imports multiple files and returns correct counts", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") mat_dir <- tempfile("mat_") dir.create(mat_dir) @@ -730,8 +724,6 @@ test_that("import_all_mat_to_db imports multiple files and returns correct count test_that("export_all_db_to_mat exports multiple samples", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") db_dir <- tempfile("db_") dir.create(db_dir) @@ -764,8 +756,6 @@ test_that("export_all_db_to_mat exports multiple samples", { test_that("round-trip: DB -> .mat -> DB produces matching data", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") db_dir <- tempfile("db_") dir.create(db_dir) @@ -1230,8 +1220,6 @@ test_that("schema migration adds is_manual to existing DB", { test_that("import_mat_to_db reads class2use_manual from .mat", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") # Create a .mat file with a known class list mat_dir <- tempfile("mat_") @@ -1269,8 +1257,6 @@ test_that("import_mat_to_db reads class2use_manual from .mat", { test_that("import_mat_to_db preserves NaN as is_manual=0", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") mat_dir <- tempfile("mat_") dir.create(mat_dir) @@ -1304,8 +1290,6 @@ test_that("import_mat_to_db preserves NaN as is_manual=0", { test_that("export_db_to_mat restores NaN for is_manual=0 rows", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") db_dir <- tempfile("db_") dir.create(db_dir) @@ -1342,8 +1326,6 @@ test_that("export_db_to_mat restores NaN for is_manual=0 rows", { test_that("full roundtrip: .mat -> SQLite -> .mat preserves NaN and class list", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") # Create original .mat with NaN entries mat_dir <- tempfile("mat_orig_") @@ -1964,8 +1946,6 @@ test_that("init_db_schema creates global_class_list table", { test_that("export_all_db_to_mat filters by samples parameter", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") db_dir <- tempfile("db_") dir.create(db_dir) diff --git a/tests/testthat/test-sample_saving.R b/tests/testthat/test-sample_saving.R index d856cbb..c584b28 100644 --- a/tests/testthat/test-sample_saving.R +++ b/tests/testthat/test-sample_saving.R @@ -259,8 +259,6 @@ test_that("save_sample_annotations accepts adc_folder parameter", { test_that("save_sample_annotations creates MAT file with real data", { skip_if_not_installed("iRfcb") skip_if_not_installed("dplyr") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") sample_name <- "D20220522T000439_IFCB134" @@ -419,8 +417,6 @@ test_that("save_sample_annotations with save_format='sqlite' creates database", test_that("save_sample_annotations with save_format='both' creates both outputs", { skip_if_not_installed("iRfcb") - skip_if_not(reticulate::py_available(), "Python not available") - skip_if_not(reticulate::py_module_available("scipy"), "scipy not available") sample_name <- "D20220522T000439_IFCB134" diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 3e6eb17..64e8cd8 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -105,28 +105,14 @@ test_that("update_settings_file recovers from a corrupt settings file", { }) # ============================================================================= -# Python environment +# Python environment (deprecated) # ============================================================================= -test_that("init_python_env returns boolean", { - # This test just checks the return type, not actual Python availability - result <- init_python_env() - - expect_type(result, "logical") - expect_length(result, 1) -}) - -test_that("init_python_env accepts venv_path parameter", { - # Test that the function accepts the parameter without error - temp_venv <- file.path(tempdir(), "test-venv-param") - - # Should not error even if venv doesn't exist (will try to create or return FALSE) - result <- tryCatch( - init_python_env(venv_path = temp_venv), - error = function(e) FALSE - ) +test_that("init_python_env is deprecated and returns FALSE invisibly", { + expect_warning(result <- init_python_env(), "deprecated") expect_type(result, "logical") + expect_false(result) }) # ============================================================================= diff --git a/vignettes/class-management.Rmd b/vignettes/class-management.Rmd index 36d6323..58da61e 100644 --- a/vignettes/class-management.Rmd +++ b/vignettes/class-management.Rmd @@ -197,7 +197,6 @@ Creates MATLAB-compatible `class2use.mat` for use with [ifcb-analysis](https://g 1. Click **Save as .mat** 2. Choose location in browser download -> **Note**: Saving .mat files requires Python with scipy. ### Save as TXT diff --git a/vignettes/faq.Rmd b/vignettes/faq.Rmd index 4962043..98aaded 100644 --- a/vignettes/faq.Rmd +++ b/vignettes/faq.Rmd @@ -56,46 +56,9 @@ A: Yes. Configure a Gradio API URL and model in Settings > Live Prediction, then ## Installation Issues -**Q: I see "Python not available" warning** - -A: This warning only appears when your storage format includes `.mat` files. Python is **not needed** for the default SQLite storage. - -If you see this warning and don't need `.mat` files, switch to SQLite in Settings > Annotation Storage. Otherwise, to enable `.mat` support: - -```{r, eval = FALSE} -library(iRfcb) -ifcb_py_install() # Creates venv in current working directory -``` - -Then restart the app. - **Q: Do I need Python to use ClassiPyR?** -A: No. The default storage format is SQLite, which works out of the box with no Python dependency. Python is only needed if you want to export `.mat` files for [ifcb-analysis](https://github.com/hsosik/ifcb-analysis) compatibility. - -**Q: Where is the Python virtual environment created?** - -A: By default, `ifcb_py_install()` creates a virtual environment at `~/.virtualenvs/iRfcb`. You can specify a different location: - -```{r, eval = FALSE} -ifcb_py_install("/path/to/your/venv") -``` - -You can also specify the venv path when launching the app: - -```{r, eval = FALSE} -run_app(venv_path = "/path/to/your/venv") -``` - -**Q: How is the Python virtual environment path resolved?** - -A: The app uses the following priority order: - -1. **`venv_path` argument** passed to `run_app()` (highest priority) -2. **Saved settings** from a previous session (stored in `settings.json`) -3. **Default** `./venv` in the working directory - -When you specify `run_app(venv_path = "/path/to/venv")`, that path is used for Python initialization and pre-filled in the Settings dialog, overriding any previously saved path. +A: No. All storage formats work out of the box: SQLite is the default, and `.mat` files for [ifcb-analysis](https://github.com/hsosik/ifcb-analysis) compatibility are read and written natively in R (via `iRfcb` >= 0.10.0). **Q: Package installation fails** @@ -221,8 +184,7 @@ A: The ROI might be empty (no actual image data). These are filtered out automat A: Check that: 1. Output folder is writable -2. If using MAT format: Python is available (not needed for default SQLite storage) -3. Click "Save Annotations" before closing +2. Click "Save Annotations" before closing --- @@ -339,7 +301,7 @@ A: No. SQLite databases are [not safe on network filesystems](https://www.sqlite A: Since the SQLite database is stored locally, you cannot simply share it over a network drive. Instead, use `.mat` files as the interchange format: -1. **Export** from the source computer (requires Python with scipy): +1. **Export** from the source computer: ```{r, eval = FALSE} library(ClassiPyR) @@ -374,8 +336,6 @@ load('sample_name.mat'); % classlist contains [roi_number, class_index] ``` -Note: Python with `scipy` must be installed to save .mat files. Change the storage format in Settings > Annotation Storage. - **Q: Can I migrate existing .mat annotations to the SQLite database?** A: Yes. The easiest way is the **Import .mat → SQLite** button in Settings > Annotation Storage, which bulk-imports all `.mat` files in your output folder. @@ -400,7 +360,7 @@ cat(result$success, "imported,", result$failed, "failed,", result$skipped, "skip **Q: Can I export SQLite annotations back to .mat files?** -A: Yes. Use the **Export SQLite → .mat** button in Settings > Annotation Storage to export all annotated samples at once. This requires Python with scipy. +A: Yes. Use the **Export SQLite → .mat** button in Settings > Annotation Storage to export all annotated samples at once. You can also export programmatically: @@ -417,7 +377,7 @@ cat(result$success, "exported,", result$failed, "failed\n") A: Yes. Use the **Export SQLite → MATLAB ZIP** button in Settings > Export from SQLite. This bundles `.mat` annotation files, feature CSVs, a `class2use.mat` config file, optional raw data, and README files into a distributable ZIP archive via `iRfcb::ifcb_zip_matlab()`. You need to provide a features folder; a data folder with raw IFCB files is optional. -If your storage format is SQLite-only, annotations are automatically converted to temporary `.mat` files (requires Python with scipy). See the [iRfcb image export tutorial](https://europeanifcbgroup.github.io/iRfcb/articles/image-export-tutorial.html) for more details on the archive format. +If your storage format is SQLite-only, annotations are automatically converted to temporary `.mat` files. See the [iRfcb image export tutorial](https://europeanifcbgroup.github.io/iRfcb/articles/image-export-tutorial.html) for more details on the archive format. **Q: Can I import images classified in another tool?** @@ -492,7 +452,7 @@ A: Yes! Settings are stored in a configuration file: - **macOS**: `~/Library/Preferences/org.R-project.R/R/ClassiPyR/settings.json` - **Windows**: `%APPDATA%/R/config/R/ClassiPyR/settings.json` -Folder paths, class list location, and Python venv path are automatically restored when you restart the app. +Folder paths and class list location are automatically restored when you restart the app. **Q: How do I reset all settings to defaults?** @@ -502,19 +462,12 @@ A: Use the `reset_settings` argument when launching the app: run_app(reset_settings = TRUE) ``` -This deletes the saved `settings.json` file and starts the app with default values. All folder paths, the class list reference, and the Python venv path are cleared, so you will need to reconfigure them. The class list file itself (`class2use_saved.*`) is not deleted from the config directory but will not be loaded until you re-upload it. This is useful if: +This deletes the saved `settings.json` file and starts the app with default values. All folder paths and the class list reference are cleared, so you will need to reconfigure them. The class list file itself (`class2use_saved.*`) is not deleted from the config directory but will not be loaded until you re-upload it. This is useful if: - The app fails to start due to invalid saved paths - Folder paths point to locations that no longer exist - You want a clean slate after changing your data layout -You can also combine it with other arguments: - -```{r, eval = FALSE} -# Reset settings and specify a new Python environment -run_app(reset_settings = TRUE, venv_path = "/path/to/your/venv") -``` - **Q: What's the yellow warning on some classes?** A: Classes marked with a warning are in your classification data but not in your class list. This can happen when: @@ -582,7 +535,6 @@ A: In the same config directory as your settings: |-------|----------| | "ROI file not found" | Check ROI/PNG Data Folder path. If no ROI exists, provide extracted PNG sample folders named by sample and click Sync | | "ADC file not found" | ADC file must be alongside ROI file | -| "Python not available" | Only affects `.mat` export. Switch to SQLite in Settings, or run `iRfcb::ifcb_py_install()` | | "Error loading class list" | Check file format (.mat or .txt) | | "No samples found" | Check ROI/PNG Data Folder configuration and naming | | App fails to start | Try `run_app(reset_settings = TRUE)` to clear saved settings | diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd index 65d2774..051415f 100644 --- a/vignettes/getting-started.Rmd +++ b/vignettes/getting-started.Rmd @@ -26,11 +26,7 @@ Make sure you have: 4. Optionally: existing classifications (CSV, H5, or classifier MAT files, see below) to start from classified results — or use [Live Prediction](user-guide.html#live-prediction) to classify on the fly 5. Optionally: existing annotations can be imported into SQLite via Settings > Import .mat → SQLite or Import PNG → SQLite -### Python Requirements - -Python is **not required** for the default workflow. ClassiPyR stores annotations in a local SQLite database that works out of the box with no external dependencies. - -Python is only needed if you want to export annotations as MATLAB `.mat` files for use with [ifcb-analysis](https://github.com/hsosik/ifcb-analysis). Reading existing `.mat` files (annotations, classifier output, class lists) also does not require Python. +ClassiPyR works out of the box with no external dependencies: annotations are stored in a local SQLite database by default, and MATLAB `.mat` files for [ifcb-analysis](https://github.com/hsosik/ifcb-analysis) are read and written natively in R. Python is not required. ### CSV Classification Format @@ -44,15 +40,6 @@ D20230101T120000_IFCB134_00002.png,Ciliate An optional `score` column (confidence values between 0 and 1) can also be included. HDF5 (`.h5`) and MATLAB (`.mat`) classifier output files from [iRfcb](https://github.com/EuropeanIFCBGroup/iRfcb) are also supported. See the [User Guide](user-guide.html) for more details. -### Python Setup (optional) - -Only needed if you plan to export `.mat` files. Skip this step if using the default SQLite storage. - -```{r, eval = FALSE} -library(iRfcb) -ifcb_py_install() # Creates venv at ~/.virtualenvs/iRfcb by default -``` - --- ## Step 1: Configure Settings @@ -62,9 +49,6 @@ Launch the app: ```{r, eval = FALSE} library(ClassiPyR) run_app() - -# Or specify a custom Python virtual environment path (takes priority over saved settings) -run_app(venv_path = "./venv") ``` Click the **gear icon** next to your username in the sidebar. @@ -88,8 +72,6 @@ Configure your folders using the built-in folder browser: Click **Save Settings**. The app will scan your folders and build a file index cache for fast loading. -> **Note**: The Python virtual environment path is configured via `run_app(venv_path = ...)` and remembered for future sessions. See the [FAQ](faq.html) for details on how the path is resolved. - --- ## Step 2: Set Up Your Class List @@ -211,14 +193,14 @@ The images will move to their new class group. Click **Save Annotations** to save: -- **SQLite database** (default) - annotations are written to `annotations.sqlite` in your Database Folder. This single file stores annotations for all samples. No Python needed. +- **SQLite database** (default) - annotations are written to `annotations.sqlite` in your Database Folder. This single file stores annotations for all samples. - Statistics CSV with accuracy metrics - PNGs organized by class You can change the storage format in **Settings > Annotation Storage**: - **SQLite** (recommended) - works out of the box -- **MAT file** - for [ifcb-analysis](https://github.com/hsosik/ifcb-analysis) compatibility (requires Python) +- **MAT file** - for [ifcb-analysis](https://github.com/hsosik/ifcb-analysis) compatibility - **Both** - writes to both SQLite and `.mat` ### Auto-save diff --git a/vignettes/user-guide.Rmd b/vignettes/user-guide.Rmd index e3de554..455b8b8 100644 --- a/vignettes/user-guide.Rmd +++ b/vignettes/user-guide.Rmd @@ -346,7 +346,6 @@ When you save, the app creates files based on your chosen storage format (config A single SQLite database file containing annotations for all samples. This is the default storage backend: -- No Python dependency required - Fast read/write performance - Single file for all samples — easy to back up and manage - Contains `annotations` table (one row per ROI) and `class_lists` table (preserves class indices for `.mat` export) @@ -364,7 +363,7 @@ MATLAB-compatible format with: - `classlist`: ROI numbers and class indices - Compatible with [ifcb-analysis](https://github.com/hsosik/ifcb-analysis) toolbox -> **Note**: Saving MAT files requires Python with scipy. Enable in Settings > Annotation Storage by selecting "MAT file" or "Both". +> **Note**: Enable in Settings > Annotation Storage by selecting "MAT file" or "Both". ### Statistics Files @@ -446,18 +445,18 @@ Folder paths are configured using a web-based folder browser that works on all p | Format | Description | |--------|-------------| -| SQLite (recommended) | Default. Stores annotations in `annotations.sqlite` in the Database Folder. No Python needed. | -| MAT file | MATLAB-compatible `.mat` files for [ifcb-analysis](https://github.com/hsosik/ifcb-analysis). Requires Python with scipy. | +| SQLite (recommended) | Default. Stores annotations in `annotations.sqlite` in the Database Folder. | +| MAT file | MATLAB-compatible `.mat` files for [ifcb-analysis](https://github.com/hsosik/ifcb-analysis). | | Both | Writes to both SQLite and `.mat` for maximum compatibility. | Below the format selector, two buttons allow bulk conversion between formats: - **Import .mat → SQLite**: Imports all `.mat` annotation files from the output folder into the SQLite database. Already-imported samples are skipped. - **Import PNG → SQLite**: Imports annotations from a folder of PNG images organized in class-name subfolders. Class names are extracted from folder names (trailing `_NNN` suffixes are stripped). Useful for re-importing corrected exports or importing external classification datasets. -- **Export SQLite → .mat**: Exports all annotated samples from the database to `.mat` files in the specified Output Folder. Requires Python with scipy. +- **Export SQLite → .mat**: Exports all annotated samples from the database to `.mat` files in the specified Output Folder. - **Export SQLite → PNG**: Extracts annotated images from ROI files into class-name subfolders in the PNG Output Folder. Useful for building training datasets for CNN classifiers. - **Export SQLite → ZIP**: Builds an EcoTaxa-ready ZIP archive from SQLite annotations that is also suitable for sharing datasets in general repositories (for example Zenodo or Figshare). The export writes class-organized PNGs, per-class inventories (`ecotaxa_.tsv`), and a README file. -- **Export SQLite → MATLAB ZIP**: Builds a MATLAB-format ZIP archive via `iRfcb::ifcb_zip_matlab()`, bundling `.mat` annotation files, feature CSVs, a `class2use.mat` config file, optional raw data (.roi, .adc, .hdr), and README files. When using SQLite-only storage the annotations are automatically converted to temporary `.mat` files (requires Python with scipy). See the [iRfcb image export tutorial](https://europeanifcbgroup.github.io/iRfcb/articles/image-export-tutorial.html) for more details on the MATLAB ZIP format. +- **Export SQLite → MATLAB ZIP**: Builds a MATLAB-format ZIP archive via `iRfcb::ifcb_zip_matlab()`, bundling `.mat` annotation files, feature CSVs, a `class2use.mat` config file, optional raw data (.roi, .adc, .hdr), and README files. When using SQLite-only storage the annotations are automatically converted to temporary `.mat` files. See the [iRfcb image export tutorial](https://europeanifcbgroup.github.io/iRfcb/articles/image-export-tutorial.html) for more details on the MATLAB ZIP format. The **Export SQLite → ZIP** and **Export SQLite → MATLAB ZIP** dialogs include optional README metadata fields: @@ -504,16 +503,6 @@ When applied, AphiaID values are saved in SQLite (`class_taxonomy` table) and sh |---------|-------------| | Auto-sync folders on startup | When enabled (default), the app checks and refreshes the file index on launch. Disable for instant startup using the existing cache. | -### Python Configuration - -The Python virtual environment path is configured when launching the app: - -```{r, eval = FALSE} -run_app(venv_path = "/path/to/your/venv") -``` - -The path is remembered for future sessions. **Priority order**: `run_app(venv_path=)` argument > saved settings > default (`./venv`). - ### Live Prediction | Setting | Description | @@ -542,7 +531,7 @@ When both fields are configured, a **Predict** button appears in the sidebar for ### MATLAB ZIP Export Notes - MATLAB ZIP export bundles `.mat` annotations, feature CSVs, `class2use.mat`, and optionally raw data into a single archive suitable for sharing (e.g. for the [SMHI IFCB Plankton Image Reference Library](https://doi.org/10.17044/scilifelab.25883455)). -- If your storage format is SQLite-only, annotations are converted to `.mat` files on the fly (requires Python with scipy). When using MAT or Both storage, existing `.mat` files from the Output Folder are used directly. +- If your storage format is SQLite-only, annotations are converted to `.mat` files on the fly. When using MAT or Both storage, existing `.mat` files from the Output Folder are used directly. - The `class2use.mat` config file is generated automatically from the current class list. - For details on the archive structure and the underlying `iRfcb::ifcb_zip_matlab()` function, see the [iRfcb image export tutorial](https://europeanifcbgroup.github.io/iRfcb/articles/image-export-tutorial.html). @@ -602,7 +591,7 @@ The app maintains two types of caches: - **macOS**: `~/Library/Preferences/org.R-project.R/R/ClassiPyR/settings.json` - **Windows**: `%APPDATA%/R/config/R/ClassiPyR/settings.json` -Settings are loaded automatically when you start the app, so your folder paths, class list location, and Python venv path are remembered between sessions. Settings can be reset by specifying `run_app(reset_settings = TRUE)`. +Settings are loaded automatically when you start the app, so your folder paths and class list location are remembered between sessions. Settings can be reset by specifying `run_app(reset_settings = TRUE)`. --- @@ -617,4 +606,4 @@ Optional dependencies: - **[`hdf5r`](https://CRAN.R-project.org/package=hdf5r)** for reading HDF5 (`.h5`) classifier output files. Install with `install.packages("hdf5r")`. -All R dependencies are installed automatically when you install `ClassiPyR`. Python is only needed for `.mat` file export. +All R dependencies are installed automatically when you install `ClassiPyR`. From 1c8f0784bcd6ab6460781826685854fa1c7d74e7 Mon Sep 17 00:00:00 2001 From: anderstorstensson Date: Thu, 27 Aug 2026 08:18:52 +0200 Subject: [PATCH 2/2] docs: use lifecycle for init_python_env() and run_app(venv_path) deprecations Replace the ad-hoc .Deprecated()/warning() calls with the standard lifecycle pattern: - Add lifecycle to Imports and badge SVGs to man/figures/ - venv_path defaults to deprecated(); is_present() guards the deprecate_warn() call - Deprecation badges in the roxygen docs for both topics (per-block @md since the package does not use roxygen markdown globally) - Test asserts the lifecycle_warning_deprecated condition class devtools::check(): 0 errors, 0 warnings, 0 notes --- DESCRIPTION | 1 + NAMESPACE | 1 + R/run_app.R | 20 +++++++++++------- R/utils.R | 26 +++++++++++++---------- man/figures/lifecycle-deprecated.svg | 21 +++++++++++++++++++ man/figures/lifecycle-experimental.svg | 21 +++++++++++++++++++ man/figures/lifecycle-stable.svg | 29 ++++++++++++++++++++++++++ man/figures/lifecycle-superseded.svg | 21 +++++++++++++++++++ man/init_python_env.Rd | 15 ++++++------- man/run_app.Rd | 12 ++++++++--- tests/testthat/test-utils.R | 5 ++++- 11 files changed, 143 insertions(+), 29 deletions(-) create mode 100644 man/figures/lifecycle-deprecated.svg create mode 100644 man/figures/lifecycle-experimental.svg create mode 100644 man/figures/lifecycle-stable.svg create mode 100644 man/figures/lifecycle-superseded.svg diff --git a/DESCRIPTION b/DESCRIPTION index fe60ed3..eec71af 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -25,6 +25,7 @@ Imports: bslib, curl, iRfcb (>= 0.10.0), + lifecycle, dplyr, DT, jsonlite, diff --git a/NAMESPACE b/NAMESPACE index be2f488..daeabdf 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -86,6 +86,7 @@ importFrom(iRfcb,ifcb_get_mat_variable) importFrom(iRfcb,ifcb_zip_matlab) importFrom(iRfcb,ifcb_zip_pngs) importFrom(jsonlite,fromJSON) +importFrom(lifecycle,deprecated) importFrom(shiny,shinyApp) importFrom(shinyFiles,shinyDirButton) importFrom(shinyjs,useShinyjs) diff --git a/R/run_app.R b/R/run_app.R index 51b16ab..59d29d8 100644 --- a/R/run_app.R +++ b/R/run_app.R @@ -6,8 +6,9 @@ #' This app relies on the iRfcb package for reading and writing IFCB data files, #' including MATLAB .mat files, entirely in R. #' -#' @param venv_path Deprecated and ignored. ClassiPyR no longer requires -#' Python; .mat files are read and written natively in R (iRfcb >= 0.10.0). +#' @param venv_path `r lifecycle::badge("deprecated")` Ignored. ClassiPyR no +#' longer requires Python; .mat files are read and written natively in R +#' (iRfcb >= 0.10.0). #' @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 @@ -30,7 +31,8 @@ #' # Reset all settings and start fresh #' run_app(reset_settings = TRUE) #' } -run_app <- function(venv_path = NULL, reset_settings = FALSE, launch.browser = TRUE, ...) { +#' @md +run_app <- function(venv_path = deprecated(), reset_settings = FALSE, launch.browser = TRUE, ...) { app_dir <- system.file("app", package = "ClassiPyR") if (app_dir == "") { stop("Could not find app directory. Try re-installing `ClassiPyR`.", @@ -50,10 +52,14 @@ run_app <- function(venv_path = NULL, reset_settings = FALSE, launch.browser = T # Capture user's working directory before Shiny changes it options(ClassiPyR.startup_wd = getwd()) - if (!is.null(venv_path)) { - warning("The venv_path argument is deprecated and ignored: ", - "ClassiPyR no longer requires Python (iRfcb >= 0.10.0 writes ", - ".mat files natively in R).", call. = FALSE) + if (lifecycle::is_present(venv_path)) { + lifecycle::deprecate_warn( + "0.3.0", "run_app(venv_path)", + details = paste( + "The argument is ignored: ClassiPyR no longer requires Python", + "(iRfcb >= 0.10.0 reads and writes .mat files natively in R)." + ) + ) } shiny::runApp(app_dir, launch.browser = launch.browser, ...) diff --git a/R/utils.R b/R/utils.R index 9b2af4f..f0c55c2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -9,6 +9,7 @@ #' @importFrom bslib bs_theme #' @importFrom DT renderDT #' @importFrom jsonlite fromJSON +#' @importFrom lifecycle deprecated #' @importFrom dplyr filter #' @importFrom DBI dbConnect dbDisconnect dbGetQuery dbWriteTable dbExecute #' @importFrom RSQLite SQLite @@ -793,26 +794,29 @@ create_empty_changes_log <- function() { ) } -#' Initialize Python environment for iRfcb (deprecated) +#' Initialize Python environment for iRfcb #' -#' Deprecated. As of iRfcb 0.10.0, MATLAB .mat files are read and written -#' with a native R implementation, so ClassiPyR no longer requires Python. -#' This function is now a no-op and will be removed in a future release. +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' `init_python_env()` was deprecated in ClassiPyR 0.3.0 and is now a no-op. +#' As of iRfcb 0.10.0, MATLAB .mat files are read and written with a native R +#' implementation, so ClassiPyR no longer requires Python. #' #' If you need a Python environment for other iRfcb features (e.g. feature -#' extraction), set one up with \code{\link[iRfcb]{ifcb_py_install}}. +#' extraction), set one up with [iRfcb::ifcb_py_install()]. #' #' @param venv_path Ignored. #' @return FALSE, invisibly #' @export #' @keywords internal +#' @md init_python_env <- function(venv_path = NULL) { - .Deprecated( - msg = paste( - "init_python_env() is deprecated and does nothing:", - "ClassiPyR no longer requires Python (iRfcb >= 0.10.0 writes .mat", - "files natively in R). For other iRfcb features that use Python,", - "see iRfcb::ifcb_py_install()." + lifecycle::deprecate_warn( + "0.3.0", "init_python_env()", + details = c( + "ClassiPyR no longer requires Python: iRfcb >= 0.10.0 reads and writes .mat files natively in R.", + "For other iRfcb features that use Python, see `iRfcb::ifcb_py_install()`." ) ) invisible(FALSE) diff --git a/man/figures/lifecycle-deprecated.svg b/man/figures/lifecycle-deprecated.svg new file mode 100644 index 0000000..b61c57c --- /dev/null +++ b/man/figures/lifecycle-deprecated.svg @@ -0,0 +1,21 @@ + + lifecycle: deprecated + + + + + + + + + + + + + + + lifecycle + + deprecated + + diff --git a/man/figures/lifecycle-experimental.svg b/man/figures/lifecycle-experimental.svg new file mode 100644 index 0000000..5d88fc2 --- /dev/null +++ b/man/figures/lifecycle-experimental.svg @@ -0,0 +1,21 @@ + + lifecycle: experimental + + + + + + + + + + + + + + + lifecycle + + experimental + + diff --git a/man/figures/lifecycle-stable.svg b/man/figures/lifecycle-stable.svg new file mode 100644 index 0000000..9bf21e7 --- /dev/null +++ b/man/figures/lifecycle-stable.svg @@ -0,0 +1,29 @@ + + lifecycle: stable + + + + + + + + + + + + + + + + lifecycle + + + + stable + + + diff --git a/man/figures/lifecycle-superseded.svg b/man/figures/lifecycle-superseded.svg new file mode 100644 index 0000000..db8d757 --- /dev/null +++ b/man/figures/lifecycle-superseded.svg @@ -0,0 +1,21 @@ + + lifecycle: superseded + + + + + + + + + + + + + + + lifecycle + + superseded + + diff --git a/man/init_python_env.Rd b/man/init_python_env.Rd index 3c06460..80dd472 100644 --- a/man/init_python_env.Rd +++ b/man/init_python_env.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/utils.R \name{init_python_env} \alias{init_python_env} -\title{Initialize Python environment for iRfcb (deprecated)} +\title{Initialize Python environment for iRfcb} \usage{ init_python_env(venv_path = NULL) } @@ -13,12 +13,13 @@ init_python_env(venv_path = NULL) FALSE, invisibly } \description{ -Deprecated. As of iRfcb 0.10.0, MATLAB .mat files are read and written -with a native R implementation, so ClassiPyR no longer requires Python. -This function is now a no-op and will be removed in a future release. -} -\details{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +\code{init_python_env()} was deprecated in ClassiPyR 0.3.0 and is now a no-op. +As of iRfcb 0.10.0, MATLAB .mat files are read and written with a native R +implementation, so ClassiPyR no longer requires Python. + If you need a Python environment for other iRfcb features (e.g. feature -extraction), set one up with \code{\link[iRfcb]{ifcb_py_install}}. +extraction), set one up with \code{\link[iRfcb:ifcb_py_install]{iRfcb::ifcb_py_install()}}. } \keyword{internal} diff --git a/man/run_app.Rd b/man/run_app.Rd index 8801afa..4dce715 100644 --- a/man/run_app.Rd +++ b/man/run_app.Rd @@ -4,11 +4,17 @@ \alias{run_app} \title{Run the ClassiPyR Shiny Application} \usage{ -run_app(venv_path = NULL, reset_settings = FALSE, launch.browser = TRUE, ...) +run_app( + venv_path = deprecated(), + reset_settings = FALSE, + launch.browser = TRUE, + ... +) } \arguments{ -\item{venv_path}{Deprecated and ignored. ClassiPyR no longer requires -Python; .mat files are read and written natively in R (iRfcb >= 0.10.0).} +\item{venv_path}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Ignored. ClassiPyR no +longer requires Python; .mat files are read and written natively in R +(iRfcb >= 0.10.0).} \item{reset_settings}{If TRUE, deletes saved settings before starting the app. Useful for troubleshooting or starting fresh. Default is FALSE.} diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 64e8cd8..6323f4d 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -109,7 +109,10 @@ test_that("update_settings_file recovers from a corrupt settings file", { # ============================================================================= test_that("init_python_env is deprecated and returns FALSE invisibly", { - expect_warning(result <- init_python_env(), "deprecated") + expect_warning( + result <- init_python_env(), + class = "lifecycle_warning_deprecated" + ) expect_type(result, "logical") expect_false(result)