llwp 2.1.1__tar.gz → 2.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llwp
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: LLWP is a fast, efficient and easy solution for exploring and assigning spectra - relying on Loomis-Wood plots.
5
5
  Author-email: Luis Bonah <bonah@ph1.uni-koeln.de>
6
6
  License-Expression: GPL-3.0-only
@@ -158,6 +158,10 @@ def status_d(func):
158
158
  return _wrapper
159
159
 
160
160
 
161
+ # Guards all modifications of matplotlib artists as well as the rendering of the figures
162
+ matplotlib_lock = threading.RLock()
163
+
164
+
161
165
  def lock_d(lock):
162
166
  @wrapt.decorator
163
167
  def _wrapper(wrapped, instance, args, kwargs):
@@ -707,6 +711,7 @@ class PlotWidget(QWidget):
707
711
 
708
712
  self.update_plot()
709
713
 
714
+ @lock_d(matplotlib_lock)
710
715
  def update_plot(self):
711
716
  scaling = config["plot_yscale"]
712
717
 
@@ -912,16 +917,17 @@ class PlotWidget(QWidget):
912
917
 
913
918
 
914
919
  drawplot_decorator = DynamicDecorator()
915
- matplotlib_lock = threading.RLock()
916
920
 
917
921
 
918
922
  ##
919
923
  ## Customized PyQt classes
920
924
  ##
921
925
  class QThread(QThread):
926
+ # Maps each function to the sequence number of its most recently created thread
922
927
  active_runnables = {}
923
928
  lock = threading.RLock()
924
929
  threads = set()
930
+ _sequence = AtomicCounter()
925
931
 
926
932
  @classmethod
927
933
  def threaded_d(cls, func):
@@ -932,26 +938,37 @@ class QThread(QThread):
932
938
 
933
939
  return wrapper
934
940
 
941
+ @classmethod
942
+ def _release_finished(cls):
943
+ # Python references are only dropped once Qt reports the thread as finished.
944
+ # Dropping them earlier (e.g. from the finished signal, which is emitted
945
+ # before the thread has fully stopped) can destroy a running QThread.
946
+ with cls.lock:
947
+ for thread in [t for t in cls.threads if t.isFinished()]:
948
+ thread.wait()
949
+ cls.threads.discard(thread)
950
+
935
951
  def __init__(self, function, *args, **kwargs):
936
952
  super().__init__()
937
953
  self.function, self.args, self.kwargs = function, args, kwargs
938
- self.threads.add(self)
939
- self.finished.connect(lambda x=self: self.threads.remove(x))
954
+ with self.lock:
955
+ self._release_finished()
956
+ # The sequence number is assigned at creation (not when the thread
957
+ # starts running), so the newest request always wins and the result
958
+ # does not depend on OS scheduling or on reused thread idents.
959
+ self.sequence = self._sequence.increase()
960
+ self.active_runnables[self.function] = self.sequence
961
+ self.threads.add(self)
940
962
 
941
963
  def earlyreturn(self):
942
- if self.active_runnables[self.function] != self.thread_id:
964
+ if self.active_runnables.get(self.function) != self.sequence:
943
965
  raise EarlyReturnError
944
966
 
945
967
  def run(self):
946
968
  try:
947
- with self.lock:
948
- self.thread_id = threading.current_thread().ident
949
- self.active_runnables[self.function] = self.thread_id
950
969
  self.function(*self.args, **self.kwargs, thread=self)
951
970
  except EarlyReturnError:
952
971
  pass
953
- except Exception:
954
- raise
955
972
 
956
973
 
957
974
  class NTimesBehavior(QObject):
@@ -1140,6 +1157,21 @@ class Figure(Figure):
1140
1157
 
1141
1158
 
1142
1159
  class FigureCanvas(FigureCanvas):
