orm-iris 1.0.0__py3-none-any.whl
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.
- iris/__init__.py +86 -0
- iris/calibration/__init__.py +0 -0
- iris/calibration/calibration_generator.py +569 -0
- iris/calibration/test_calibrator_wavelength.py +123 -0
- iris/controllers/__init__.py +369 -0
- iris/controllers/camera_controller_dummy.py +89 -0
- iris/controllers/camera_controller_thorlabs_color.py +264 -0
- iris/controllers/camera_controller_thorlabs_mono.py +248 -0
- iris/controllers/camera_controller_webcam.py +119 -0
- iris/controllers/class_camera_controller.py +170 -0
- iris/controllers/class_spectrometer_controller.py +140 -0
- iris/controllers/class_xy_stage_controller.py +375 -0
- iris/controllers/class_z_stage_controller.py +270 -0
- iris/controllers/mcm301_wrapper.py +76 -0
- iris/controllers/oceandirect_wrapper.py +83 -0
- iris/controllers/raman_spectrometer_controller_Andor_dll.py +1280 -0
- iris/controllers/raman_spectrometer_controller_Andor_pylablib.py +438 -0
- iris/controllers/raman_spectrometer_controller_PI_dll.py +601 -0
- iris/controllers/raman_spectrometer_controller_PI_pylablib.py +419 -0
- iris/controllers/raman_spectrometer_controller_PI_trial.py +535 -0
- iris/controllers/raman_spectrometer_controller_QEPro.py +533 -0
- iris/controllers/raman_spectrometer_controller_WasatchEnlighten.py +237 -0
- iris/controllers/raman_spectrometer_controller_dummy.py +273 -0
- iris/controllers/xy_stage_controller_PI.py +498 -0
- iris/controllers/xy_stage_controller_PI_dll.py +884 -0
- iris/controllers/xy_stage_controller_dummy.py +309 -0
- iris/controllers/xy_stage_controller_m30xy.py +780 -0
- iris/controllers/xy_stage_controller_zaber.py +597 -0
- iris/controllers/z_stage_controller_dummy.py +261 -0
- iris/controllers/z_stage_controller_mcm301.py +408 -0
- iris/controllers/z_stage_controller_pfm450.py +390 -0
- iris/controllers/z_stage_controller_z825b.py +515 -0
- iris/data/__init__.py +86 -0
- iris/data/calibration_objective.py +672 -0
- iris/data/measurement_Raman.py +798 -0
- iris/data/measurement_RamanMap.py +2402 -0
- iris/data/measurement_coordinates.py +370 -0
- iris/data/measurement_image.py +1439 -0
- iris/gui/__init__.py +260 -0
- iris/gui/dataHub_MeaImg.py +704 -0
- iris/gui/dataHub_MeaRMap.py +1254 -0
- iris/gui/hilvl_Brightfield.py +214 -0
- iris/gui/hilvl_Raman.py +1469 -0
- iris/gui/hilvl_coorGen.py +380 -0
- iris/gui/image_calibration/Canvas_ROIdefinition.py +359 -0
- iris/gui/image_calibration/__init__.py +0 -0
- iris/gui/image_calibration/objective_calibration.py +1100 -0
- iris/gui/image_calibration/plotter_heatmap_overlay.py +421 -0
- iris/gui/motion_video.py +1857 -0
- iris/gui/raman.py +1222 -0
- iris/gui/shortcut_handler.py +246 -0
- iris/gui/submodules/__init__.py +0 -0
- iris/gui/submodules/heatmap_plotter_MeaRMap.py +836 -0
- iris/gui/submodules/image_tiling.py +544 -0
- iris/gui/submodules/mappingCoordinatesTreeview.py +337 -0
- iris/gui/submodules/meaCoor_generator/__init__.py +0 -0
- iris/gui/submodules/meaCoor_generator/line_zScan.py +171 -0
- iris/gui/submodules/meaCoor_generator/points_image.py +347 -0
- iris/gui/submodules/meaCoor_generator/rectangle_aroundCentre.py +196 -0
- iris/gui/submodules/meaCoor_generator/rectangle_endToEnd.py +309 -0
- iris/gui/submodules/meaCoor_generator/rectangle_image.py +514 -0
- iris/gui/submodules/meaCoor_generator/rectangle_video.py +505 -0
- iris/gui/submodules/meaCoor_generator/singlePoint_zScan.py +209 -0
- iris/gui/submodules/meaCoor_generator/ssfrm_tilemthd1_rect_around.py +294 -0
- iris/gui/submodules/meaCoor_modifier/__init__.py +0 -0
- iris/gui/submodules/meaCoor_modifier/ellipsify.py +219 -0
- iris/gui/submodules/meaCoor_modifier/every_z.py +440 -0
- iris/gui/submodules/meaCoor_modifier/gridify.py +1065 -0
- iris/gui/submodules/meaCoor_modifier/multitranslateXYZ.py +218 -0
- iris/gui/submodules/meaCoor_modifier/topology_visualiser.py +234 -0
- iris/gui/submodules/meaCoor_modifier/translateXYZ.py +501 -0
- iris/gui/submodules/meaCoor_modifier/zInterpolate.py +343 -0
- iris/gui/submodules/peakfinder_plotter_MeaRaman.py +313 -0
- iris/gui/timestamp_coorshift.py +123 -0
- iris/multiprocessing/__init__.py +46 -0
- iris/multiprocessing/basemanager.py +11 -0
- iris/multiprocessing/dataStreamer_Raman.py +635 -0
- iris/multiprocessing/dataStreamer_StageCam.py +758 -0
- iris/resources/__init__.py +0 -0
- iris/resources/coordinate_generators/__init__.py +0 -0
- iris/resources/coordinate_generators/convert_2Dto3D_ui.py +131 -0
- iris/resources/coordinate_generators/point_image_ui.py +150 -0
- iris/resources/coordinate_generators/point_zScanLinear_ui.py +157 -0
- iris/resources/coordinate_generators/rect_aroundcentre_ui.py +286 -0
- iris/resources/coordinate_generators/rect_image_ui.py +266 -0
- iris/resources/coordinate_generators/rect_startend_ui.py +265 -0
- iris/resources/coordinate_generators/rect_video_ui.py +254 -0
- iris/resources/coordinate_modifiers/__init__.py +0 -0
- iris/resources/coordinate_modifiers/ellipsify_ui.py +76 -0
- iris/resources/coordinate_modifiers/every_z_ui.py +211 -0
- iris/resources/coordinate_modifiers/gridify_setup_finetuning_ui.py +193 -0
- iris/resources/coordinate_modifiers/gridify_setup_naming_ui.py +163 -0
- iris/resources/coordinate_modifiers/gridify_setup_ui.py +486 -0
- iris/resources/coordinate_modifiers/multiTranslatorXYZ_ui.py +122 -0
- iris/resources/coordinate_modifiers/topology_visuliser_ui.py +86 -0
- iris/resources/coordinate_modifiers/translator_xyz_ui.py +341 -0
- iris/resources/coordinate_modifiers/zInterpolate_ui.py +118 -0
- iris/resources/dataHubPlus_Raman_ui.py +60 -0
- iris/resources/dataHub_Raman_ui.py +120 -0
- iris/resources/dataHub_coor_ui.py +79 -0
- iris/resources/dataHub_image_ui.py +109 -0
- iris/resources/font/__init__.py +0 -0
- iris/resources/heatmap_plotter_overlay_ui.py +74 -0
- iris/resources/heatmap_plotter_ui.py +262 -0
- iris/resources/hilvl_Raman_ui.py +276 -0
- iris/resources/hilvl_brightfield_ui.py +72 -0
- iris/resources/hilvl_coorGen_coorMod_ui.py +52 -0
- iris/resources/hilvl_coorGen_ui.py +127 -0
- iris/resources/main_analyser_ui.py +87 -0
- iris/resources/main_controller_ui.py +141 -0
- iris/resources/motion_video/__init__.py +0 -0
- iris/resources/motion_video/brightfieldcontrol_ui.py +144 -0
- iris/resources/motion_video/stagecontrol_ui.py +491 -0
- iris/resources/objective_calibration_controls_ui.py +215 -0
- iris/resources/objective_calibration_main_ui.py +107 -0
- iris/resources/objectives_ui.py +68 -0
- iris/resources/raman_ui.py +313 -0
- iris/resources/spectra_peak_finder_ui.py +185 -0
- iris/resources/spectrometer_calibration_ui.py +129 -0
- iris/resources/tiling_method_control_ui.py +139 -0
- iris/resources/tiling_method_ui.py +102 -0
- iris/utils/__init__.py +0 -0
- iris/utils/general.py +420 -0
- iris/utils/gridify.py +111 -0
- orm_iris-1.0.0.dist-info/METADATA +214 -0
- orm_iris-1.0.0.dist-info/RECORD +129 -0
- orm_iris-1.0.0.dist-info/WHEEL +5 -0
- orm_iris-1.0.0.dist-info/licenses/LICENSE +674 -0
- orm_iris-1.0.0.dist-info/top_level.txt +1 -0
iris/__init__.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from .utils.general import read_update_config_file_section
|
|
5
|
+
|
|
6
|
+
dict_lib_default = {
|
|
7
|
+
'spectrometer_calibration_path': '',
|
|
8
|
+
'objective_calibration_directory': './calibrations/objectives/',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dict_lib_comments = {
|
|
12
|
+
'spectrometer_calibration_path': 'Path to the spectrometer calibration file',
|
|
13
|
+
'objective_calibration_directory': 'Path to the objective calibration directory',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
dict_lib_read = read_update_config_file_section(
|
|
17
|
+
dict_controllers_default=dict_lib_default,
|
|
18
|
+
dict_controllers_comments=dict_lib_comments,
|
|
19
|
+
section='LIBRARY',
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
for key in dict_lib_default.keys():
|
|
23
|
+
if key.endswith('_path'):
|
|
24
|
+
if dict_lib_read[key] == '': continue
|
|
25
|
+
assert os.path.isfile(dict_lib_read[key]),\
|
|
26
|
+
f"Error: {key} directory inserted in the config.ini file is invalid.\
|
|
27
|
+
Please check the directory path and try again."
|
|
28
|
+
|
|
29
|
+
class LibraryConfigEnum(Enum):
|
|
30
|
+
"""
|
|
31
|
+
Enum class for the app-wide configuration parameters.
|
|
32
|
+
"""
|
|
33
|
+
SPECTROMETER_CALIBRATION_PATH = dict_lib_read['spectrometer_calibration_path']
|
|
34
|
+
OBJECTIVE_CALIBRATION_DIR = dict_lib_read['objective_calibration_directory']
|
|
35
|
+
|
|
36
|
+
SPECTROMETER_CALIBRATION_DIR_DEFAULT = './calibrations/spectrometers/'
|
|
37
|
+
|
|
38
|
+
############################################################################################################
|
|
39
|
+
# >>>>>> Data analysis specific parameters <<<<<<
|
|
40
|
+
############################################################################################################
|
|
41
|
+
dict_dataAnalysis_default = {
|
|
42
|
+
# > Basic data analysis parameters <
|
|
43
|
+
'laser_wavelength_nm': float(785),
|
|
44
|
+
'similarity_threshold': 0.1,
|
|
45
|
+
'laser_power_milliwatt': float(50),
|
|
46
|
+
# > Labels <
|
|
47
|
+
'wavelength_label': 'Wavelength [nm]', # Label for the wavelength dataframe column and plot axis
|
|
48
|
+
'intensity_label': 'Intensity [a.u.]', # Label for the intensity dataframe column and plot axis
|
|
49
|
+
'raman_shift_label': 'Raman Shift [cm^-1]', # Label for the Raman shift plot axis
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
dict_dataAnalysis_comments = {
|
|
53
|
+
# > Basic data analysis parameters <
|
|
54
|
+
'laser_wavelength_nm': 'Default value for the laser excitation wavelength in [nm] metadata',
|
|
55
|
+
'similarity_threshold': 'Threshold for the similarity of the spectra for wavelength similarity check',
|
|
56
|
+
'laser_power_milliwatt': 'Default value for the laser power in [mW] metadata',
|
|
57
|
+
# > Labels <
|
|
58
|
+
'wavelength_label': 'Label for the wavelength dataframe column and plot axis',
|
|
59
|
+
'intensity_label': 'Label for the intensity dataframe column and plot axis',
|
|
60
|
+
'raman_shift_label': 'Label for the Raman shift plot axis',
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
dict_dataAnalysis_read = read_update_config_file_section(
|
|
64
|
+
dict_controllers_default=dict_dataAnalysis_default,
|
|
65
|
+
dict_controllers_comments=dict_dataAnalysis_comments,
|
|
66
|
+
section='DATA ANALYSIS',
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
class DataAnalysisConfigEnum(Enum):
|
|
70
|
+
"""
|
|
71
|
+
Enum class for the app-wide configuration parameters.
|
|
72
|
+
"""
|
|
73
|
+
# > Basic data analysis parameters <
|
|
74
|
+
LASER_WAVELENGTH_NM = dict_dataAnalysis_read['laser_wavelength_nm']
|
|
75
|
+
SIMILARITY_THRESHOLD = dict_dataAnalysis_read['similarity_threshold']
|
|
76
|
+
LASER_POWER_MILLIWATT = dict_dataAnalysis_read['laser_power_milliwatt']
|
|
77
|
+
# > Labels <
|
|
78
|
+
WAVELENGTH_LABEL = dict_dataAnalysis_read['wavelength_label']
|
|
79
|
+
INTENSITY_LABEL = dict_dataAnalysis_read['intensity_label']
|
|
80
|
+
RAMANSHIFT_LABEL = dict_dataAnalysis_read['raman_shift_label']
|
|
81
|
+
COORX_LABEL = 'coor_x'
|
|
82
|
+
COORY_LABEL = 'coor_y'
|
|
83
|
+
COORZ_LABEL = 'coor_z'
|
|
84
|
+
ID_TIMESTAMP_LABEL = 'timestamp'
|
|
85
|
+
LIST_MEA_LABEL = 'list_df'
|
|
86
|
+
AVE_MEA_LABEL = 'averaged_df'
|
|
File without changes
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This class generates the calibration for the spectrometer to map pixels to wavelengths,
|
|
3
|
+
and the intensity calibration of a Raman spectrometer
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
import threading
|
|
11
|
+
import multiprocessing as mp
|
|
12
|
+
from multiprocessing.managers import DictProxy
|
|
13
|
+
from multiprocessing.connection import Connection, Pipe
|
|
14
|
+
|
|
15
|
+
import PySide6.QtWidgets as qw
|
|
16
|
+
from PySide6.QtCore import Signal, Slot, QObject, QThread, QTimer
|
|
17
|
+
|
|
18
|
+
from scipy.optimize import curve_fit
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
import matplotlib
|
|
24
|
+
import matplotlib.pyplot as plt
|
|
25
|
+
from matplotlib.axes import Axes
|
|
26
|
+
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
|
|
27
|
+
matplotlib.use('Agg')
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
|
|
31
|
+
if __name__ == '__main__':
|
|
32
|
+
import sys
|
|
33
|
+
import os
|
|
34
|
+
libdir = os.path.abspath(r'.\iris')
|
|
35
|
+
sys.path.insert(0, os.path.dirname(libdir))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
from iris import LibraryConfigEnum, DataAnalysisConfigEnum
|
|
39
|
+
|
|
40
|
+
from iris.resources.spectrometer_calibration_ui import Ui_spectrometerCalibrator
|
|
41
|
+
|
|
42
|
+
# Make a calibration parameters class to standardise the calibration parameters to store the following info:
|
|
43
|
+
# wavelen_poly_coeffs, wavelen_list_measured, wavelen_list_reference, intensity_poly_coeffs, intensity_list_measured, intensity_list_reference
|
|
44
|
+
class CalibrationParams(dict):
|
|
45
|
+
def __init__(self):
|
|
46
|
+
super().__init__()
|
|
47
|
+
self['wavelen_poly_coeffs'] = (1,0)
|
|
48
|
+
self['wavelen_list_measured'] = []
|
|
49
|
+
self['wavelen_list_reference'] = []
|
|
50
|
+
self['intensity_poly_coeffs'] = (0,1)
|
|
51
|
+
self['intensity_list_pixel_idx'] = []
|
|
52
|
+
self['intensity_list_measured'] = []
|
|
53
|
+
self['intensity_list_reference'] = []
|
|
54
|
+
|
|
55
|
+
# Polynomial coefficients of the transfer function y = poly_coeffs[0]x^(len(poly_coeffs)-1) + poly_coeffs[1]x^(len(poly_coeffs)-2) + ... + poly_coeffs[-1]
|
|
56
|
+
# Note: the intensity list has to be in the order of the spectrometer pixel idx
|
|
57
|
+
|
|
58
|
+
class SpectrometerCalibrator():
|
|
59
|
+
"""
|
|
60
|
+
The backend for the wavelength calibration to be used in the RamanMeasurementHub
|
|
61
|
+
"""
|
|
62
|
+
def __init__(self, pipe_update: Connection, pipe_measurement: Connection):
|
|
63
|
+
self._cal_params = CalibrationParams()
|
|
64
|
+
|
|
65
|
+
# The table to map the measured wavelengths to the reference wavelengths
|
|
66
|
+
self._transTable_wv = {}
|
|
67
|
+
self._transTable_int = {}
|
|
68
|
+
|
|
69
|
+
# Pipe to receive the path to update the calibration parameters
|
|
70
|
+
self._pipe_update = pipe_update
|
|
71
|
+
self._pipe_mea = pipe_measurement
|
|
72
|
+
self._flg_isrunning = mp.Event()
|
|
73
|
+
self._thd_update = threading.Thread(target=self._auto_update)
|
|
74
|
+
self._thd_update.start()
|
|
75
|
+
self._thd_calibrate = threading.Thread(target=self._auto_calibrate)
|
|
76
|
+
self._thd_calibrate.start()
|
|
77
|
+
|
|
78
|
+
def terminate(self):
|
|
79
|
+
"""
|
|
80
|
+
Terminates the backend
|
|
81
|
+
"""
|
|
82
|
+
self._flg_isrunning.clear()
|
|
83
|
+
self._thd_update.join()
|
|
84
|
+
self._pipe_update.close()
|
|
85
|
+
|
|
86
|
+
def calibrate_measurement(self, measurement:pd.DataFrame):
|
|
87
|
+
"""
|
|
88
|
+
Calibrates the spectrometer measurement based on the calibration parameters
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
measurement (pd.DataFrame): The measurement data to be calibrated
|
|
92
|
+
"""
|
|
93
|
+
list_wavelength_raw = measurement[DataAnalysisConfigEnum.WAVELENGTH_LABEL.value].values
|
|
94
|
+
list_intensity_raw = measurement[DataAnalysisConfigEnum.INTENSITY_LABEL.value].values
|
|
95
|
+
list_wavelength_cal = [self.get_wavelength(wavelength_raw)\
|
|
96
|
+
for wavelength_raw in list_wavelength_raw]
|
|
97
|
+
list_intensity_cal = [self.get_intensity(wavelength_raw,intensity_raw)\
|
|
98
|
+
for wavelength_raw,intensity_raw in zip(list_wavelength_raw,list_intensity_raw)]
|
|
99
|
+
|
|
100
|
+
# Reconstruct the dataframe with the calibrated values
|
|
101
|
+
cal_spectrum = measurement.copy()
|
|
102
|
+
cal_spectrum[DataAnalysisConfigEnum.WAVELENGTH_LABEL.value] = list_wavelength_cal
|
|
103
|
+
cal_spectrum[DataAnalysisConfigEnum.INTENSITY_LABEL.value] = list_intensity_cal
|
|
104
|
+
|
|
105
|
+
return cal_spectrum
|
|
106
|
+
|
|
107
|
+
def _auto_calibrate(self):
|
|
108
|
+
"""
|
|
109
|
+
Automatically calibrates the spectrometer measurement
|
|
110
|
+
"""
|
|
111
|
+
self._flg_isrunning.set()
|
|
112
|
+
while self._flg_isrunning.is_set():
|
|
113
|
+
if self._pipe_mea.poll(timeout=1):
|
|
114
|
+
measurement = self._pipe_mea.recv()
|
|
115
|
+
ret = None
|
|
116
|
+
try: ret = self.calibrate_measurement(measurement)
|
|
117
|
+
except Exception as e: print('ERROR SpectrometerCalibrator._auto_calibrate: ',e)
|
|
118
|
+
finally: self._pipe_mea.send(ret)
|
|
119
|
+
|
|
120
|
+
def _auto_update(self):
|
|
121
|
+
"""
|
|
122
|
+
Automatically updates the calibration parameters
|
|
123
|
+
"""
|
|
124
|
+
self._flg_isrunning.set()
|
|
125
|
+
while self._flg_isrunning.is_set():
|
|
126
|
+
if self._pipe_update.poll(timeout=1):
|
|
127
|
+
recv = self._pipe_update.recv()
|
|
128
|
+
self._cal_params = recv
|
|
129
|
+
self._transTable_wv.clear()
|
|
130
|
+
self._transTable_int.clear()
|
|
131
|
+
time.sleep(0.5)
|
|
132
|
+
|
|
133
|
+
def get_wavelength(self,pixel_idx:float) -> float:
|
|
134
|
+
"""
|
|
135
|
+
Gets the wavelength based on the pixel index
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
pixel_idx (float): The pixel index
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
float: The wavelength
|
|
142
|
+
"""
|
|
143
|
+
assert isinstance(pixel_idx,(int,float)), 'ERROR SpectrometerCalibrator.get_wavelength: The pixel index must be a number'
|
|
144
|
+
pixel_req = '{:.3f}'.format(pixel_idx)
|
|
145
|
+
if pixel_req in self._transTable_wv.keys():
|
|
146
|
+
wavelength_cal = self._transTable_wv[pixel_req]
|
|
147
|
+
else:
|
|
148
|
+
wavelength_cal = np.polyval(self._cal_params['wavelen_poly_coeffs'],pixel_idx)
|
|
149
|
+
self._transTable_wv[pixel_req] = wavelength_cal
|
|
150
|
+
return wavelength_cal
|
|
151
|
+
|
|
152
|
+
def get_intensity(self,pixel_idx:float,intensity:float) -> float:
|
|
153
|
+
"""
|
|
154
|
+
Gets the intensity based on the pixel index and the measured intensity
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
pixel_idx (float): The pixel index
|
|
158
|
+
intensity (float): The measured intensity
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
float: The calibrated intensity
|
|
162
|
+
"""
|
|
163
|
+
assert isinstance(pixel_idx,(int,float)), 'ERROR SpectrometerCalibrator.get_intensity: The pixel index must be a number'
|
|
164
|
+
assert isinstance(intensity,(int,float)), 'ERROR SpectrometerCalibrator.get_intensity: The intensity must be a number'
|
|
165
|
+
pixel_req = '{:.3f}'.format(pixel_idx)
|
|
166
|
+
|
|
167
|
+
if pixel_req in self._transTable_int.keys():
|
|
168
|
+
cal_ratio = self._transTable_int[pixel_req]
|
|
169
|
+
else:
|
|
170
|
+
cal_ratio = np.polyval(self._cal_params['intensity_poly_coeffs'],pixel_idx)
|
|
171
|
+
self._transTable_int[pixel_req] = cal_ratio
|
|
172
|
+
|
|
173
|
+
intensity_cal = cal_ratio*intensity
|
|
174
|
+
return intensity_cal
|
|
175
|
+
|
|
176
|
+
class Wdg_SpectrometerCalibrationGenerator(Ui_spectrometerCalibrator, qw.QWidget):
|
|
177
|
+
"""
|
|
178
|
+
GUI for the spectrometer calibration generator
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
master (tk.Tk): The root window
|
|
182
|
+
pipe_update (Connection): The pipe to update the calibration parameters in the backend
|
|
183
|
+
dict_cal (DictCalibration): The calibration parameters dictionary
|
|
184
|
+
"""
|
|
185
|
+
def __init__(self, parent:qw.QWidget, pipe_update: Connection):
|
|
186
|
+
super().__init__(parent)
|
|
187
|
+
self.setupUi(self)
|
|
188
|
+
|
|
189
|
+
# >> General parameters <<
|
|
190
|
+
figsize_in = (3.5,3.5)
|
|
191
|
+
|
|
192
|
+
# >> Backend functions <<
|
|
193
|
+
# Pipe to update the calibration parameters in the backend
|
|
194
|
+
self._pipe_update = pipe_update
|
|
195
|
+
|
|
196
|
+
# >> Global calibration parameters and widgets <<
|
|
197
|
+
# Frontend params to temporarily store the calibration parameters
|
|
198
|
+
self._cal_params = CalibrationParams()
|
|
199
|
+
|
|
200
|
+
# Buttons to load, save, and calculate the calibration params
|
|
201
|
+
self.btnLoad.clicked.connect(self._load_calibration)
|
|
202
|
+
self.btnSave.clicked.connect(self._save_calibration)
|
|
203
|
+
|
|
204
|
+
# >> Wavelength calibration parameters and widgets <<
|
|
205
|
+
# Canvas to show the wavelength calibration transfer function
|
|
206
|
+
self._fig_wv, self._ax_wv = plt.subplots(1,1,figsize=figsize_in)
|
|
207
|
+
self._canvas_wv = FigureCanvas(figure=self._fig_wv)
|
|
208
|
+
self.lytPixelmapCanvas.addWidget(self._canvas_wv)
|
|
209
|
+
self._canvas_wv.draw_idle()
|
|
210
|
+
|
|
211
|
+
# Treeview to show the measured and reference wavelengths
|
|
212
|
+
self._tree = self.treePixelmap
|
|
213
|
+
self._tree.setColumnCount(2)
|
|
214
|
+
self._tree.setHeaderLabels(['Measured peak loc [pixel OR nm]','Reference peak loc [nm]'])
|
|
215
|
+
|
|
216
|
+
# Button to load the measured and reference wavelengths
|
|
217
|
+
self.btnLoadPixelmap.clicked.connect(self._load_wavelength)
|
|
218
|
+
|
|
219
|
+
# >> Intensity calibration parameters and widgets<<
|
|
220
|
+
# Canvas to show the intensity calibration transfer function
|
|
221
|
+
figsize_in_int = (figsize_in[0]*2,figsize_in[1])
|
|
222
|
+
self._fig_int, axes = plt.subplots(1,2,figsize=figsize_in_int)
|
|
223
|
+
self._ax_int_raw, self._ax_int_cal = axes
|
|
224
|
+
self._canvas_int = FigureCanvas(figure=self._fig_int)
|
|
225
|
+
self.lytIntensityCanvas.addWidget(self._canvas_int)
|
|
226
|
+
self._canvas_int.draw_idle()
|
|
227
|
+
|
|
228
|
+
# Button to load the measured and reference intensities
|
|
229
|
+
self.btnLoadIntensity.clicked.connect(self._load_intensity)
|
|
230
|
+
|
|
231
|
+
# >> Auto-load the calibration file from the last session <<
|
|
232
|
+
cal_filepath = LibraryConfigEnum.SPECTROMETER_CALIBRATION_PATH.value
|
|
233
|
+
if os.path.isfile(cal_filepath) and cal_filepath.endswith('.json'):
|
|
234
|
+
QTimer.singleShot(0, lambda: self._load_calibration(cal_filepath))
|
|
235
|
+
|
|
236
|
+
def _load_calibration(self,loadpath:str|None=None) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Loads the calibration file
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
loadpath (str|None): The path to the calibration file. If None, a file dialog will be opened
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
bool: True if the calibration file is loaded successfully, False otherwise
|
|
245
|
+
"""
|
|
246
|
+
if loadpath is None or not os.path.exists(loadpath) or not os.path.isfile(loadpath) or not loadpath.endswith('.json'):
|
|
247
|
+
init_file = LibraryConfigEnum.SPECTROMETER_CALIBRATION_PATH.value
|
|
248
|
+
init_dir = os.path.dirname(init_file) if os.path.exists(init_file) else ''
|
|
249
|
+
loadpath = qw.QFileDialog.getOpenFileName(
|
|
250
|
+
self,
|
|
251
|
+
'Load calibration file',
|
|
252
|
+
init_dir,
|
|
253
|
+
'JSON files (*.json)',
|
|
254
|
+
)[0]
|
|
255
|
+
if loadpath == '': return
|
|
256
|
+
|
|
257
|
+
with open(loadpath,'r') as f: dict_params = json.load(f)
|
|
258
|
+
try:
|
|
259
|
+
for key in dict_params:
|
|
260
|
+
val = dict_params[key]
|
|
261
|
+
if isinstance(val,list): self._cal_params[key] = [float(v) for v in val]
|
|
262
|
+
elif isinstance(val,tuple): self._cal_params[key] = tuple([float(v) for v in val])
|
|
263
|
+
else: self._cal_params[key] = float(val)
|
|
264
|
+
except Exception as e: print('ERROR _load_calibration file read: ',e); return
|
|
265
|
+
|
|
266
|
+
try:self._analyse_intensity_calibration_params(calculate_transfunc=False)
|
|
267
|
+
except Exception as e: qw.QMessageBox.warning(self,'Load calibration error','Error in loading intensity calibration parameters:\n{}'.format(e))
|
|
268
|
+
|
|
269
|
+
try:self._analyse_wavelength_calibration_params(calculate_transfunc=False)
|
|
270
|
+
except Exception as e: qw.QMessageBox.warning(self,'Load calibration error','Error in loading wavelength calibration parameters:\n{}'.format(e))
|
|
271
|
+
|
|
272
|
+
# Send the loadpath to the backend
|
|
273
|
+
self._pipe_update.send(self._cal_params)
|
|
274
|
+
|
|
275
|
+
def _save_calibration(self):
|
|
276
|
+
"""
|
|
277
|
+
Saves the calibration file as a json file
|
|
278
|
+
"""
|
|
279
|
+
filepath = qw.QFileDialog.getSaveFileName(
|
|
280
|
+
self,
|
|
281
|
+
'Save calibration file',
|
|
282
|
+
LibraryConfigEnum.SPECTROMETER_CALIBRATION_DIR_DEFAULT.value,
|
|
283
|
+
'JSON files (*.json)',
|
|
284
|
+
)[0]
|
|
285
|
+
if filepath == '': return
|
|
286
|
+
with open(filepath,'w') as f: json.dump(self._cal_params,f)
|
|
287
|
+
|
|
288
|
+
def _calculate_transferFunc_int_cubic(self,list_pixel_idx:list[float],list_int_mea:list[float],list_int_ref:list[float])\
|
|
289
|
+
-> tuple[tuple[float,float,float,float],list[tuple[float,float]]]:
|
|
290
|
+
"""
|
|
291
|
+
Generates the transfer function from the measured and reference/expected values
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
list_pixel_idx (list[float]): List of the pixel indices
|
|
295
|
+
list_int_mea (list[float]): List of the measured intensities
|
|
296
|
+
list_int_ref (list[float]): List of the reference intensities
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
tuple[tuple[float,float,float,float],list[tuple[float,float]]]: The coefficients of
|
|
300
|
+
the transfer function (a,b,c,d) for the cubic function of a*x**3 + b*x**2 + c*x + d
|
|
301
|
+
and the list of coordinates of the intensity ratio [ref/mea]
|
|
302
|
+
"""
|
|
303
|
+
def cubic(x,a,b,c,d): return a*x**3 + b*x**2 + c*x + d
|
|
304
|
+
|
|
305
|
+
# Calculate the intensity ratio (reference/measured)
|
|
306
|
+
list_int_ratio = [int_ref/int_mea for int_mea,int_ref in zip(list_int_mea,list_int_ref)]
|
|
307
|
+
|
|
308
|
+
list_ratio_coors = list(zip(list_pixel_idx,list_int_ratio))
|
|
309
|
+
|
|
310
|
+
# Fit the transfer function
|
|
311
|
+
popt,_ = curve_fit(cubic,xdata=list_pixel_idx,ydata=list_int_ratio)
|
|
312
|
+
popt = tuple(popt) # Convert the array to tuple for json serialisation during saving
|
|
313
|
+
return popt, list_ratio_coors
|
|
314
|
+
|
|
315
|
+
def _calculate_pxlMappingFunc_wv_cubic(self,list_mea:list[float],list_ref:list[float])\
|
|
316
|
+
-> tuple[float,float,float,float]:
|
|
317
|
+
"""
|
|
318
|
+
Generates the pixel mapping function from the measured and reference/expected values
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
list_mea (list[float]): List of the measured values
|
|
322
|
+
list_ref (list[float]): List of the reference/expected values
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
tuple[float,float,float,float]: The coefficients of the mapping function (a,b,c,d) for the cubic function of a*x**3 + b*x**2 + c*x + d
|
|
326
|
+
"""
|
|
327
|
+
def cubic(x,a,b,c,d): return a*x**3 + b*x**2 + c*x + d
|
|
328
|
+
|
|
329
|
+
# Fit the transfer function
|
|
330
|
+
popt,_ = curve_fit(cubic,xdata=list_mea,ydata=list_ref)
|
|
331
|
+
popt = tuple(popt) # Convert the array to tuple for json serialisation during saving
|
|
332
|
+
return popt
|
|
333
|
+
|
|
334
|
+
def _load_intensity(self):
|
|
335
|
+
"""
|
|
336
|
+
Loads the measured and reference intensities
|
|
337
|
+
"""
|
|
338
|
+
qw.QMessageBox.information(
|
|
339
|
+
self,
|
|
340
|
+
'Load intensity calibration points','Please select the csv file containing the measured'
|
|
341
|
+
' and reference intensities\nThe csv file should have 3 columns: Pixel index (or raw wavelength),'
|
|
342
|
+
' Measured intensity [a.u.], Reference intensity [a.u.].\nThe first line (header) will be skipped')
|
|
343
|
+
|
|
344
|
+
filepath = qw.QFileDialog.getOpenFileName(
|
|
345
|
+
self,
|
|
346
|
+
'Load intensity calibration points',
|
|
347
|
+
'',
|
|
348
|
+
'CSV files (*.csv)',
|
|
349
|
+
)[0]
|
|
350
|
+
if filepath == '': return
|
|
351
|
+
|
|
352
|
+
df = pd.read_csv(filepath,header=None,skiprows=1)
|
|
353
|
+
|
|
354
|
+
self._cal_params['intensity_list_pixel_idx'] = df.iloc[:,0].tolist()
|
|
355
|
+
self._cal_params['intensity_list_measured'] = df.iloc[:,1].tolist()
|
|
356
|
+
self._cal_params['intensity_list_reference'] = df.iloc[:,2].tolist()
|
|
357
|
+
|
|
358
|
+
# Calculate the transfer function and plot it
|
|
359
|
+
self._analyse_intensity_calibration_params()
|
|
360
|
+
self._pipe_update.send(self._cal_params)
|
|
361
|
+
|
|
362
|
+
def _analyse_intensity_calibration_params(self,calculate_transfunc:bool=True):
|
|
363
|
+
"""
|
|
364
|
+
Analyse the intensity calibration based on the values stored in the
|
|
365
|
+
calibration parameters dictionary. Also assigns the calculatedtransfer function
|
|
366
|
+
coefficients into the dictionary if requested.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
calculate_transfunc (bool): If True, the transfer function will be calculated and stored
|
|
370
|
+
"""
|
|
371
|
+
list_pixel_idx = self._cal_params['intensity_list_pixel_idx']
|
|
372
|
+
list_int_mea = self._cal_params['intensity_list_measured']
|
|
373
|
+
list_int_ref = self._cal_params['intensity_list_reference']
|
|
374
|
+
transfer_func_coeff, list_ratio_coors = self._calculate_transferFunc_int_cubic(list_pixel_idx,list_int_mea,list_int_ref)
|
|
375
|
+
|
|
376
|
+
# Store the transfer function coefficients
|
|
377
|
+
if calculate_transfunc:self._cal_params['intensity_poly_coeffs'] = transfer_func_coeff
|
|
378
|
+
|
|
379
|
+
# Plot the transfer function
|
|
380
|
+
self._plot_int_transfer_func(self._cal_params['intensity_list_pixel_idx'],self._cal_params['intensity_list_measured'],
|
|
381
|
+
self._cal_params['intensity_list_reference'],list_ratio_coors)
|
|
382
|
+
|
|
383
|
+
def _plot_int_transfer_func(self,list_pixel_idx:list[float],list_int_mea:list[float],list_int_ref:list[float],
|
|
384
|
+
list_ratio_coors:list[tuple[float,float]]):
|
|
385
|
+
"""
|
|
386
|
+
Plots the raw values and the transfer function for the intensity calibration based on given values
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
list_pixel_idx (list[float]): List of the pixel indices
|
|
390
|
+
list_int_mea (list[float]): List of the measured intensities
|
|
391
|
+
list_int_ref (list[float]): List of the reference intensities
|
|
392
|
+
list_ratio_coors (list[tuple[float,float]]): List of the coordinates of the intensity ratio [ref/mea]
|
|
393
|
+
"""
|
|
394
|
+
# > Raw values <
|
|
395
|
+
# Generate the plot for the raw values
|
|
396
|
+
fig,ax = self._fig_int, self._ax_int_raw
|
|
397
|
+
ax.cla()
|
|
398
|
+
ax.scatter(list_pixel_idx,list_int_mea,c='r',label='Measured intensity',marker='x',s=1)
|
|
399
|
+
ax.scatter(list_pixel_idx,list_int_ref,c='b',label='Reference intensity',marker='x',s=1)
|
|
400
|
+
ax.set_xlabel('Pixel index')
|
|
401
|
+
ax.set_ylabel('Intensity [a.u.]')
|
|
402
|
+
ax.set_title('Intensity calibration')
|
|
403
|
+
ax.legend()
|
|
404
|
+
|
|
405
|
+
# > Transfer function <
|
|
406
|
+
# Grab the transfer function
|
|
407
|
+
transfer_func_coeff = self._cal_params['intensity_poly_coeffs']
|
|
408
|
+
|
|
409
|
+
# Generate the points for the plot
|
|
410
|
+
x_min = min(list_pixel_idx)
|
|
411
|
+
x_max = max(list_pixel_idx)
|
|
412
|
+
x = np.linspace(x_min,x_max,100)
|
|
413
|
+
y = np.polyval(transfer_func_coeff,x)
|
|
414
|
+
|
|
415
|
+
x2 = [coor[0] for coor in list_ratio_coors]
|
|
416
|
+
y2 = [coor[1] for coor in list_ratio_coors]
|
|
417
|
+
y2_min = min(y2)
|
|
418
|
+
y2_max = max(y2)
|
|
419
|
+
if y2_min > 0 and y2_max > 0: y2_min = 0
|
|
420
|
+
if y2_min < 0 and y2_max < 0: y2_max = 0
|
|
421
|
+
|
|
422
|
+
# Generate the plot
|
|
423
|
+
fig,ax = self._fig_int, self._ax_int_cal
|
|
424
|
+
ax:Axes
|
|
425
|
+
ax.cla()
|
|
426
|
+
ax.plot(x,y,c='r',label='Transfer function')
|
|
427
|
+
ax.scatter(x2,y2,c='b',label='Intensity ratio [ref/mea]',marker='x',s=1)
|
|
428
|
+
ax.set_ylim(y2_min-abs(y2_min)*0.1,y2_max+abs(y2_max)*0.1)
|
|
429
|
+
ax.set_xlabel('Pixel index [pixel] OR Wavelength [nm]')
|
|
430
|
+
ax.set_ylabel('Intensity [a.u.]')
|
|
431
|
+
ax.set_title('Intensity calibration transfer function')
|
|
432
|
+
ax.legend()
|
|
433
|
+
|
|
434
|
+
# Show the plot and redraw the canvas
|
|
435
|
+
self._canvas_int.draw()
|
|
436
|
+
|
|
437
|
+
def _load_wavelength(self):
|
|
438
|
+
"""
|
|
439
|
+
Loads the measured and reference wavelengths
|
|
440
|
+
"""
|
|
441
|
+
qw.QMessageBox.information(
|
|
442
|
+
self,
|
|
443
|
+
'Load wavelength calibration points','Please select the csv file containing the measured'
|
|
444
|
+
' and reference wavelengths\nThe csv file should have 2 columns: Measured peak loc [pixel OR nm],'
|
|
445
|
+
' Reference peak loc [nm].\nThe first line (header) will be skipped')
|
|
446
|
+
|
|
447
|
+
filepath = qw.QFileDialog.getOpenFileName(
|
|
448
|
+
self,
|
|
449
|
+
'Load wavelength calibration points',
|
|
450
|
+
'',
|
|
451
|
+
'CSV files (*.csv)',
|
|
452
|
+
)[0]
|
|
453
|
+
if filepath == '': return
|
|
454
|
+
|
|
455
|
+
df = pd.read_csv(filepath,header=None,skiprows=1)
|
|
456
|
+
|
|
457
|
+
self._cal_params['wavelen_list_measured'] = df.iloc[:,0].tolist()
|
|
458
|
+
self._cal_params['wavelen_list_reference'] = df.iloc[:,1].tolist()
|
|
459
|
+
|
|
460
|
+
# Calculate the transfer function
|
|
461
|
+
self._analyse_wavelength_calibration_params()
|
|
462
|
+
self._pipe_update.send(self._cal_params)
|
|
463
|
+
|
|
464
|
+
def _analyse_wavelength_calibration_params(self,calculate_transfunc:bool=True):
|
|
465
|
+
"""
|
|
466
|
+
Analyse the wavelength calibration based on the values stored in the
|
|
467
|
+
calibration parameters dictionary. Also assigns the calculated transfer function
|
|
468
|
+
coefficients into the dictionary.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
calculate_transfunc (bool): If True, the transfer function will be calculated
|
|
472
|
+
"""
|
|
473
|
+
if calculate_transfunc:
|
|
474
|
+
list_peakRS_mea = self._cal_params['wavelen_list_measured']
|
|
475
|
+
list_peakRS_ref = self._cal_params['wavelen_list_reference']
|
|
476
|
+
transfer_func_coeff = self._calculate_pxlMappingFunc_wv_cubic(list_peakRS_mea,list_peakRS_ref)
|
|
477
|
+
self._cal_params['wavelen_poly_coeffs'] = transfer_func_coeff
|
|
478
|
+
|
|
479
|
+
# Update the treeview
|
|
480
|
+
self._update_treeview_wv()
|
|
481
|
+
|
|
482
|
+
# Plot the transfer function
|
|
483
|
+
self._plot_wv_pxlMapping_func(self._cal_params['wavelen_list_measured'],self._cal_params['wavelen_list_reference'])
|
|
484
|
+
|
|
485
|
+
def _plot_wv_pxlMapping_func(self,list_peakRS_mea:list[float],list_peakRS_ref:list[float]):
|
|
486
|
+
"""
|
|
487
|
+
Plots the pixel mapping function for the wavelength calibration
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
list_peakRS_mea (list[float]): List of the Raman shift peaks of the sample representative
|
|
491
|
+
list_peakRS_ref (list[float]): List of the Raman shift peaks of the reference
|
|
492
|
+
"""
|
|
493
|
+
# > Grab the transfer function <
|
|
494
|
+
transfer_func_coeff = self._cal_params['wavelen_poly_coeffs']
|
|
495
|
+
|
|
496
|
+
# Generate the x and y values from the transfer function
|
|
497
|
+
x_min = min(list_peakRS_mea)
|
|
498
|
+
x_max = max(list_peakRS_mea)
|
|
499
|
+
x = np.linspace(x_min,x_max,100)
|
|
500
|
+
y = np.polyval(transfer_func_coeff,x)
|
|
501
|
+
|
|
502
|
+
# Generate the plot
|
|
503
|
+
fig,ax = self._fig_wv, self._ax_wv
|
|
504
|
+
ax.cla()
|
|
505
|
+
ax.plot(x,y,c='b',label='Pixel mapping func')
|
|
506
|
+
ax.scatter(list_peakRS_mea,list_peakRS_ref,c='r',label='Peaks')
|
|
507
|
+
ax.set_xlabel('Measurement Raman peaks [nm OR pixel]')
|
|
508
|
+
ax.set_ylabel('Reference Raman peaks [nm]')
|
|
509
|
+
ax.set_title('Spectrometer pixel mapping')
|
|
510
|
+
ax.legend()
|
|
511
|
+
|
|
512
|
+
# Add a label to each scatter points
|
|
513
|
+
for i,peakRS in enumerate(list_peakRS_mea):
|
|
514
|
+
ax.annotate(str(peakRS),(list_peakRS_mea[i],list_peakRS_ref[i]))
|
|
515
|
+
|
|
516
|
+
# Show the plot and redraw the canvas
|
|
517
|
+
self._canvas_wv.draw()
|
|
518
|
+
|
|
519
|
+
def _update_treeview_wv(self):
|
|
520
|
+
"""
|
|
521
|
+
Updates the treeview for the wavelength calibration
|
|
522
|
+
"""
|
|
523
|
+
self._tree.clear()
|
|
524
|
+
for (mea,ref) in zip(self._cal_params['wavelen_list_measured'],self._cal_params['wavelen_list_reference']):
|
|
525
|
+
item = qw.QTreeWidgetItem()
|
|
526
|
+
item.setText(0,str(mea))
|
|
527
|
+
item.setText(1,str(ref))
|
|
528
|
+
|
|
529
|
+
self._tree.addTopLevelItem(item)
|
|
530
|
+
|
|
531
|
+
class MainWindow_SpectrometerCalibrationGenerator(qw.QMainWindow):
|
|
532
|
+
"""
|
|
533
|
+
Main window for the spectrometer calibration generator
|
|
534
|
+
"""
|
|
535
|
+
def __init__(self, pipe_update: Connection):
|
|
536
|
+
super().__init__()
|
|
537
|
+
self.setWindowTitle('Spectrometer Calibration Generator')
|
|
538
|
+
self._wdg = Wdg_SpectrometerCalibrationGenerator(self, pipe_update)
|
|
539
|
+
self.setCentralWidget(self._wdg)
|
|
540
|
+
|
|
541
|
+
def get_WdgCalibrator(self) -> Wdg_SpectrometerCalibrationGenerator:
|
|
542
|
+
"""
|
|
543
|
+
Gets the calibrator widget
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
Wdg_SpectrometerCalibrationGenerator: The calibrator widget
|
|
547
|
+
"""
|
|
548
|
+
return self._wdg
|
|
549
|
+
|
|
550
|
+
@Slot()
|
|
551
|
+
def closeEvent(self, event):
|
|
552
|
+
"""
|
|
553
|
+
Overrides the close event to hide the window instead of closing it
|
|
554
|
+
"""
|
|
555
|
+
self.hide()
|
|
556
|
+
event.ignore()
|
|
557
|
+
|
|
558
|
+
def test():
|
|
559
|
+
app = qw.QApplication([])
|
|
560
|
+
main_window = qw.QMainWindow()
|
|
561
|
+
pipe_update, pipe_update_child = Pipe()
|
|
562
|
+
wdg = Wdg_SpectrometerCalibrationGenerator(main_window, pipe_update)
|
|
563
|
+
# SpectrometerCalibrator(pipe_update_child, Pipe()[0])
|
|
564
|
+
main_window.setCentralWidget(wdg)
|
|
565
|
+
main_window.show()
|
|
566
|
+
sys.exit(app.exec())
|
|
567
|
+
|
|
568
|
+
if __name__ == '__main__':
|
|
569
|
+
test()
|