diff --git a/README.md b/README.md index 1930a81..ab43559 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,8 @@ Group website: https://murphy-group.chemistry.illinois.edu/ ## Notes: - DO NOT double-click on RodSizer_CLEANER_MacOS.command unless you are sure that you want to clean ALL local history of data and reports. -- First-time launching may take some time, like 5-10 mins. Most of that is - installing the deep-learning packages (`tensorflow` + `stardist`, ~1 GB). These - are kept for possible future ML-based detection but are **not used** by the - current pipeline (K-means + watershed), so the wait is one-time setup only — - later launches are fast. See `backend/requirements.txt` to remove them if you - want a leaner install. +- First-time launching takes a few minutes (typically 2-3) while Python + packages are installed. This is one-time setup only — later launches are fast. - Try ask a coding agent if there's an issue with environment setup. - MacOS is more recommended. - (Windows) If Windows Defender asks, click `More Info` -> `Run Anyway`. @@ -88,4 +84,4 @@ Group website: https://murphy-group.chemistry.illinois.edu/ ## Requirements - macOS or Windows -- Python 3 installed (standard on most Macs, or downloadable from `python.org`) +- Python 3.10-3.12 (the Mac launcher installs it automatically if missing; on Windows download from `python.org`) diff --git a/RodSizer_DEBUG_Windows.bat b/RodSizer_DEBUG_Windows.bat index 5f13165..e55bd80 100644 --- a/RodSizer_DEBUG_Windows.bat +++ b/RodSizer_DEBUG_Windows.bat @@ -32,9 +32,11 @@ REM Auto-Detect if exist "C:\Python39\python.exe" set "PY_EXE=C:\Python39\python.exe" & goto FOUND_PYTHON if exist "C:\Python310\python.exe" set "PY_EXE=C:\Python310\python.exe" & goto FOUND_PYTHON if exist "C:\Python311\python.exe" set "PY_EXE=C:\Python311\python.exe" & goto FOUND_PYTHON +if exist "C:\Python312\python.exe" set "PY_EXE=C:\Python312\python.exe" & goto FOUND_PYTHON if exist "C:\Program Files\Python39\python.exe" set "PY_EXE=C:\Program Files\Python39\python.exe" & goto FOUND_PYTHON if exist "C:\Program Files\Python310\python.exe" set "PY_EXE=C:\Program Files\Python310\python.exe" & goto FOUND_PYTHON if exist "C:\Program Files\Python311\python.exe" set "PY_EXE=C:\Program Files\Python311\python.exe" & goto FOUND_PYTHON +if exist "C:\Program Files\Python312\python.exe" set "PY_EXE=C:\Program Files\Python312\python.exe" & goto FOUND_PYTHON :ASK_USER echo. diff --git a/RodSizer_Launcher_MacOS.command b/RodSizer_Launcher_MacOS.command index b33ea75..b5aa3e0 100755 --- a/RodSizer_Launcher_MacOS.command +++ b/RodSizer_Launcher_MacOS.command @@ -74,65 +74,47 @@ if command -v tesseract &>/dev/null; then fi # ============================================================================= -# STEP 2 — Python 3.11 -# -# TensorFlow 2.x officially supports Python 3.8–3.11. -# Python 3.12+ does NOT have stable TensorFlow wheels yet. -# We therefore require Python 3.11 specifically. +# STEP 2 — Python 3.10-3.12 +# (the pinned dependency set in backend/requirements.txt targets these +# versions; 3.13+ lacks wheels for the pinned numpy) # ============================================================================= -step "Step 2/4: Checking Python 3.11" +step "Step 2/4: Checking Python" PYTHON_CMD="" -# Helper: find python3.11 by checking all known locations -find_python311() { - # 1. Homebrew-managed path (most reliable — works regardless of PATH) - local brew_prefix - brew_prefix="$(brew --prefix python@3.11 2>/dev/null)" - if [ -n "$brew_prefix" ] && [ -x "$brew_prefix/bin/python3.11" ]; then - echo "$brew_prefix/bin/python3.11"; return - fi - - # 2. Direct binary in PATH - if command -v python3.11 &>/dev/null; then - command -v python3.11; return - fi - - # 3. Common Homebrew Cellar fallback (Apple Silicon) - local cellar_py - cellar_py=$(ls /opt/homebrew/Cellar/python@3.11/*/bin/python3.11 2>/dev/null | sort -V | tail -1) - if [ -x "$cellar_py" ]; then - echo "$cellar_py"; return - fi - # Intel - cellar_py=$(ls /usr/local/Cellar/python@3.11/*/bin/python3.11 2>/dev/null | sort -V | tail -1) - if [ -x "$cellar_py" ]; then - echo "$cellar_py"; return - fi - - # 4. pyenv - if command -v pyenv &>/dev/null; then - local pyenv_ver - pyenv_ver=$(pyenv versions --bare 2>/dev/null | grep "^3\.11\." | sort -V | tail -1) - if [ -n "$pyenv_ver" ]; then - local pyenv_py="$(pyenv root)/versions/$pyenv_ver/bin/python3.11" - [ -x "$pyenv_py" ] && echo "$pyenv_py"; return +# Helper: find a suitable Python (3.10-3.12), preferring newer versions +find_python() { + local candidate + for candidate in python3.12 python3.11 python3.10 python3; do + if command -v "$candidate" &>/dev/null; then + if "$candidate" -c 'import sys; sys.exit(0 if (3, 10) <= sys.version_info[:2] <= (3, 12) else 1)' &>/dev/null; then + command -v "$candidate"; return + fi fi - fi + done + + # Homebrew-managed fallback (works regardless of PATH) + local brew_prefix ver + for ver in 3.12 3.11 3.10; do + brew_prefix="$(brew --prefix python@$ver 2>/dev/null)" + if [ -n "$brew_prefix" ] && [ -x "$brew_prefix/bin/python$ver" ]; then + echo "$brew_prefix/bin/python$ver"; return + fi + done } -PYTHON_CMD="$(find_python311)" +PYTHON_CMD="$(find_python)" # If still not found, install via Homebrew then retry if [ -z "$PYTHON_CMD" ]; then - warn "Python 3.11 not found. Installing via Homebrew (needed for TensorFlow)..." - brew install python@3.11 - PYTHON_CMD="$(find_python311)" + warn "Python 3.10-3.12 not found. Installing 3.12 via Homebrew..." + brew install python@3.12 + PYTHON_CMD="$(find_python)" fi if [ -z "$PYTHON_CMD" ] || [ ! -x "$PYTHON_CMD" ]; then - error "Could not find or install Python 3.11." - error "Please install it manually: brew install python@3.11" + error "Could not find or install Python." + error "Please install it manually: brew install python@3.12" echo "Press Enter to close..."; read -r; exit 1 fi @@ -170,84 +152,21 @@ needs_python_packages() { "$VENV_PYTHON" - <<'PY' &>/dev/null import importlib -for name in ("fastapi", "uvicorn", "tensorflow", "numpy", "cv2", "stardist"): +for name in ("fastapi", "uvicorn", "numpy", "cv2", "skimage", "tifffile", "h5py"): importlib.import_module(name) PY } -install_tensorflow_with_fallback() { - local tf_spec="tensorflow==2.21.0" - - info "Installing TensorFlow..." - if "$VENV_PYTHON" -m pip install "$tf_spec"; then - return 0 - fi - - warn "pip could not resolve tensorflow from the package index." - warn "Retrying TensorFlow install directly from PyPI..." - PIP_CONFIG_FILE=/dev/null "$VENV_PYTHON" -m pip install --index-url https://pypi.org/simple "$tf_spec" && return 0 - - return 1 -} - # Check whether a first-time install is needed if ! needs_python_packages; then - info "Installing Python packages — this may take 5–10 minutes on first run." + info "Installing Python packages — this may take 2–3 minutes on first run." info "Please do NOT close this window." # Upgrade pip / setuptools silently "$VENV_PYTHON" -m pip install --upgrade pip setuptools wheel --quiet - # ── TensorFlow first (sets numpy version constraint everything else must follow) ── - # - # tensorflow-macos was deprecated at 2.13 and forces numpy<2, which conflicts - # with opencv-python-headless >=4.9 and ncempy >=1.15 (both require numpy>=2). - # - # tensorflow >= 2.16 ships a universal wheel that runs natively on Apple - # Silicon (M-series) without a separate macos fork, and is compatible with - # numpy 2.x. tensorflow-metal is still the GPU-acceleration plugin for M-chips. - if ! install_tensorflow_with_fallback; then - error "TensorFlow installation failed." - error "This is usually a package-index or network issue, not a RodSizer code issue." - if [ "$IS_APPLE_SILICON" = true ]; then - error "If needed, try this manually inside backend/.venv:" - error " python -m pip install --index-url https://pypi.org/simple tensorflow==2.21.0" - else - error "Try rerunning later, or install TensorFlow manually inside backend/.venv." - fi - echo "Press Enter to close..."; read -r; exit 1 - fi - - # Note: tensorflow-metal is NOT installed because v1.2.0 is incompatible - # with tensorflow >=2.16 (dlopen fails on _pywrap_tensorflow_internal.so). - # Modern TensorFlow already runs natively on Apple Silicon without it. - - if ! "$VENV_PYTHON" -c "import tensorflow" &>/dev/null 2>&1; then - error "TensorFlow import failed after installation." - error "Try deleting backend/.venv and relaunching, or check network connectivity." - echo "Press Enter to close..."; read -r; exit 1 - fi - - # ── Remaining packages (numpy version is now fixed by TF above) ───────────── - TMP_REQ=$(mktemp /tmp/rodsizer_req_XXXX.txt) - grep -v "^tensorflow" "$BACKEND_DIR/requirements.txt" > "$TMP_REQ" - - if ! "$VENV_PYTHON" -m pip install -r "$TMP_REQ" --quiet; then + if ! "$VENV_PYTHON" -m pip install -r "$BACKEND_DIR/requirements.txt" --quiet; then error "Failed to install some packages. Check the output above." - rm -f "$TMP_REQ" - echo "Press Enter to close..."; read -r; exit 1 - fi - rm -f "$TMP_REQ" - - # ── Verify tensorflow is importable ─────────────────────────────────────── - if ! "$VENV_PYTHON" -c "import tensorflow" &>/dev/null 2>&1; then - error "TensorFlow installation failed." - error "Try running this manually inside the venv:" - if [ "$IS_APPLE_SILICON" = true ]; then - error " python -m pip install --index-url https://pypi.org/simple tensorflow==2.21.0" - else - error " python -m pip install tensorflow==2.21.0" - fi echo "Press Enter to close..."; read -r; exit 1 fi diff --git a/RodSizer_Launcher_Windows.bat b/RodSizer_Launcher_Windows.bat index 1273e89..8a5b50e 100644 --- a/RodSizer_Launcher_Windows.bat +++ b/RodSizer_Launcher_Windows.bat @@ -29,9 +29,11 @@ REM --- 3. Try Auto-Detect Common Paths --- if exist "C:\Python39\python.exe" set "PY_EXE=C:\Python39\python.exe" & goto FOUND_PYTHON if exist "C:\Python310\python.exe" set "PY_EXE=C:\Python310\python.exe" & goto FOUND_PYTHON if exist "C:\Python311\python.exe" set "PY_EXE=C:\Python311\python.exe" & goto FOUND_PYTHON +if exist "C:\Python312\python.exe" set "PY_EXE=C:\Python312\python.exe" & goto FOUND_PYTHON if exist "C:\Program Files\Python39\python.exe" set "PY_EXE=C:\Program Files\Python39\python.exe" & goto FOUND_PYTHON if exist "C:\Program Files\Python310\python.exe" set "PY_EXE=C:\Program Files\Python310\python.exe" & goto FOUND_PYTHON if exist "C:\Program Files\Python311\python.exe" set "PY_EXE=C:\Program Files\Python311\python.exe" & goto FOUND_PYTHON +if exist "C:\Program Files\Python312\python.exe" set "PY_EXE=C:\Program Files\Python312\python.exe" & goto FOUND_PYTHON REM --- 4. User Input Fallback --- :ASK_USER diff --git a/backend/requirements.txt b/backend/requirements.txt index ee2d12c..1c30e9a 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,24 +1,24 @@ -fastapi -uvicorn -python-multipart -numpy -opencv-python-headless -scikit-image -ncempy -matplotlib -pandas -Pillow -pytesseract -tifffile -openpyxl -h5py +# Pinned, known-good dependency set (tested on Python 3.12, July 2026). +# Pinning exact versions keeps every computer running identical code — +# unpinned installs were a major source of "works here, not there" bugs. +# Python 3.10-3.12 required (numpy 1.26 has no wheels for 3.13+). +fastapi==0.122.0 +uvicorn==0.38.0 +python-multipart==0.0.20 +numpy==1.26.4 +opencv-python-headless==4.12.0.88 +scikit-image==0.25.2 +ncempy==1.14 +matplotlib==3.10.7 +pandas==2.3.3 +Pillow==12.0.0 +pytesseract==0.3.13 +tifffile==2025.10.16 +openpyxl==3.1.5 +h5py==3.15.1 -# --- Heavy deps kept for possible future ML detection (NOT used right now) --- -# stardist + tensorflow are the main reason the FIRST-TIME setup is slow (~5-10 -# min) and large (~1 GB+): they pull in a full TensorFlow build. The current -# detection pipeline is K-means + distance-transform watershed and does NOT use -# them (they are imported nowhere in the hot path). They are kept on purpose in -# case StarDist-based detection is added later. Remove these two lines if you -# want a much faster, smaller install and don't plan to use StarDist. -stardist -tensorflow +# NOTE: stardist + tensorflow were removed (July 2026). Nothing in the current +# pipeline (K-means + distance-transform watershed) uses them, and they were the +# main reason first-time setup was slow (~5-10 min, ~1 GB) and failed on some +# machines (no TensorFlow wheels for newer Python versions). If StarDist-based +# detection is ever added, re-add: stardist and tensorflow diff --git a/frontend/folder_analysis.html b/frontend/folder_analysis.html index d383a03..2c019ae 100644 --- a/frontend/folder_analysis.html +++ b/frontend/folder_analysis.html @@ -278,11 +278,11 @@