1160
+ # Rendering and painting run in the GUI thread, while worker threads modify the
1161
+ # artists of the figure. Both have to hold the matplotlib_lock, otherwise the
1162
+ # renderer might traverse artists that are removed at the same time.
1163
+ def draw(self):
1164
+ with matplotlib_lock:
1165
+ super().draw()
1166
+
1167
+ def paintEvent(self, event):
1168
+ with matplotlib_lock:
1169
+ super().paintEvent(event)
1170
+
1171
+ def blit(self, *args, **kwargs):
1172
+ with matplotlib_lock:
1173
+ super().blit(*args, **kwargs)
1174
+
1143
1175
  def __init__(self, *args, **kwargs):
1144
1176
  super().__init__(*args, **kwargs)
1145
1177
  self.wheelEvent = lambda event: event.ignore()
@@ -2639,6 +2671,7 @@ class LWPAx:
2639
2671
  ax.set_xticks([])
2640
2672
 
2641
2673
  @drawplot_decorator.d
2674
+ @lock_d(matplotlib_lock)
2642
2675
  def update(self):
2643
2676
  ax = self.ax
2644
2677
  ax.set_xlim(self.xrange)
@@ -2753,7 +2786,7 @@ class LWPAx:
2753
2786
 
2754
2787
  else:
2755
2788
  segs = np.array(((xs, xs), (ys * 0, ys))).T
2756
- colors = dataframe["color"].to_numpy()
2789
+ colors = dataframe["color"].to_numpy().copy()
2757
2790
 
2758
2791
  mask = xs == self.ref_position
2759
2792
  colors[mask] = config["color_ref"]
@@ -2923,12 +2956,13 @@ class LWPAx:
2923
2956
 
2924
2957
  def fit_data(self, xmin, xmax):
2925
2958
  # Delete artists highlighting previous fit
2926
- if self.__class__.fit_vline is not None:
2927
- self.__class__.fit_vline.remove()
2928
- self.__class__.fit_vline = None
2929
- if self.__class__.fit_curve is not None:
2930
- self.__class__.fit_curve.remove()
2931
- self.__class__.fit_curve = None
2959
+ with matplotlib_lock:
2960
+ if self.__class__.fit_vline is not None:
2961
+ self.__class__.fit_vline.remove()
2962
+ self.__class__.fit_vline = None
2963
+ if self.__class__.fit_curve is not None:
2964
+ self.__class__.fit_curve.remove()
2965
+ self.__class__.fit_curve = None
2932
2966
 
2933
2967
  # Fit the data
2934
2968
  xmiddle, xuncert, fit_xs, fit_ys = self.fit_peak(xmin, xmax)
@@ -2936,12 +2970,13 @@ class LWPAx:
2936
2970
  QApplication.clipboard().setText(str(xmiddle))
2937
2971
 
2938
2972
  # Highlight fit in plot
2939
- self.__class__.fit_curve = self.ax.plot(
2940
- fit_xs, fit_ys, color=config["color_fit"], alpha=0.7, linewidth=1
2941
- )[0]
2942
- self.__class__.fit_vline = self.ax.axvline(
2943
- x=xmiddle, color=config["color_fit"], ls="--", alpha=1, linewidth=1
2944
- )
2973
+ with matplotlib_lock:
2974
+ self.__class__.fit_curve = self.ax.plot(
2975
+ fit_xs, fit_ys, color=config["color_fit"], alpha=0.7, linewidth=1
2976
+ )[0]
2977
+ self.__class__.fit_vline = self.ax.axvline(
2978
+ x=xmiddle, color=config["color_fit"], ls="--", alpha=1, linewidth=1
2979
+ )
2945
2980
 
2946
2981
  # Create assignment object
