Skip to content

Commit bfdea09

Browse files
committed
Merge remote-tracking branch 'origin/develop' into develop
2 parents d8370d9 + 439ec00 commit bfdea09

15 files changed

Lines changed: 304 additions & 59 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ See DataLab [roadmap page](https://datalab-platform.com/en/contributing/roadmap.
185185
* Planckian (blackbody) fitting with physics-correct parameter bounds
186186
* Asymmetric peak fitting (two-half Gaussian) with independent left/right parameters
187187
* CDF (Cumulative Distribution Function) fitting for statistical analysis
188+
* **Locked parameter support** (requires PlotPy v2.8.0):
189+
* Added ability to lock individual fit parameters during automatic optimization
190+
* Users can manually adjust parameters and lock them to prevent changes during auto-fit
191+
* Visual indicators: locked parameters show 🔒 emoji and are grayed out with disabled controls
192+
* Enables partial optimization workflows: fix well-determined parameters, optimize uncertain ones
193+
* All optimization algorithms (simplex, Powell, BFGS, L-BFGS-B, conjugate gradient, least squares) support locked parameters
194+
* Improves fit convergence by reducing problem dimensionality
195+
* Lock/unlock parameters via the parameter settings dialog (gear icon)
188196
* **Improved user experience**: The curve fitting dialogs now provide:
189197
* More reliable initial parameter guesses reducing manual adjustment needs
190198
* Better parameter bounds preventing optimization failures

datalab/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ class MainSection(conf.Section, metaclass=conf.SectionMeta):
121121
plugins_enabled = conf.Option()
122122
plugins_path = conf.Option()
123123
tour_enabled = conf.Option()
124+
v020_plugins_warning_ignore = conf.Option() # True: do not warn, False: warn
124125

125126

126127
class ConsoleSection(conf.Section, metaclass=conf.SectionMeta):
@@ -341,6 +342,7 @@ def initialize():
341342
Conf.main.plugins_enabled.get(True)
342343
Conf.main.plugins_path.get(Conf.get_path("plugins"))
343344
Conf.main.tour_enabled.get(True)
345+
Conf.main.v020_plugins_warning_ignore.get(False)
344346
# Console section
345347
Conf.console.console_enabled.get(True)
346348
Conf.console.show_console_on_error.get(False)

datalab/gui/actionhandler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,7 @@ def cra_fit(title, fitdlgfunc, tip: str | None = None):
10841084
):
10851085
self.action_for(fit_name, separator=separator_needed)
10861086
separator_needed = False
1087+
self.action_for("evaluate_fit", separator=True)
10871088
self.action_for("apply_window")
10881089
self.action_for("detrending")
10891090
self.action_for("interpolate")