Batch Ledger

- - - - - + + + + + @@ -308,6 +308,8 @@

Batch Ledger

dlBtn.href = `/folders/${encodeURIComponent(folderName)}/export_aggregate`; let lengthChart, widthChart; + let currentData = []; + let currentSort = { column: null, direction: 'asc' }; async function loadAggregateData() { try { @@ -338,14 +340,41 @@

Batch Ledger

document.getElementById('meanAR').textContent = data.stats.mean_ar || "-"; // Charts - const items = data.data; + currentData = data.data || []; + const items = currentData; const lengths = items.map(d => d.length_nm); const widths = items.map(d => d.width_nm); lengthChart = createHistogram('lengthChart', 'Length Distribution', lengths); widthChart = createHistogram('widthChart', 'Width Distribution', widths); - // Table + renderTable(); + } + + function renderTable() { + let items = currentData.slice(); + + if (currentSort.column) { + const col = currentSort.column; + const dir = currentSort.direction === 'asc' ? 1 : -1; + items.sort((a, b) => { + const va = a[col], vb = b[col]; + if (typeof va === 'string' || typeof vb === 'string') { + return String(va ?? '').localeCompare(String(vb ?? '')) * dir; + } + return ((va ?? 0) - (vb ?? 0)) * dir; + }); + } + + // Update header arrows + document.querySelectorAll('#dataTable .sort-arrow').forEach(span => { + if (span.dataset.col === currentSort.column) { + span.textContent = currentSort.direction === 'asc' ? '▲' : '▼'; + } else { + span.textContent = ''; + } + }); + const tbody = document.querySelector('#dataTable tbody'); tbody.innerHTML = ''; items.forEach(item => { @@ -366,6 +395,16 @@

Batch Ledger

}); } + function sortTable(column) { + if (currentSort.column === column) { + currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; + } else { + currentSort.column = column; + currentSort.direction = 'asc'; + } + renderTable(); + } + function createHistogram(canvasId, label, data) { const ctx = document.getElementById(canvasId).getContext('2d'); if (!data.length) return;
Source ImageIDL (nm)W (nm)ARSource Image ID L (nm) W (nm) AR