2947
2982
  new_assignment = {
@@ -2990,8 +3025,8 @@ class LWPAx:
2990
3025
  xmiddle, xuncert = fit_results["xmiddle"], fit_results["xuncert"]
2991
3026
  fit_xs, fit_ys = fit_results["fit_xs"], fit_results["fit_ys"]
2992
3027
  except Exception as E:
2993
- self.fitcurve = None
2994
- self.fitline = None
3028
+ self.fit_curve = None
3029
+ self.fit_line = None
2995
3030
  notify_error.emit(
2996
3031
  f"The fitting failed with the following error message : {str(E)}"
2997
3032
  )
@@ -3324,7 +3359,7 @@ class LWPWidget(QGroupBox):
3324
3359
 
3325
3360
  for i_col in range(n_widgets):
3326
3361
  thread.earlyreturn()
3327
- if i_col > n_cols:
3362
+ if i_col >= n_cols:
3328
3363
  continue
3329
3364
 
3330
3365
  refwidget = tab_widget.widget(i_col)
@@ -3390,24 +3425,28 @@ class LWPWidget(QGroupBox):
3390
3425
  thread.earlyreturn()
3391
3426
 
3392
3427
  # Set the correct values to the LWPAxes
3393
- if self.lwpaxes.shape != (n_rows, n_cols):
3394
- notify_error.emit("Shape of LWPAxes is out of sync with requested values.")
3395
- return
3428
+ # The lock makes sure that the axes are not replaced while they are updated
3429
+ with matplotlib_lock:
3430
+ if self.lwpaxes.shape != (n_rows, n_cols):
3431
+ notify_error.emit(
3432
+ "Shape of LWPAxes is out of sync with requested values."
3433
+ )
3434
+ return
3396
3435
 
3397
- for i_row in range(n_rows):
3398
- for i_col in range(n_cols):
3399
- ax = self.lwpaxes[i_row, i_col]
3400
- ax.ref_position = positions[i_row, i_col]
3401
- ax.xrange = (xmins[i_row, i_col], xmaxs[i_row, i_col])
3402
- ax.qns = qns[i_row, i_col]
3403
- ax.indices = {
3404
- label: (
3405
- min_indices[label][i_row, i_col],
3406
- max_indices[label][i_row, i_col],
3407
- )
3408
- for label in ("exp", "cat", "lin")
3409
- }
3410
- ax.update()
3436
+ for i_row in range(n_rows):
3437
+ for i_col in range(n_cols):
3438
+ ax = self.lwpaxes[i_row, i_col]
3439
+ ax.ref_position = positions[i_row, i_col]
3440
+ ax.xrange = (xmins[i_row, i_col], xmaxs[i_row, i_col])
3441
+ ax.qns = qns[i_row, i_col]
3442
+ ax.indices = {
3443
+ label: (
3444
+ min_indices[label][i_row, i_col],
3445
+ max_indices[label][i_row, i_col],
3446
+ )
3447
+ for label in ("exp", "cat", "lin")
3448
+ }
3449
+ ax.update()
3411
3450
 
3412
3451
  def get_current_ax(self):
3413
3452
  shape = self.lwpaxes.shape
@@ -7748,28 +7787,30 @@ class ResidualsWindow(EQDockWidget):
7748
7787
  colors = df["color"].to_numpy()
7749
7788
  tuples = list(zip(xs, ys))
7750
7789
  tuples = tuples if len(tuples) != 0 else [[None, None]]
7751
- self.points.set_offsets(tuples)
7752
- self.points.set_color(colors)
7753
- if len(xs) and config["residuals_autoscale"]:
7754
- xmin, xmax = np.min(xs), np.max(xs)
7755
- if xmin == xmax:
7756
- xmin -= 1
7757
- xmax += 1
7758
- self.ax.set_xlim(
7759
- [
7760
- xmin - 0.02 * (xmax - xmin),
7761
- xmax + 0.02 * (xmax - xmin),
7762
- ]
7763
- )
7764
- ymin, ymax = np.min(ys), np.max(ys)
7765
- if ymin == ymax:
7766
- ymin -= 1
7767
- ymax += 1
7768
- y_range = [ymin, ymax]
7769
- self.ax.set_ylim(
7770
- y_range[0] - config["plot_ymargin"] * (y_range[1] - y_range[0]),
7771
- y_range[1] + config["plot_ymargin"] * (y_range[1] - y_range[0]),
7772
- )
7790
+ with matplotlib_lock:
7791
+ self.points.set_offsets(tuples)
7792
+ self.points.set_color(colors)
7793
+ if len(xs) and config["residuals_autoscale"]:
7794
+ xmin, xmax = np.min(xs), np.max(xs)
7795
+ if xmin == xmax:
7796
+ xmin -= 1
7797
+ xmax += 1
7798
+ self.ax.set_xlim(
7799
+ [
7800
+ xmin - 0.02 * (xmax - xmin),
7801
+ xmax + 0.02 * (xmax - xmin),
7802
+ ]
7803
+ )
7804
+ ymin, ymax = np.min(ys), np.max(ys)
7805
+ if ymin == ymax:
7806
+ ymin -= 1
7807
+ ymax += 1
7808
+ y_range = [ymin, ymax]
7809
+ self.ax.set_ylim(
7810
+ y_range[0] - config["plot_ymargin"] * (y_range[1] - y_range[0]),
7811
+ y_range[1] + config["plot_ymargin"] * (y_range[1] - y_range[0]),
7812
+ )
7813
+
7773
7814
  notify_info.emit("<br/>".join(message))
7774
7815
  except Exception:
7775
7816
  notify_warning.emit("There was an error in your Residuals window input")
@@ -7905,7 +7946,6 @@ class BlendedLinesWindow(EQDockWidget):
7905
7946
  [], [], color=config["blendedlines_color_total"]
7906
7947
  )[0]
