@@ -1221,22 +1221,13 @@ private void runOnDemandAsync() {
12211221 onDemandButton .setEnabled (true );
12221222 return ;
12231223 }
1224- // Signal quality requires an image. Instead of aborting the entire scan when no image is loaded, soft-disable
1225- // just this check and proceed with the rest. The user gets a non-blocking status update explaining why;
1226- // they can re-enable manually after loading an image
1227- final PlausibilityCheck .SignalQuality sq = monitor .getDeepCheck (PlausibilityCheck .SignalQuality .class );
1228- if (sq != null && sq .isEnabled () && sntui .plugin .getLoadedData () == null ) {
1229- // Untick the checkbox AND fire its listener so the underlying check is also disabled. Using
1230- // doClick() toggles the selected state and triggers all registered action listeners in one call
1231- sntui .error ("\" Min. signal contrast ratio\" disabled: It requires a loaded image." );
1232- if (signalQualityCheckbox .isSelected ()) signalQualityCheckbox .doClick (0 );
1233- // If SignalQuality was the only enabled parameter, there's nothing left to scan.
1234- // Re-check after auto-disabling
1235- if (noParametersSelected ()) {
1236- onDemandButton .setEnabled (true );
1237- return ;
1238- }
1224+ // Image-requiring checks: when no image is loaded, soft-disable each
1225+ // one (rather than aborting the scan) and proceed with the rest.
1226+ if (disableImageDependentChecksIfNoImage ()) {
1227+ onDemandButton .setEnabled (true );
1228+ return ;
12391229 }
1230+ final PlausibilityCheck .SignalQuality sq = monitor .getDeepCheck (PlausibilityCheck .SignalQuality .class );
12401231 // Provide image context for checks that need it (e.g., SignalQuality)
12411232 monitor .setImageData (sntui .plugin .getLoadedData ());
12421233 // Ensure image statistics are available for auto-threshold. If not yet
@@ -1805,6 +1796,7 @@ private void wireCheckbox(final JCheckBox cb, final JSpinner spinner,
18051796 // Set initial enabled state
18061797 if (spinner != null ) {
18071798 spinner .setEnabled (cb .isSelected ());
1799+ enableLiveCommit (spinner );
18081800 }
18091801 cb .addActionListener (e -> {
18101802 final boolean sel = cb .isSelected ();
@@ -1814,6 +1806,20 @@ private void wireCheckbox(final JCheckBox cb, final JSpinner spinner,
18141806 });
18151807 }
18161808
1809+ /**
1810+ * Configures the spinner's editor so typed values commit on every valid keystroke rather than only on
1811+ * Enter / focus loss / arrow click. Fixes the case where typing a new threshold and then clicking the
1812+ * histogram button (or running a full scan) would otherwise use the previously-committed value.
1813+ */
1814+ private static void enableLiveCommit (final JSpinner spinner ) {
1815+ if (spinner .getEditor () instanceof JSpinner .DefaultEditor de ) {
1816+ final javax .swing .JFormattedTextField tf = de .getTextField ();
1817+ if (tf .getFormatter () instanceof javax .swing .text .DefaultFormatter df ) {
1818+ df .setCommitsOnValidEdit (true );
1819+ }
1820+ }
1821+ }
1822+
18171823 @ Override
18181824 public void warningsUpdated (final List <PlausibilityCheck .Warning > warnings ) {
18191825 SwingUtilities .invokeLater (() -> {
@@ -1924,31 +1930,29 @@ private void showCuratedPathsInPathManager() {
19241930 }
19251931
19261932 private static String getDocAnchor (final String checkName ) {
1927- // Anchor strings here must match the auto-generated header IDs in
1928- // curation.md (Jekyll/kramdown: lowercase, strip punctuation, replace
1929- // spaces with hyphens). Update both sides together when renaming
1930- // sections in the docs.
1933+ // Anchor strings here must match the auto-generated header IDs in curation.md (Jekyll/kramdown:
1934+ // all lowercase, strip punctuation, replace spaces with hyphens).
19311935 return switch (checkName ) {
19321936 // Live checks
1933- case "Branch angle" -> "#min- fork-angle-" ;
1934- case "Direction continuity" -> "#max -direction-change-at-fork -" ;
1935- case "Radius continuity" -> "#max-ratio-of-radius-change -at-fork" ;
1936- case "Inter-fork distance" -> "#min- inter-fork-distance" ;
1937- case "Terminal path length" -> "#min-length-of- terminal-paths" ;
1938- case "Tortuosity consistency" -> "#max- tortuosity-mismatch-at-fork" ;
1939- case "Constant radii" -> "#flag-paths-with- uniform-radii " ;
1937+ case "Branch angle" -> "#fork-angle-min -" ;
1938+ case "Direction continuity" -> "#fork -direction-flow-max-change -" ;
1939+ case "Radius continuity" -> "#radius-continuity-max-ratio -at-fork" ;
1940+ case "Inter-fork distance" -> "#inter-fork-distance-min " ;
1941+ case "Terminal path length" -> "#terminal-paths-min-length " ;
1942+ case "Tortuosity consistency" -> "#tortuosity-max -mismatch-at-fork" ;
1943+ case "Constant radii" -> "#uniform-thickness-flag " ;
19401944 // On-demand (deep) checks
1941- case "Path overlap" -> "#max-proximity-for-path-cross-overs " ;
1942- case "Bundled paths" -> "#max-proximity-for-bundled-paths " ;
1943- case "Missed-fork candidate" -> "#min-distance-for- missed-fork-candidates " ;
1944- case "Z-extent ratio" -> "#min- z-extent-ratio" ;
1945- case "Thickness jumps" -> "#max-ratio-of-abrupt-thickness-changes " ;
1946- case "Thickness inversions" -> "#min-run-length-for-thickness-inversions " ;
1947- case "Path signal quality" -> "#min -signal-contrast-ratio " ;
1948- case "Tip signal quality" -> "#min- tip-signal-contrast-ratio " ;
1949- case "Path signal quality dips" -> "#min -signal-quality-dip " ;
1945+ case "Path overlap" -> "#cross-over-detection-max-proximity " ;
1946+ case "Bundled paths" -> "#bundle-detection-max-angle- " ;
1947+ case "Missed-fork candidate" -> "#missed-fork-candidate-max-proximity " ;
1948+ case "Z-extent ratio" -> "#z-extent-min -ratio" ;
1949+ case "Thickness jumps" -> "#thickness-jumps-max-ratio " ;
1950+ case "Thickness inversions" -> "#thickness-inversions- min-run-length" ;
1951+ case "Path signal quality" -> "#path -signal-quality-min-contrast " ;
1952+ case "Tip signal quality" -> "#tip-signal-quality-min-contrast " ;
1953+ case "Path signal quality dips" -> "#path -signal-quality-dips-min-drop " ;
19501954 // Scripting-only checks (no UI but Help on Issue... still routes here)
1951- case "Soma distance" -> "#max-distance-from-soma-or- root" ;
1955+ case "Soma distance" -> "#max-distance-from-soma-root" ;
19521956 case "Boundary proximity" -> "#min-distance-from-image-boundary" ;
19531957 // Externally-contributed warnings (e.g. from the Seeds tab's
19541958 // "Send Selected Seeds to Curation Assistant" submenu).
@@ -1987,6 +1991,45 @@ private boolean noParametersSelected() {
19871991 return noParametersSelected (null , "" );
19881992 }
19891993
1994+ /**
1995+ * Soft-disables any image-dependent on-demand check when no image is loaded: unticks the corresponding checkbox
1996+ * (and fires its listener via {@code doClick()} so the underlying {@code PlausibilityCheck} also gets disabled),
1997+ * then surfaces a single consolidated error naming the disabled checks. Returns {@code true} when the caller
1998+ * should abort the scan because no enabled parameters remain after the auto-disable; {@code false} when the scan
1999+ * can proceed (either because an image is loaded, no image-based check was enabled, or other checks remain enabled).
2000+ */
2001+ private boolean disableImageDependentChecksIfNoImage () {
2002+ if (sntui .plugin .getLoadedData () != null ) return false ;
2003+ final List <String > disabled = new ArrayList <>(3 );
2004+ autoDisableImageCheck (monitor .getDeepCheck (PlausibilityCheck .SignalQuality .class ),
2005+ signalQualityCheckbox , "Min. signal contrast ratio" , disabled );
2006+ autoDisableImageCheck (monitor .getDeepCheck (PlausibilityCheck .UncertainTerminal .class ),
2007+ uncertainTerminalCheckbox , "Min. tip signal contrast ratio" , disabled );
2008+ autoDisableImageCheck (monitor .getDeepCheck (PlausibilityCheck .IntensityValley .class ),
2009+ intensityValleyCheckbox , "Min. signal-quality dip" , disabled );
2010+ if (disabled .isEmpty ()) return false ;
2011+ final String msg = (disabled .size () == 1 )
2012+ ? "\" " + disabled .getFirst () + "\" disabled: it requires a loaded image."
2013+ : "The following validations were disabled because no image is loaded: " + String .join (", " , disabled ) + "." ;
2014+ sntui .error (msg );
2015+ // Re-check after auto-disabling: if every parameter is now off,
2016+ // the caller should abort the scan rather than running with nothing.
2017+ return noParametersSelected ();
2018+ }
2019+
2020+ /**
2021+ * Helper for {@link #disableImageDependentChecksIfNoImage}: if the given image-dependent check is currently
2022+ * enabled, unticks its checkbox (firing the listener) and appends its display label to {@code tracker}.
2023+ * Does nothing if the check is null, already disabled, or the checkbox is null/unselected.
2024+ */
2025+ private static void autoDisableImageCheck (final PlausibilityCheck .DeepCheck check ,
2026+ final JCheckBox checkbox , final String label ,
2027+ final List <String > tracker ) {
2028+ if (check == null || !check .isEnabled ()) return ;
2029+ if (checkbox != null && checkbox .isSelected ()) checkbox .doClick (0 );
2030+ tracker .add (label );
2031+ }
2032+
19902033 private boolean noParametersSelected (final List <JCheckBox > scope , final String category ) {
19912034 // Defensive null-handling: liveCheckboxes / onDemandCheckboxes are assigned only after the panel has been
19922035 // built. Callers that arrive before construction (script-driven scans, programmatic triggers from external
0 commit comments