datalab/gui/main.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
from datalab.gui.h5io import H5InputOutput
6464
from datalab.gui.panel import base, image, macro, signal
6565
from datalab.gui.settings import edit_settings
66-
from datalab.plugins import PluginRegistry, discover_plugins
66+
from datalab.plugins import PluginRegistry, discover_plugins, discover_v020_plugins
6767
from datalab.utils import dephash
6868
from datalab.utils import qthelpers as qth
6969
from datalab.utils.qthelpers import (
@@ -651,10 +651,64 @@ def check_for_previous_crash(self) -> None: # pragma: no cover
651651
if choice == QW.QMessageBox.StandardButton.Yes:
652652
self.__show_logviewer()
653653

654+
def check_for_v020_plugins(self) -> None: # pragma: no cover
655+
"""Check for v0.20 plugins and warn user if any are found"""
656+
if Conf.main.v020_plugins_warning_ignore.get(False):
657+
return
658+
659+
v020_plugins = discover_v020_plugins()
660+
if execenv.unattended or not v020_plugins:
661+
return
662+
663+
# Build plugin list with clickable directory paths
664+
plugin_items = []
665+
for name, directory_path in v020_plugins:
666+
if directory_path:
667+
# Create clickable file:// link to directory
668+
dir_url = QC.QUrl.fromLocalFile(directory_path).toString()
669+
plugin_items.append(
670+
f'<li>{name} (<a href="{dir_url}">{directory_path}</a>)</li>'
671+
)
672+
else:
673+
plugin_items.append(f"<li>{name}</li>")
674+
plugin_list = "<ul>" + "".join(plugin_items) + "</ul>"
675+
676+
txtlist = [
677+
"<b>" + _("DataLab v0.20 plugins detected") + "</b>",
678+
"",
679+
_("The following plugins are using the old DataLab v0.20 format:"),
680+
plugin_list,
681+
_(
682+
"These plugins will <b>not be loaded</b> in DataLab v1.0 because "
683+
"they are not compatible with the new architecture."
684+
),
685+
"",
686+
_(
687+
"To use these plugins with DataLab v1.0, you need to update them. "
688+
"Please refer to the migration guide on the DataLab website "
689+
)
690+
+ '(<a href="https://datalab-platform.com/en/features/advanced/'
691+
'migration_v020_to_v100.html">Migration guide</a>)'
692+
+ _(" or in the PDF documentation."),
693+
"",
694+
_("Choosing to ignore this message will prevent it from appearing again."),
695+
]
696+
697+
answer = QW.QMessageBox.question(
698+
self,
699+
APP_NAME,
700+
"<br>".join(txtlist),
701+
QW.QMessageBox.Ok | QW.QMessageBox.Ignore,
702+
)
703+
704+
if answer == QW.QMessageBox.Ignore:
705+
Conf.main.v020_plugins_warning_ignore.set(True)
706+
654707
def execute_post_show_actions(self) -> None:
655708
"""Execute post-show actions"""
656709
self.check_stable_release()
657710
self.check_for_previous_crash()
711+
self.check_for_v020_plugins()
658712
tour = Conf.main.tour_enabled.get()
659713
if tour:
660714
Conf.main.tour_enabled.set(False)
@@ -895,7 +949,7 @@ def __setup_global_actions(self) -> None:
895949
_("Auto-refresh"),
896950
icon=get_icon("refresh-auto.svg"),
897951
tip=_("Auto-refresh plot when object is modified, added or removed"),
898-
toggled=self.toggle_auto_refresh,
952+
toggled=self.handle_autorefresh_action,
899953
)
900954
self.showfirstonly_action = create_action(
901955
self,
@@ -1385,6 +1439,48 @@ def toggle_show_titles(self, state: bool) -> None:
13851439
obj.set_metadata_option("showlabel", state)
13861440
datapanel.refresh_plot("selected", True, False)
13871441

1442+
def handle_autorefresh_action(self, state: bool) -> None:
1443+
"""Handle auto-refresh action from UI (with confirmation dialog)
1444+
1445+
Args:
1446+
state: desired state
1447+
"""
1448+
# If disabling auto-refresh, show confirmation dialog
1449+
if not state:
1450+
txtlist = [
1451+
"<b>" + _("Disable auto-refresh?") + "</b>",
1452+
"",
1453+
_(
1454+
"When auto-refresh is disabled, the plot view will not "
1455+
"automatically update when objects are modified, added or removed."
1456+
),
1457+
"",
1458+
_(
1459+
"You will need to manually click the refresh button to update "
1460+
"the view."
1461+
),
1462+
"",
1463+
_("Are you sure you want to disable auto-refresh?"),
1464+
]
1465+
1466+
answer = QW.QMessageBox.question(
1467+
self,
1468+
APP_NAME,
1469+
"<br>".join(txtlist),
1470+
QW.QMessageBox.Yes | QW.QMessageBox.No,
1471+
QW.QMessageBox.No,
1472+
)
1473+
1474+
if answer == QW.QMessageBox.No:
1475+
# User cancelled, restore the action's checked state
1476+
self.autorefresh_action.blockSignals(True)
1477+
self.autorefresh_action.setChecked(True)
1478+
self.autorefresh_action.blockSignals(False)
1479+
return
1480+
1481+
# Apply the change
1482+
self.toggle_auto_refresh(state)
1483+
13881484
@remote_controlled
13891485
def toggle_auto_refresh(self, state: bool) -> None:
13901486
"""Toggle auto refresh option

datalab/gui/processor/signal.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@
4242
class SignalProcessor(BaseProcessor[SignalROI, ROI1DParam]):
4343
"""Object handling signal processing: operations, processing, analysis"""
4444

45+
FIT_FUNCTIONS = (
46+
(_("Linear fit"), sips.linear_fit),
47+
(_("Polynomial fit"), sips.polynomial_fit),
48+
(_("Gaussian fit"), sips.gaussian_fit),
49+
(_("Lorentzian fit"), sips.lorentzian_fit),
50+
(_("Voigt fit"), sips.voigt_fit),
51+
(_("Planckian fit"), sips.planckian_fit),
52+
(_("Two Half-Gaussians fit"), sips.twohalfgaussian_fit),
53+
(_("Piecewise exponential fit"), sips.piecewiseexponential_fit),
54+
(_("Exponential fit"), sips.exponential_fit),
55+
(_("Sinusoidal fit"), sips.sinusoidal_fit),
56+
(_("CDF fit"), sips.cdf_fit),
57+
(_("Sigmoid fit"), sips.sigmoid_fit),
58+
)
59+
4560
# pylint: disable=duplicate-code
4661

4762
def register_operations(self) -> None:
@@ -342,23 +357,18 @@ def register_processing(self) -> None:
342357
"bandstop.svg",
343358
)
344359
# Curve fitting
345-
for fit_name, fit_func in [
346-
(_("Linear fit"), sips.linear_fit),
347-
(_("Polynomial fit"), sips.polynomial_fit),
348-
(_("Gaussian fit"), sips.gaussian_fit),
349-
(_("Lorentzian fit"), sips.lorentzian_fit),
350-
(_("Voigt fit"), sips.voigt_fit),
351-
(_("Planckian fit"), sips.planckian_fit),
352-
(_("Two Half-Gaussians fit"), sips.twohalfgaussian_fit),
353-
(_("Piecewise exponential fit"), sips.piecewiseexponential_fit),
354-
(_("Exponential fit"), sips.exponential_fit),
355-
(_("Sinusoidal fit"), sips.sinusoidal_fit),
356-
(_("CDF fit"), sips.cdf_fit),
357-
(_("Sigmoid fit"), sips.sigmoid_fit),
358-
]:
360+
for fit_name, fit_func in self.FIT_FUNCTIONS:
359361
icon_name = f"{fit_func.__name__}.svg"
360362
self.register_1_to_1(fit_func, fit_name, icon_name=icon_name)
361363

364+
# Evaluate fit on another signal's x-axis
365+
self.register_2_to_1(
366+
sips.evaluate_fit,
367+
_("Evaluate fit"),
368+
obj2_name=_("signal for X values"),
369+
comment=_("Evaluate a fitting curve on the x-axis of another signal"),
370+
)
371+
362372
# Other processing
363373
self.register_1_to_1(
364374
sips.apply_window,

datalab/gui/settings.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ class MainSettings(gds.DataSet):
6969
"as well as in your PYTHONPATH."
7070
),
7171
)
72+
v020_plugins_warning_ignore = gds.BoolItem(
73+
_("Ignore compatibility issues warning"),
74+
_("DataLab v0.20 plugins"),
75+
help=_(
76+
"If enabled, DataLab will not warn you about v0.20 plugins that are "
77+
"no longer compatible with v1.0."
78+
),
79+
)
7280
_g0 = gds.EndGroup("")
7381

7482

@@ -137,17 +145,17 @@ class ProcSettings(gds.DataSet):
137145
% ("→", "→"),
138146
)
139147
use_signal_bounds = gds.BoolItem(
140-
"",
141-
_("Use signal bounds for new signals"),
148+
_("Use current signal bounds"),
149+
_("New signals"),
142150
help=_(
143151
"If enabled, when creating a new signal, the xmin and xmax values will be "
144152
"initialized from the current signal's bounds.<br>"
145153
"If disabled, the default values from the New Signal dialog will be used."
146154
),
147155
)
148156
use_image_dims = gds.BoolItem(
149-
"",
150-
_("Use image dimensions for new images"),
157+
_("Use current image dimensions"),
158+
_("New images"),
151159
help=_(
152160
"If enabled, when creating a new image, the width and height values will "
153161
"be initialized from the current image's dimensions.<br>"

0 commit comments

Comments
 (0)