7907
7948
  self.plot_parts = []
7908
- self.plot_parts_lock = threading.Lock()
7909
7949
  self.update_plot_requested.connect(super().update_plot)
7910
7950
 
7911
7951
  def update_plot(self):
@@ -8046,7 +8086,7 @@ class BlendedLinesWindow(EQDockWidget):
8046
8086
  x, profile, derivative, 0, *args, fixedwidth=fixedwidth
8047
8087
  )
8048
8088
 
8049
- with plot_widget.plot_parts_lock:
8089
+ with matplotlib_lock:
8050
8090
  for part in plot_widget.plot_parts:
8051
8091
  part.remove()
8052
8092
  plot_widget.plot_parts = []
@@ -8146,8 +8186,9 @@ class BlendedLinesWindow(EQDockWidget):
8146
8186
 
8147
8187
  ax = plot_widget.ax
8148
8188
 
8149
- plot_widget.fit_line.set_data(res_xs, res_ys)
8150
- plot_widget.fit_line.set_color(config["blendedlines_color_total"])
8189
+ with matplotlib_lock:
8190
+ plot_widget.fit_line.set_data(res_xs, res_ys)
8191
+ plot_widget.fit_line.set_color(config["blendedlines_color_total"])
8151
8192
 
8152
8193
  opt_param = []
8153
8194
  err_param = []
@@ -8171,7 +8212,7 @@ class BlendedLinesWindow(EQDockWidget):
8171
8212
  tmp_ys = fitfunction_no_baseline(res_xs, *tmp_params)
8172
8213
  tmp_ys += exp_mean
8173
8214
 
8174
- with plot_widget.plot_parts_lock:
8215
+ with matplotlib_lock:
8175
8216
  plot_widget.plot_parts.append(
8176
8217
  ax.plot(
8177
8218
  res_xs,
@@ -8184,7 +8225,7 @@ class BlendedLinesWindow(EQDockWidget):
8184
8225
  opt_param.append(tmp_params)
8185
8226
  err_param.append(tmp_errors)
8186
8227
 
8187
- with plot_widget.plot_parts_lock:
8228
+ with matplotlib_lock:
8188
8229
  plot_widget.plot_parts.append(
8189
8230
  ax.scatter(
8190
8231
  [x[0] for x in opt_param],
@@ -8195,7 +8236,7 @@ class BlendedLinesWindow(EQDockWidget):
8195
8236
 
8196
8237
  if polynomrank > 0 and config["blendedlines_showbaseline"]:
8197
8238
  baseline_args = popt[-polynomrank:]
8198
- with plot_widget.plot_parts_lock:
8239
+ with matplotlib_lock:
8199
8240
  plot_widget.plot_parts.append(
8200
8241
  ax.plot(
8201
8242
  res_xs,
@@ -8234,7 +8275,7 @@ class BlendedLinesWindow(EQDockWidget):
8234
8275
 
8235
8276
  peaks = self.peaks.copy()
8236
8277
  fixedwidth = config["blendedlines_fixedwidth"]
8237
- with plot_widget.plot_parts_lock:
8278
+ with matplotlib_lock:
8238
8279
  for part in plot_widget.plot_parts:
8239
8280
  part.remove()
8240
8281
  plot_widget.plot_parts = []
@@ -8407,7 +8448,7 @@ class BlendedLinesWindow(EQDockWidget):
8407
8448
 
8408
8449
  idx += delta_idx
8409
8450
 
8410
- with plot_widget.plot_parts_lock:
8451
+ with matplotlib_lock:
8411
8452
  plot_widget.plot_parts.append(
8412
8453
  ax.scatter(
8413
8454
  [x[0] for x in opt_param],
@@ -8436,8 +8477,9 @@ class BlendedLinesWindow(EQDockWidget):
8436
8477
 
8437
8478
  thread.earlyreturn()
8438
8479
 
8439
- plot_widget.fit_line.set_data(res_xs, res_ys)
8440
- plot_widget.fit_line.set_color(config["blendedlines_color_total"])
8480
+ with matplotlib_lock:
8481
+ plot_widget.fit_line.set_data(res_xs, res_ys)
8482
+ plot_widget.fit_line.set_color(config["blendedlines_color_total"])
8441
8483
 
8442
8484
  self.fill_table_requested.emit()
8443
8485
  self.set_indicator_text.emit("Ready")
@@ -9167,28 +9209,29 @@ class EnergyLevelsWindow(EQDockWidget):
9167
9209
  colors = df["color"].to_numpy()
9168
9210
  tuples = list(zip(xs, ys))
9169
9211
  tuples = tuples if len(tuples) != 0 else [[None, None]]
9170
- self.points.set_offsets(tuples)
9171
- self.points.set_color(colors)
9172
- if len(xs) and config["energylevels_autoscale"]:
9173
- xmin, xmax = np.min(xs), np.max(xs)
9174
- if xmin == xmax:
9175
- xmin -= 1
9176
- xmax += 1
9177
- self.ax.set_xlim(
9178
- [
9179
- xmin - 0.02 * (xmax - xmin),
9180
- xmax + 0.02 * (xmax - xmin),
9181
- ]
9182
- )
9183
- ymin, ymax = np.min(ys), np.max(ys)
9184
- if ymin == ymax:
9185
- ymin -= 1
9186
- ymax += 1
9187
- y_range = [ymin, ymax]
9188
- self.ax.set_ylim(
9189
- y_range[0] - config["plot_ymargin"] * (y_range[1] - y_range[0]),
9190
- y_range[1] + config["plot_ymargin"] * (y_range[1] - y_range[0]),
9191
- )
9212
+ with matplotlib_lock:
9213
+ self.points.set_offsets(tuples)
9214
+ self.points.set_color(colors)
9215
+ if len(xs) and config["energylevels_autoscale"]:
9216
+ xmin, xmax = np.min(xs), np.max(xs)
9217
+ if xmin == xmax:
9218
+ xmin -= 1
9219
+ xmax += 1
9220
+ self.ax.set_xlim(
9221
+ [
9222
+ xmin - 0.02 * (xmax - xmin),
9223
+ xmax + 0.02 * (xmax - xmin),
9224
+ ]
9225
+ )
9226
+ ymin, ymax = np.min(ys), np.max(ys)
9227
+ if ymin == ymax:
9228
+ ymin -= 1
9229
+ ymax += 1
9230
+ y_range = [ymin, ymax]
9231
+ self.ax.set_ylim(
9232
+ y_range[0] - config["plot_ymargin"] * (y_range[1] - y_range[0]),
9233
+ y_range[1] + config["plot_ymargin"] * (y_range[1] - y_range[0]),
9234
+ )
9192
9235
  except Exception:
9193
9236
  notify_error.emit("There was an error in your Energy Levels window inputs")
9194
9237
  raise
@@ -9267,6 +9310,7 @@ class PeakfinderWindow(EQDockWidget):
9267
9310
  [], [], color=config["peakfinder_peakcolor"], marker="*"
9268
9311
  )
9269
9312
 
9313
+ @lock_d(matplotlib_lock)
9270
9314
  def update_plot(self):
9271
9315
  xmin, xmax = self.xrange
9272
9316
  tuples = list(filter(lambda x: xmin < x[0] < xmax, self.parent.peaks))
@@ -9291,7 +9335,7 @@ class PeakfinderWindow(EQDockWidget):
9291
9335
  layout.addLayout(tmp_layout)
9292
9336
 
9293
9337
  self.run_button = QQ(
9294
- QPushButton, text="Run Peakfinder", change=lambda: self.find_peaks()
9338
+ QPushButton, text="Run Peakfinder", change=lambda: self.on_run_clicked()
9295
9339
  )
9296
9340
  tmp_layout.addWidget(self.run_button)
9297
9341
  tmp_layout.addWidget(
@@ -9415,11 +9459,15 @@ class PeakfinderWindow(EQDockWidget):
9415
9459
  config["peakfinder_kwargs"] = kwargs
9416
9460
  return kwargs.copy()
9417
9461
 
9462
+ def on_run_clicked(self):
9463
+ self.run_button.setEnabled(False)
9464
+ kwargs = self.get_kwargs()
9465
+ self.find_peaks(kwargs)
9466
+
9418
9467
  @QThread.threaded_d
9419
9468
  @peak_locked
9420
- def find_peaks(self, thread=None):
9469
+ def find_peaks(self, kwargs, thread=None):
9421
9470
  self.peakfinding_started.emit()
9422
- kwargs = self.get_kwargs()
9423
9471
 
9424
9472
  if "frequency" in kwargs:
9425
9473
  val = kwargs["frequency"]
@@ -10691,6 +10739,7 @@ class ASAPAx(LWPAx):
10691
10739
  self.exp_coll.set(segments=segs, color=color)
10692
10740
 
10693
10741
  @drawplot_decorator.d
10742
+ @lock_d(matplotlib_lock)
10694
10743
  def update(self):
10695
10744
  ax = self.ax
10696
10745
 
@@ -10860,12 +10909,13 @@ class ASAPAx(LWPAx):
10860
10909
  return
10861
10910
 
10862
10911
  # Delete artists highlighting previous fit
10863
- if self.__class__.fit_vline is not None:
10864
- self.__class__.fit_vline.remove()
10865
- self.__class__.fit_vline = None
10866
- if self.__class__.fit_curve is not None:
10867
- self.__class__.fit_curve.remove()
10868
- self.__class__.fit_curve = None
10912
+ with matplotlib_lock:
10913
+ if self.__class__.fit_vline is not None:
10914
+ self.__class__.fit_vline.remove()
10915
+ self.__class__.fit_vline = None
10916
+ if self.__class__.fit_curve is not None:
10917
+ self.__class__.fit_curve.remove()
10918
+ self.__class__.fit_curve = None
10869
10919
 
10870
10920
  if onclick:
10871
10921
  xmiddle = xmin
@@ -10874,14 +10924,16 @@ class ASAPAx(LWPAx):
10874
10924
  else:
10875
10925
  # Fit the data
10876
10926
  xmiddle, xuncert, fit_xs, fit_ys = self.fit_peak(xmin, xmax)
10877
- self.__class__.fit_curve = self.ax.plot(
10878
- fit_xs, fit_ys, color=config["color_fit"], alpha=0.7, linewidth=1
10879
- )[0]
10927
+ with matplotlib_lock:
10928
+ self.__class__.fit_curve = self.ax.plot(
10929
+ fit_xs, fit_ys, color=config["color_fit"], alpha=0.7, linewidth=1
10930
+ )[0]
10880
10931
 
10881
10932
  # Highlight fit in plot
10882
- self.__class__.fit_vline = self.ax.axvline(
10883
- x=xmiddle, color=config["color_fit"], ls="--", alpha=1, linewidth=1
10884
- )
10933
+ with matplotlib_lock:
10934
+ self.__class__.fit_vline = self.ax.axvline(
10935
+ x=xmiddle, color=config["color_fit"], ls="--", alpha=1, linewidth=1
10936
+ )
10885
10937
 
10886
10938
  # Emit signal for detail viewer
10887
10939
  self.x_fit = xmiddle
@@ -10978,8 +11030,8 @@ class ASAPAx(LWPAx):
10978
11030
  fit_xs, fit_ys = fit_results["fit_xs"], fit_results["fit_ys"]
10979
11031
 
10980
11032
  except Exception as E:
10981
- self.fitcurve = None
10982
- self.fitline = None
11033
+ self.fit_curve = None
11034
+ self.fit_line = None
10983
11035
  notify_error.emit(
10984
11036
  f"The fitting failed with the following error message : {str(E)}"
10985
11037
  )
@@ -11333,12 +11385,13 @@ class ASAPWidget(LWPWidget):
11333
11385
  @status_d
11334
11386
  @drawplot_decorator.d
11335
11387
  def calc_correlation_plots(self, thread=None):
11336
- if self._ax_class.fit_vline is not None:
11337
- self._ax_class.fit_vline.remove()
11338
- self._ax_class.fit_vline = None
11339
- if self._ax_class.fit_curve is not None:
11340
- self._ax_class.fit_curve.remove()
11341
- self._ax_class.fit_curve = None
11388
+ with matplotlib_lock:
11389
+ if self._ax_class.fit_vline is not None:
11390
+ self._ax_class.fit_vline.remove()
11391
+ self._ax_class.fit_vline = None
11392
+ if self._ax_class.fit_curve is not None:
11393
+ self._ax_class.fit_curve.remove()
11394
+ self._ax_class.fit_curve = None
11342
11395
 
11343
11396
  if not hasattr(ASAPSettingsWindow, "instance"):
11344
11397
  return
@@ -11368,7 +11421,7 @@ class ASAPWidget(LWPWidget):
11368
11421
 
11369
11422
  for i_col in range(n_widgets):
11370
11423
  thread.earlyreturn()
11371
- if i_col > n_cols:
11424
+ if i_col >= n_cols:
11372
11425
  continue
11373
11426
 
11374
11427
  refwidget = tab_widget.widget(i_col)
@@ -11956,12 +12009,15 @@ class ASAPSquaredWindow(EQDockWidget):
11956
12009
  self.noq = config["series_qns"]
11957
12010
  assignments = []
11958
12011
 
11959
- for ax in self.fig.get_axes():
11960
- self.fig.delaxes(ax)
11961
- self.ax = self.fig.subplots()
11962
- self.span_selector = matplotlib.widgets.SpanSelector(
11963
- self.ax, self.on_range, "horizontal", useblit=True
11964
- )
12012
+ with matplotlib_lock:
12013
+ for ax in self.fig.get_axes():
12014
+ self.fig.delaxes(ax)
12015
+ self.fit_vline = None
12016
+ self.fit_curve = None
12017
+ self.ax = self.fig.subplots()
12018
+ self.span_selector = matplotlib.widgets.SpanSelector(
12019
+ self.ax, self.on_range, "horizontal", useblit=True
12020
+ )
11965
12021
 
11966
12022
  width = config["asap_squaredwidth"] or config["plot_width"]
11967
12023
  stepsize = config["asap_squaredstepsize"] or config["asap_stepsize"]
@@ -12029,9 +12085,10 @@ class ASAPSquaredWindow(EQDockWidget):
12029
12085
  tot_ys /= ys_max
12030
12086
  log_y_max += np.log10(ys_max)
12031
12087
 
12032
- self.ax.plot(rel_xs, tot_ys, color=config["color_exp"])
12033
- if config["asap_squaredplotlog"]:
12034
- self.ax.set_yscale("log")
12088
+ with matplotlib_lock:
12089
+ self.ax.plot(rel_xs, tot_ys, color=config["color_exp"])
12090
+ if config["asap_squaredplotlog"]:
12091
+ self.ax.set_yscale("log")
12035
12092
 
12036
12093
  self.data = np.vstack((rel_xs, tot_ys)).T
12037
12094
  self.drawplot.emit()
@@ -12102,12 +12159,13 @@ class ASAPSquaredWindow(EQDockWidget):
12102
12159
  @status_d
12103
12160
  def fit_data(self, xmin, xmax, thread=None):
12104
12161
  # Delete artists highlighting previous fit
12105
- if self.fit_vline is not None:
12106
- self.fit_vline.remove()
12107
- self.fit_vline = None
12108
- if self.fit_curve is not None:
12109
- self.fit_curve.remove()
12110
- self.fit_curve = None
12162
+ with matplotlib_lock:
12163
+ if self.fit_vline is not None:
12164
+ self.fit_vline.remove()
12165
+ self.fit_vline = None
12166
+ if self.fit_curve is not None:
12167
+ self.fit_curve.remove()
12168
+ self.fit_curve = None
12111
12169
 
12112
12170
  if self.data is None:
12113
12171
  notify_warning.emit("No cross-correlation data available.")
@@ -12140,8 +12198,8 @@ class ASAPSquaredWindow(EQDockWidget):
12140
12198
  f"The following parameters were determined: \n{popt=}\n{perr=}"
12141
12199
  )
12142
12200
  except Exception as E:
12143
- self.fitcurve = None
12144
- self.fitline = None
12201
+ self.fit_curve = None
12202
+ self.fit_vline = None
12145
12203
  notify_error.emit(
12146
12204
  f"The fitting failed with the following error message : {str(E)}"
12147
12205
  )
@@ -12151,12 +12209,13 @@ class ASAPSquaredWindow(EQDockWidget):
12151
12209
  self.clipboard_requested.emit(str(xmiddle))
12152
12210
 
12153
12211
  # Highlight fit in plot
12154
- self.fit_curve = self.ax.plot(
12155
- fit_xs, fit_ys, color=config["color_fit"], alpha=0.7, linewidth=1
12156
- )[0]
12157
- self.fit_vline = self.ax.axvline(
12158
- x=xmiddle, color=config["color_fit"], ls="--", alpha=1, linewidth=1
12159
- )
12212
+ with matplotlib_lock:
12213
+ self.fit_curve = self.ax.plot(
12214
+ fit_xs, fit_ys, color=config["color_fit"], alpha=0.7, linewidth=1
12215
+ )[0]
12216
+ self.fit_vline = self.ax.axvline(
12217
+ x=xmiddle, color=config["color_fit"], ls="--", alpha=1, linewidth=1
12218
+ )
12160
12219
  self.xmiddle = xmiddle
12161
12220
  self.xuncert = xuncert
12162
12221
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llwp
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: LLWP is a fast, efficient and easy solution for exploring and assigning spectra - relying on Loomis-Wood plots.
5
5
  Author-email: Luis Bonah <bonah@ph1.uni-koeln.de>
6
6
  License-Expression: GPL-3.0-only
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "llwp"
7
- version = "2.1.1"
7
+ version = "2.1.2"
8
8
  authors = [
9
9
  { name="Luis Bonah", email="bonah@ph1.uni-koeln.de" },
10
10
  ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes