peakviz 0.1.0__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.
peakviz-0.1.0/LICENSE ADDED
File without changes
peakviz-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: peakviz
3
+ Version: 0.1.0
4
+ Summary: A tool to visualize Hyperspectral data from point and imaging sensors.
5
+ Author-email: Tasnim Tabassum Nova <tabassumnova1@gmail.com>
6
+ Project-URL: Homepage, https://github.com/TabassumNova/PeakViz
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: PyQt5
11
+ Requires-Dist: numpy
12
+ Requires-Dist: matplotlib
13
+ Requires-Dist: pandas
14
+ Requires-Dist: plotly
15
+ Dynamic: license-file
16
+
17
+ # PeakViz
18
+ This repository represents a simple workflow to visualize Hyperspectral data from point sensors: PSR and Fourier Transform Infrared Spectroscopy (FTIR) sensor. The wavelength ranges for these sensors are :
19
+ - PSR : 350.0 - 2500.0 nm
20
+ - FTIR : 2500.0629 - 15385.6915 nm
21
+
22
+ <p align="center">
23
+ <img src="image1.png" width="500"/>
24
+ </p>
25
+
26
+ # Tutorial
27
+ Coming soon ...
28
+
29
+ # Acknowledgement
30
+ This work is performed at [Helmholtz Institute Freiberg for Resource Technology](https://www.hzdr.de/db/Cms?pOid=32948&pNid=2423&pLang=en) in the [Exploration](https://www.iexplo.space/) department
31
+
32
+
33
+
@@ -0,0 +1,17 @@
1
+ # PeakViz
2
+ This repository represents a simple workflow to visualize Hyperspectral data from point sensors: PSR and Fourier Transform Infrared Spectroscopy (FTIR) sensor. The wavelength ranges for these sensors are :
3
+ - PSR : 350.0 - 2500.0 nm
4
+ - FTIR : 2500.0629 - 15385.6915 nm
5
+
6
+ <p align="center">
7
+ <img src="image1.png" width="500"/>
8
+ </p>
9
+
10
+ # Tutorial
11
+ Coming soon ...
12
+
13
+ # Acknowledgement
14
+ This work is performed at [Helmholtz Institute Freiberg for Resource Technology](https://www.hzdr.de/db/Cms?pOid=32948&pNid=2423&pLang=en) in the [Exploration](https://www.iexplo.space/) department
15
+
16
+
17
+
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "peakviz"
7
+ version = "0.1.0"
8
+ description = "A tool to visualize Hyperspectral data from point and imaging sensors."
9
+ authors = [
10
+ { name = "Tasnim Tabassum Nova", email = "tabassumnova1@gmail.com" }
11
+ ]
12
+ readme = "README.md"
13
+ license = { file = "LICENSE" }
14
+ requires-python = ">=3.8"
15
+ dependencies = [
16
+ "PyQt5",
17
+ "numpy",
18
+ "matplotlib",
19
+ "pandas",
20
+ "plotly",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/TabassumNova/PeakViz"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from . import dataloader, point_sensor, pre_processing, vizualization
@@ -0,0 +1,137 @@
1
+ import pandas as pd
2
+ import os
3
+ import re
4
+
5
+ def is_float(s):
6
+ try:
7
+ float(s)
8
+ return True
9
+ except ValueError:
10
+ return False
11
+
12
+ def check_and_extract_line(file_path, search_text):
13
+ with open(file_path, 'r') as file:
14
+ content = file.read()
15
+ if search_text in content:
16
+ # Use regex to find the line starting with the specified 'line' variable
17
+ pattern = f"^{re.escape(search_text)}.*$"
18
+ match = re.search(pattern, content, re.MULTILINE)
19
+ if match:
20
+ return True, match.group(0)
21
+ return False, None
22
+
23
+ def find_xy(extracted_line):
24
+ if re.search(r'reflectance', extracted_line, re.IGNORECASE):
25
+ return 'wavelength', 'reflectance'
26
+ elif re.search(r'absorbance', extracted_line, re.IGNORECASE):
27
+ wavenumber_present = bool(re.search(r'wavenumber', extracted_line, re.IGNORECASE))
28
+ wavelength_present = bool(re.search(r'nanometer', extracted_line, re.IGNORECASE))
29
+ if wavenumber_present:
30
+ return 'wavenumber', 'absorbance'
31
+ elif wavelength_present:
32
+ return 'wavelength', 'absorbance'
33
+
34
+
35
+ # For point sensors
36
+ # Load all type of files
37
+ # Reflectance/ Absorbance/ Nanometer/ Wavenumber
38
+ def load_data(paths, signal_type, search_text):
39
+ all_energy = []
40
+ ## for reference spectrum
41
+ spectrum_dict = {}
42
+ spectrum_dict['reflectance'] = []
43
+ spectrum_dict['absorbance'] = []
44
+ # if signal_type == 'reference':
45
+ # refSpectrum_dict = {}
46
+ # refSpectrum_dict['reflectance'] = []
47
+ # refSpectrum_dict['absorbance'] = []
48
+
49
+ ###
50
+ for file_path in paths:
51
+ seach_text_present, extracted_line = check_and_extract_line(file_path, search_text)
52
+ x_axis, y_axis = find_xy(extracted_line)
53
+ if y_axis != signal_type and signal_type != 'reference':
54
+ return None, None
55
+
56
+ filename = os.path.basename(file_path)
57
+ with open(file_path, 'r') as f:
58
+ lines = f.readlines()
59
+
60
+ if x_axis == 'wavelength':
61
+ sample_energy = [filename]
62
+ wavelength_list = ['sample']
63
+ for line0 in lines:
64
+ line = line0.strip().split(" ")
65
+ if is_float(line[0]):
66
+ wavelength = float(line[0])
67
+ energy = float(line[-1])
68
+ sample_energy.append(energy)
69
+ wavelength_list.append(wavelength)
70
+ # all_energy.append(sample_energy)
71
+ spectrum_dict[y_axis].append(sample_energy)
72
+
73
+ elif x_axis == 'wavenumber':
74
+ sample_energy = []
75
+ wavelength_list = []
76
+ for line0 in lines:
77
+ line = line0.strip().split(" ")
78
+ if is_float(line[0]):
79
+ energy = float(line[-1])
80
+ sample_energy = [energy] + sample_energy
81
+ # TODO
82
+ # Fill wavelength list only for one sample file to avoid
83
+ # iterative process
84
+ wavenumber = float(line[0])
85
+ wavelength = (1/wavenumber)* (10 ** 7)
86
+ wavelength_list = [wavelength] + wavelength_list
87
+ wavelength_list = ['sample'] + wavelength_list
88
+ sample_energy = [filename] + sample_energy
89
+ # all_energy.append(sample_energy)
90
+ spectrum_dict[y_axis].append(sample_energy)
91
+
92
+ if len(spectrum_dict['reflectance']) == 0:
93
+ reflectance_df = None
94
+ else:
95
+ reflectance_df = pd.DataFrame(spectrum_dict['reflectance'], columns=wavelength_list)
96
+ if len(spectrum_dict['absorbance']) == 0:
97
+ absorbance_df = None
98
+ else:
99
+ absorbance_df = pd.DataFrame(spectrum_dict['absorbance'], columns=wavelength_list)
100
+
101
+ return reflectance_df, absorbance_df
102
+ # df = pd.DataFrame(spectrum_dict, columns=wavelength_list)
103
+ # return df
104
+
105
+
106
+ def load_refSpectrum(spectrum_paths):
107
+ all_energy = []
108
+ for file_path in spectrum_paths:
109
+ filename = os.path.basename(file_path)
110
+ sample_energy = []
111
+ wavelength_list = []
112
+ with open(file_path, 'r') as f:
113
+ lines = f.readlines()
114
+ for line0 in lines:
115
+ line = line0.strip().split(" ")
116
+ if is_float(line[1]):
117
+ wavelength = float(line[1])
118
+ energy = float(line[-1])
119
+ sample_energy.append(energy)
120
+ wavelength_list.append(wavelength)
121
+ pass
122
+ wavelength_list = ['polymer'] + wavelength_list
123
+ sample_energy = [filename] + sample_energy
124
+ all_energy.append(sample_energy)
125
+ refSpectrum_df = pd.DataFrame(all_energy, columns=wavelength_list)
126
+ return refSpectrum_df
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: peakviz
3
+ Version: 0.1.0
4
+ Summary: A tool to visualize Hyperspectral data from point and imaging sensors.
5
+ Author-email: Tasnim Tabassum Nova <tabassumnova1@gmail.com>
6
+ Project-URL: Homepage, https://github.com/TabassumNova/PeakViz
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: PyQt5
11
+ Requires-Dist: numpy
12
+ Requires-Dist: matplotlib
13
+ Requires-Dist: pandas
14
+ Requires-Dist: plotly
15
+ Dynamic: license-file
16
+
17
+ # PeakViz
18
+ This repository represents a simple workflow to visualize Hyperspectral data from point sensors: PSR and Fourier Transform Infrared Spectroscopy (FTIR) sensor. The wavelength ranges for these sensors are :
19
+ - PSR : 350.0 - 2500.0 nm
20
+ - FTIR : 2500.0629 - 15385.6915 nm
21
+
22
+ <p align="center">
23
+ <img src="image1.png" width="500"/>
24
+ </p>
25
+
26
+ # Tutorial
27
+ Coming soon ...
28
+
29
+ # Acknowledgement
30
+ This work is performed at [Helmholtz Institute Freiberg for Resource Technology](https://www.hzdr.de/db/Cms?pOid=32948&pNid=2423&pLang=en) in the [Exploration](https://www.iexplo.space/) department
31
+
32
+
33
+
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/__init__.py
5
+ src/dataloader.py
6
+ src/point_sensor.py
7
+ src/pre_processing.py
8
+ src/vizualization.py
9
+ src/peakviz.egg-info/PKG-INFO
10
+ src/peakviz.egg-info/SOURCES.txt
11
+ src/peakviz.egg-info/dependency_links.txt
12
+ src/peakviz.egg-info/requires.txt
13
+ src/peakviz.egg-info/top_level.txt
14
+ tests/test_dataloader.py
15
+ tests/test_imaging_sensor.py
16
+ tests/test_vizualization.py
@@ -0,0 +1,5 @@
1
+ PyQt5
2
+ numpy
3
+ matplotlib
4
+ pandas
5
+ plotly
@@ -0,0 +1,5 @@
1
+ __init__
2
+ dataloader
3
+ point_sensor
4
+ pre_processing
5
+ vizualization
@@ -0,0 +1,348 @@
1
+ import sys
2
+ from PyQt5.QtCore import *
3
+ from PyQt5.QtWidgets import *
4
+ from PyQt5.QtGui import *
5
+
6
+ from .dataloader import *
7
+ from .vizualization import *
8
+ from .pre_processing import *
9
+
10
+ class PointSensor(QScrollArea):
11
+
12
+ def __init__(self):
13
+ super(PointSensor, self).__init__()
14
+ self.widget1 = QWidget()
15
+ layout1 = QGridLayout(self.widget1)
16
+ layout1.setAlignment(Qt.AlignTop)
17
+
18
+ self.sensor = None
19
+ self.search_text = None #TODO: make this text from user input
20
+ self.reflectance_files = None
21
+ self.absorbance_files = None
22
+ self.reflectance_df = None
23
+ self.absorbance_df = None
24
+ self.batchname = ''
25
+ self.lib_path = None
26
+ self.library_df = None
27
+ self.spectrum_paths = None
28
+ self.refSpectrum_df = {}
29
+ self.rescaling_flag = False
30
+ self.average_flag = False
31
+ self.reflectance_rescaled_df = None
32
+ self.absorbance_rescaled_df = None
33
+ self.reflectance_averaged_df = None
34
+ self.absorbance_averaged_df = None
35
+ self.download_flag = False
36
+ self.setFixedWidth(500)
37
+ self.setFixedHeight(700)
38
+
39
+ # Choose sensor
40
+ self.label1 = QLabel(self.widget1)
41
+ self.label1.setObjectName('Choose point sensor: ')
42
+ self.label1.setText('Choose point sensor: ')
43
+ font = QFont()
44
+ font.setBold(True)
45
+ font.setPointSize(14)
46
+ self.label1.setFont(font)
47
+ self.label1.setGeometry(QRect(10, 20, 150, 20))
48
+
49
+
50
+ # creating check box for choosing sensor
51
+ self.checkBoxSWIR = QCheckBox("VNIR/SWIR", self.widget1)
52
+ self.checkBoxSWIR.setGeometry(10, 50, 100, 20)
53
+ self.checkBoxMWIR = QCheckBox("MWIR/LWIR", self.widget1)
54
+ self.checkBoxMWIR.setGeometry(120, 50, 100, 20)
55
+
56
+ # calling the uncheck method if any check box state is changed
57
+ self.checkBoxSWIR.stateChanged.connect(self.select_sensor)
58
+ self.checkBoxMWIR.stateChanged.connect(self.select_sensor)
59
+
60
+ # Select Reflectance files
61
+ self.label2 = QLabel(self.widget1)
62
+ self.label2.setObjectName('Select single or multiple "Reflectance" files')
63
+ self.label2.setText('Select single or multiple "Reflectance" files')
64
+ self.label2.setFont(font)
65
+ self.label2.setGeometry(QRect(10, 90, 300, 20))
66
+
67
+ # Button for loading Reflectance
68
+ self.btn1 = QPushButton(self.widget1)
69
+ self.btn1.setObjectName('Load Reflectance')
70
+ self.btn1.setText('Load Reflectance')
71
+ self.btn1.setGeometry(QRect(10, 120, 130, 40))
72
+ self.btn1.clicked.connect(self.reflectance_file_dialog)
73
+
74
+ # Message for loading Reflectance files
75
+ self.label3 = QLabel(self.widget1)
76
+ self.label3.setObjectName('Reflectance loaded')
77
+ self.label3.setText('')
78
+ self.label3.setStyleSheet("border: 0.5px solid gray;")
79
+ self.label3.setGeometry(QRect(150, 125, 130, 30))
80
+
81
+
82
+ # Label for renaming files from comment (VNIR/SWIR only)
83
+ self.rename_label = QLabel(self.widget1)
84
+ self.rename_label.setObjectName('Rename files from comment')
85
+ self.rename_label.setText('Rename the files (Only applicable to VNIR/SWIR)')
86
+ self.rename_label.setFont(font)
87
+ self.rename_label.setGeometry(QRect(10, 170, 350, 20))
88
+
89
+ # Checkbox for renaming
90
+ self.rename_checkbox = QCheckBox('Rename', self.widget1)
91
+ self.rename_checkbox.setGeometry(QRect(10, 195, 80, 25))
92
+ # self.rename_checkbox.stateChanged.connect(self.select_rename)
93
+
94
+ # Save button beside checkbox
95
+ self.save_button = QPushButton('Save', self.widget1)
96
+ self.save_button.setGeometry(QRect(100, 195, 80, 25))
97
+ # self.save_button.clicked.connect(self.rename_files)
98
+
99
+ # Select Absorbance files
100
+ self.label4 = QLabel(self.widget1)
101
+ self.label4.setObjectName('Select single or multiple "Absorbance" files (Optional)')
102
+ self.label4.setText('Select single or multiple "Absorbance" files (Optional)')
103
+ self.label4.setFont(font)
104
+ self.label4.setGeometry(QRect(10, 235, 365, 20))
105
+
106
+ # Button for loading Absorbance
107
+ self.btn2 = QPushButton(self.widget1)
108
+ self.btn2.setObjectName('Load Absorbance')
109
+ self.btn2.setText('Load Absorbance')
110
+ self.btn2.setGeometry(QRect(10, 265, 130, 40))
111
+ self.btn2.clicked.connect(self.absorbance_file_dialog)
112
+
113
+ # Message for loading Absorbance files
114
+ self.label5 = QLabel(self.widget1)
115
+ self.label5.setObjectName('Absorbance loaded')
116
+ self.label5.setText('')
117
+ self.label5.setStyleSheet("border: 0.5px solid gray;")
118
+ self.label5.setGeometry(QRect(150, 270, 130, 30))
119
+
120
+ # Select Library file
121
+ self.label6 = QLabel(self.widget1)
122
+ self.label6.setObjectName('Select fingerprint library for the specified sensor (Optional)')
123
+ self.label6.setText('Select fingerprint library for the specified sensor (Optional)')
124
+ self.label6.setFont(font)
125
+ self.label6.setGeometry(QRect(10, 310, 400, 20))
126
+
127
+ # Button for loading Library
128
+ self.btn3 = QPushButton(self.widget1)
129
+ self.btn3.setObjectName('Load Fingerprints')
130
+ self.btn3.setText('Load Fingerprints')
131
+ self.btn3.setGeometry(QRect(10, 340, 130, 40))
132
+ self.btn3.clicked.connect(self.open_library)
133
+
134
+ # Message for library files
135
+ self.label7 = QLabel(self.widget1)
136
+ self.label7.setObjectName('Fingerprints loaded')
137
+ self.label7.setText('')
138
+ self.label7.setStyleSheet("border: 0.5px solid gray;")
139
+ self.label7.setGeometry(QRect(150, 345, 130, 30))
140
+
141
+ #### test
142
+ # Select Reference spectrum
143
+ self.label8 = QLabel(self.widget1)
144
+ self.label8.setObjectName('Select Reference spectrum for the specified sensor (Optional)')
145
+ self.label8.setText('Select Reference spectrum for the specified sensor (Optional)')
146
+ self.label8.setFont(font)
147
+ self.label8.setGeometry(QRect(10, 385, 420, 20))
148
+
149
+ # Button for loading Reference spectrum
150
+ self.btn4 = QPushButton(self.widget1)
151
+ self.btn4.setObjectName('Load Spectrums')
152
+ self.btn4.setText('Load Spectrums')
153
+ self.btn4.setGeometry(QRect(10, 415, 130, 40))
154
+ self.btn4.clicked.connect(self.open_refSpectrums)
155
+
156
+ # Message for Reference spectrum
157
+ self.label9 = QLabel(self.widget1)
158
+ self.label9.setObjectName('Spectrums loaded')
159
+ self.label9.setText('')
160
+ self.label9.setStyleSheet("border: 0.5px solid gray;")
161
+ self.label9.setGeometry(QRect(150, 420, 130, 30))
162
+ #########
163
+
164
+ # Select pre-processing method
165
+ self.label10 = QLabel(self.widget1)
166
+ self.label10.setObjectName('Select Pre-processing method (Optional)')
167
+ self.label10.setText('Select Pre-processing method (Optional)')
168
+ self.label10.setFont(font)
169
+ self.label10.setGeometry(QRect(10, 460, 365, 20))
170
+
171
+ # creating check box for re-scaling
172
+ self.checkBoxRescaling = QCheckBox("Y-axis rescaling", self.widget1)
173
+ self.checkBoxRescaling.setGeometry(10, 490, 160, 30)
174
+ self.checkBoxRescaling.stateChanged.connect(self.select_rescaling)
175
+
176
+ # creating check box for choosing sensor
177
+ self.checkBoxAverage = QCheckBox("Average", self.widget1)
178
+ self.checkBoxAverage.setGeometry(160, 490, 160, 30)
179
+ self.checkBoxAverage.stateChanged.connect(self.select_average)
180
+
181
+ # Start visualization
182
+ self.label11 = QLabel(self.widget1)
183
+ self.label11.setObjectName('Data visualisation')
184
+ self.label11.setText('Data visualisation')
185
+ self.label11.setFont(font)
186
+ self.label11.setGeometry(QRect(10, 530, 365, 20))
187
+
188
+ # creating check box for choosing sensor
189
+ self.checkBoxDownload = QCheckBox("Download as .html", self.widget1)
190
+ self.checkBoxDownload.setGeometry(10, 560, 150, 30)
191
+ self.checkBoxDownload.stateChanged.connect(self.select_download)
192
+
193
+ # For opening data
194
+ self.btn5 = QPushButton(self.widget1)
195
+ self.btn5.setObjectName('Open Data')
196
+ self.btn5.setText('Open Data')
197
+ self.btn5.setGeometry(QRect(10, 590, 111, 40))
198
+ self.btn5.clicked.connect(self.open_data)
199
+
200
+ self.setWidget(self.widget1)
201
+ self.setWidgetResizable(True)
202
+ self.widget1.setLayout(layout1)
203
+
204
+ def select_sensor(self, state):
205
+ if state == Qt.Checked:
206
+ if self.sender() == self.checkBoxSWIR:
207
+ self.checkBoxMWIR.setChecked(False)
208
+ self.sensor = 'VNIR_SWIR'
209
+ self.search_text = 'Measurement:'
210
+ elif self.sender() == self.checkBoxMWIR:
211
+ self.checkBoxSWIR.setChecked(False)
212
+ self.sensor = 'MWIR_LWIR'
213
+ self.search_text = 'XYUNITS'
214
+
215
+ def create_batchname(self, file_path):
216
+ # Get the batchname
217
+ if self.sensor == 'VNIR_SWIR':
218
+ clock = 3
219
+ elif self.sensor == 'MWIR_LWIR':
220
+ clock = 4
221
+ while clock:
222
+ file_path, folder = os.path.split(file_path)
223
+ clock -= 1
224
+ self.batchname = folder
225
+
226
+
227
+ def reflectance_file_dialog(self):
228
+ # Open the file dialog and get the selected file name
229
+ self.reflectance_files, _ = QFileDialog.getOpenFileNames(self)
230
+ if self.reflectance_files:
231
+ self.reflectance_df, _ = load_data(self.reflectance_files, signal_type='reflectance',
232
+ search_text= self.search_text)
233
+ message = "Reflectance loaded" if self.reflectance_df is not None else "Error!"
234
+ self.label3.setText(message)
235
+ ###
236
+ if not self.batchname:
237
+ self.create_batchname(self.reflectance_files[0])
238
+
239
+
240
+ def absorbance_file_dialog(self):
241
+ # Open the file dialog and get the selected file name
242
+ self.absorbance_files, _ = QFileDialog.getOpenFileNames(self)
243
+ if self.absorbance_files:
244
+ _, self.absorbance_df = load_data(self.absorbance_files, signal_type='absorbance',
245
+ search_text= self.search_text)
246
+ message = "Absorbance loaded" if self.absorbance_df is not None else "Error!"
247
+ self.label5.setText(message)
248
+ if not self.batchname:
249
+ self.create_batchname(self.absorbance_files[0])
250
+
251
+
252
+ def open_library(self):
253
+ # Open the file dialog and get the selected excel file
254
+ # Load the file as pandas dataframe
255
+ self.lib_path, _ = QFileDialog.getOpenFileName(self)
256
+ if self.lib_path:
257
+ self.label7.setText("Library loaded")
258
+ self.library_df = pd.read_excel(self.lib_path)
259
+
260
+ def open_refSpectrums(self):
261
+ # Select one or multiple .txt files for reference spectrum
262
+ # Open the file dialog and get the selected .txt files
263
+ # Load the file as pandas dataframe
264
+ self.spectrum_paths, _ = QFileDialog.getOpenFileNames(self)
265
+ if self.spectrum_paths:
266
+ # self.refSpectrum_df = load_refSpectrum(self.spectrum_paths)
267
+ ###
268
+ reflectRef_df, absorbRef_df = load_data(self.spectrum_paths, signal_type='reference',
269
+ search_text= 'XYUNITS')
270
+ if reflectRef_df is not None:
271
+ # self.reflectance_df = pd.concat([self.reflectance_df, reflectRef_df], axis=0)
272
+ self.refSpectrum_df['Reflectance'] = reflectRef_df
273
+ if absorbRef_df is not None:
274
+ # self.absorbance_df = pd.concat([self.absorbance_df, absorbRef_df], axis=0)
275
+ self.refSpectrum_df['Absorbance'] = absorbRef_df
276
+ ###
277
+ self.label9.setText('Spectrums loaded')
278
+
279
+ def select_rescaling(self, state):
280
+ if state == Qt.Checked:
281
+ if self.sender() == self.checkBoxRescaling:
282
+ self.rescaling_flag = True
283
+ if self.reflectance_files:
284
+ self.reflectance_rescaled_df = rescale_data(self.reflectance_df)
285
+ if self.absorbance_files:
286
+ self.absorbance_rescaled_df = rescale_data(self.absorbance_df)
287
+
288
+ def select_average(self, state):
289
+ if state == Qt.Checked:
290
+ if self.sender() == self.checkBoxAverage:
291
+ self.average_flag = True
292
+ if self.reflectance_files:
293
+ self.reflectance_averaged_df = average_data(self.reflectance_df,
294
+ self.reflectance_files[0],
295
+ signal_type= 'Reflectance')
296
+ if self.absorbance_files:
297
+ self.absorbance_averaged_df = average_data(self.absorbance_df,
298
+ self.absorbance_files[0],
299
+ signal_type= 'Absorbance')
300
+
301
+ def select_download(self, state):
302
+ if state == Qt.Checked:
303
+ if self.sender() == self.checkBoxDownload:
304
+ self.download_flag = True
305
+
306
+
307
+ def open_data(self):
308
+ df_plot = {}
309
+ if self.reflectance_files:
310
+ df_plot['Reflectance'] = (self.reflectance_df, 'Reflectance')
311
+ if self.absorbance_files:
312
+ df_plot['Absorbance'] = (self.absorbance_df, 'Absorbance')
313
+ if self.rescaling_flag:
314
+ df_plot['Reflectance (Re-scaled)'] = (self.reflectance_rescaled_df, 'Reflectance')
315
+ df_plot['Absorbance (Re-scaled)'] = (self.absorbance_rescaled_df, 'Absorbance')
316
+ pass
317
+
318
+ # This creates vizualization to plot multiple data
319
+ viz(self.batchname, df_plot, fingerprint_library=self.library_df,
320
+ reference_Spectrums=self.refSpectrum_df,
321
+ sensor=self.sensor, download=self.download_flag)
322
+ # Check if del obj is possible
323
+ self.reflectance_files = None
324
+ self.absorbance_files = None
325
+ self.reflectance_df = None
326
+ self.absorbance_df = None
327
+ self.batchname = None
328
+ # self.library_df = None
329
+ # self.rescaling_flag = False
330
+ self.reflectance_rescaled_df = None
331
+ self.absorbance_rescaled_df = None
332
+ # self.download_flag = False
333
+ self.label3.setText("")
334
+ self.label5.setText("")
335
+ self.label9.setText("")
336
+ # self.label7.setText("")
337
+ # if hasattr(self, 'downloadCheckBox'):
338
+ self.checkBoxDownload.setChecked(False)
339
+ # if hasattr(self, 'averageCheckBox'):
340
+ self.checkBoxAverage.setChecked(False)
341
+
342
+
343
+
344
+ # if __name__ == "__main__":
345
+ # app = QApplication(sys.argv)
346
+ # window = Window()
347
+ # window.show()
348
+ # sys.exit(app.exec())
@@ -0,0 +1,53 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import os
4
+
5
+ def rescale_data(df):
6
+ new = []
7
+ for index, row in df.iterrows():
8
+ text = [row.iloc[0]]
9
+ energy = np.array(row)[1:]
10
+ min_val = energy.min()
11
+ max_val = energy.max()
12
+ rescaled_energy = list((energy - min_val) / (max_val - min_val))
13
+ new.append(text+rescaled_energy)
14
+
15
+ rescaled_df = pd.DataFrame(new, columns=list(df.columns))
16
+ return rescaled_df
17
+
18
+ def common_prefix(strings):
19
+ if not strings:
20
+ return ""
21
+
22
+ # Find the length of the shortest string
23
+ min_length = min(len(s) for s in strings)
24
+
25
+ # Compare characters
26
+ for i in range(min_length):
27
+ if len(set(s[i] for s in strings)) > 1:
28
+ return strings[0][:i]
29
+
30
+ # If we've made it here, return the whole shortest string
31
+ return strings[0][:min_length]
32
+
33
+ def average_data(df, input_path, signal_type):
34
+ samples = list(df['sample'])
35
+ averaged_name = common_prefix(samples)
36
+ averaged_list = []
37
+ for (columnName, columnData) in df.items():
38
+ is_float = np.issubdtype(columnData.dtype, np.floating)
39
+ if is_float:
40
+ mean = columnData.mean().item()
41
+ averaged_list.append(mean)
42
+ averaged_df1 = pd.DataFrame([averaged_list], columns=list(df.columns[1:]))
43
+
44
+ ### For generating text file
45
+ reshaped_df = averaged_df1.melt(var_name="Wavelength (nm)", value_name="FTIR signal").reset_index(drop=True)
46
+ file_path, folder = os.path.split(input_path)
47
+ output_path = os.path.join(file_path, averaged_name+ signal_type + '.txt')
48
+ with open(output_path, 'w') as f:
49
+ f.write("XYUNITS Nanometer; "+ signal_type + "\n")
50
+ reshaped_df.to_csv(f, sep=' ', index=False, header=True)
51
+
52
+ averaged_df2 = pd.DataFrame([[averaged_name]+averaged_list], columns=list(df.columns))
53
+ return averaged_df2
@@ -0,0 +1,163 @@
1
+ import ast
2
+ import plotly.graph_objects as go
3
+ import plotly.express as px
4
+ import numpy as np
5
+
6
+
7
+ def viz(batch_name, df_plot, fingerprint_library=None, reference_Spectrums=None,
8
+ sensor='', download=False):
9
+ fig = go.Figure()
10
+ buttons = []
11
+ start = 0
12
+ end = start
13
+ visible_dict = {}
14
+ y_axis_dict = {}
15
+ for key_index, (key, plots) in enumerate(df_plot.items()):
16
+ df = plots[0]
17
+ y_axis_dict[key] = plots[1]
18
+ wavelengths = list(df.columns)[1:]
19
+ data = []
20
+ max_energy = df.iloc[:, 1:].max().max() # For plotting vertical lines
21
+ min_energy = df.iloc[:, 1:].min().min() # For plotting vertical lines
22
+ for index, row in df.iterrows():
23
+ sample_name = list(row)[0]
24
+ energy = list(row)[1:]
25
+ graph = go.Scatter(x=wavelengths, y=energy, name = sample_name, mode='lines',
26
+ visible=(key_index == 0),
27
+ # hoverinfo='x'
28
+ )
29
+ data.append(graph)
30
+ fig.add_trace(graph)
31
+ end += 1
32
+
33
+ # Visualisation for Reference Spectrum libraries
34
+ if reference_Spectrums:
35
+ ref_df = reference_Spectrums[key]
36
+ wavelengths = list(ref_df.columns)[1:]
37
+ for index, row in ref_df.iterrows():
38
+ polymer_name = list(row)[0]
39
+ energy = list(row)[1:]
40
+ graph = go.Scatter(x=wavelengths, y=energy, name = polymer_name, mode='lines',
41
+ visible=(key_index == 0),
42
+ # hoverinfo='x'
43
+ )
44
+ data.append(graph)
45
+ fig.add_trace(graph)
46
+ end += 1
47
+ # TODO: remove redundancy
48
+ max_energy = max(max_energy, ref_df.iloc[:, 1:].max().max()) # For plotting vertical lines
49
+ min_energy = min(min_energy, ref_df.iloc[:, 1:].min().min()) # For plotting vertical lines
50
+
51
+ # Visualisation for fingerprint libraries
52
+ if fingerprint_library is not None:
53
+ lib = fingerprint_library if sensor=='imaging' else fingerprint_library[fingerprint_library['sensor'] == sensor]
54
+ unique_groups = lib['polymer'].unique()
55
+ color_map = {group: px.colors.qualitative.Light24[i % len(px.colors.qualitative.Light24)] for i, group in enumerate(unique_groups)}
56
+ for index, row in lib.iterrows():
57
+ group_name = row['polymer']
58
+ colour = row['colour']
59
+ # Check if the row['wavelengths'] is a string of ranges
60
+ # If yes, then plot shaded area
61
+ if '-' in row['wavelengths']:
62
+ ranges = row['wavelengths'].strip('[]').split(',')
63
+ for i, r in enumerate(ranges):
64
+ r = r.strip()
65
+ if '-' in r:
66
+ start_range, end_range = map(float, r.split('-'))
67
+ shade = go.Scatter(
68
+ x=[start_range, start_range, end_range, end_range, start_range],
69
+ y=[min_energy, max_energy, max_energy, min_energy, min_energy],
70
+ fill='toself',
71
+ fillcolor='LightSkyBlue',
72
+ opacity=0.3,
73
+ mode='lines',
74
+ line={'color': 'LightSkyBlue'},
75
+ name=group_name,
76
+ legendgroup=group_name,
77
+ showlegend=True if i == 0 else False,
78
+ visible=(key_index == 0),
79
+ )
80
+ data.append(shade)
81
+ fig.add_trace(shade)
82
+ end += 1
83
+
84
+ else:
85
+ wavelengths = [float(x) for x in ast.literal_eval(row['wavelengths'])]
86
+ for i, x_value in enumerate(wavelengths):
87
+ line = go.Scatter(
88
+ x=[x_value]*2, # Duplicate x values for vertical lines
89
+ y=np.linspace(min_energy, max_energy, num=2).tolist(), # Alternate y values for vertical lines
90
+ mode='lines+text',
91
+ name=group_name,
92
+ line={'color': colour},
93
+ legendgroup=group_name,
94
+ # showlegend=False,
95
+ showlegend=True if i == 0 else False,
96
+ visible=(key_index == 0),
97
+ textposition="top left",
98
+ )
99
+ data.append(line)
100
+ fig.add_trace(line)
101
+ # Add a vertical line at x=2 using data coordinates
102
+
103
+ end += 1
104
+
105
+ visible_dict[key] = (start, end)
106
+ start = end
107
+
108
+
109
+
110
+
111
+ # Buttons for dropdown menu
112
+ for dd_key, limit in visible_dict.items():
113
+ visible = [False] * len(fig.data)
114
+ visible[limit[0] : limit[1]] = [True] * (limit[1]-limit[0])
115
+ buttons.append(dict(
116
+ label=dd_key,
117
+ method="update",
118
+ args=[{"visible": visible},
119
+ {'yaxis': {'title': y_axis_dict[dd_key]}},
120
+ {"title": f"{batch_name} : {sensor} - {dd_key}"}]
121
+ ))
122
+
123
+ # fig.add_annotation(textangle=-90)
124
+ # Add dropdown menu
125
+ fig.update_layout(
126
+ updatemenus=[go.layout.Updatemenu(
127
+ buttons=buttons,
128
+ direction="down",
129
+ pad={"r": 10, "t": 10},
130
+ showactive=False,
131
+ x=0.1,
132
+ xanchor="left",
133
+ y=1.1,
134
+ yanchor="top"
135
+ )]
136
+ )
137
+
138
+ text = batch_name + "_" + sensor
139
+ fig.update_layout(
140
+ # yaxis_range=[0, 1],
141
+ xaxis_title='Wavelength',
142
+ # yaxis_title='Reflectance',
143
+ title={
144
+ 'text': batch_name + " : " + sensor,
145
+ 'font': {
146
+ 'size': 24,
147
+ 'color': 'black',
148
+ 'family': 'Arial',
149
+ 'weight': 'bold'
150
+ },
151
+ 'x': 0.5,
152
+ 'xanchor': 'center'
153
+ },
154
+ )
155
+ fig.update_layout(hovermode="x unified")
156
+ fig.update_traces(textposition='top center')
157
+
158
+ if download:
159
+ fig.write_html(text + ".html")
160
+
161
+ fig.show()
162
+
163
+
@@ -0,0 +1,40 @@
1
+ import pytest
2
+ import numpy as np
3
+ import pandas as pd
4
+ import os
5
+ from src import dataloader
6
+
7
+ def test_is_float():
8
+ assert dataloader.is_float('1.23')
9
+ assert not dataloader.is_float('abc')
10
+
11
+ def test_find_xy_reflectance():
12
+ line = 'wavelength reflectance'
13
+ x, y = dataloader.find_xy(line)
14
+ assert x == 'wavelength' and y == 'reflectance'
15
+
16
+ def test_find_xy_absorbance():
17
+ line = 'wavenumber absorbance'
18
+ x, y = dataloader.find_xy(line)
19
+ assert x == 'wavenumber' and y == 'absorbance'
20
+
21
+ def test_load_data(tmp_path):
22
+ # Create a dummy reflectance file
23
+ file_path = tmp_path / 'test.txt'
24
+ with open(file_path, 'w') as f:
25
+ f.write('wavelength reflectance\n')
26
+ f.write('1000 0.1\n')
27
+ f.write('1100 0.2\n')
28
+ reflectance_df, absorbance_df = dataloader.load_data([str(file_path)], 'reflectance', 'wavelength reflectance')
29
+ assert reflectance_df is not None
30
+ assert absorbance_df is None
31
+ assert 'sample' in reflectance_df.columns
32
+
33
+ def test_load_refSpectrum(tmp_path):
34
+ file_path = tmp_path / 'ref.txt'
35
+ with open(file_path, 'w') as f:
36
+ f.write('sample 1000 0.1\n')
37
+ f.write('sample 1100 0.2\n')
38
+ df = dataloader.load_refSpectrum([str(file_path)])
39
+ assert df is not None
40
+ assert 'polymer' in df.columns or 'sample' in df.columns
@@ -0,0 +1,23 @@
1
+ import pytest
2
+ import numpy as np
3
+ import pandas as pd
4
+ from src import imaging_sensor
5
+
6
+ def test_imaging_sensor_init():
7
+ sensor = imaging_sensor.ImagingSensor()
8
+ assert hasattr(sensor, 'widget1')
9
+ assert hasattr(sensor, 'hylib_dict')
10
+ assert sensor.hylib_dict is None
11
+
12
+ def test_select_absorbance(monkeypatch):
13
+ sensor = imaging_sensor.ImagingSensor()
14
+ sensor.hylib_dict = {'dummy': pd.DataFrame(np.random.rand(2, 2))}
15
+ sensor.checkBoxAbsorbance = type('obj', (), {'isChecked': lambda self: True})()
16
+ sensor.sender = lambda: sensor.checkBoxAbsorbance
17
+ # Patch convert_absorbance to check if called
18
+ called = {}
19
+ def fake_convert():
20
+ called['yes'] = True
21
+ sensor.convert_absorbance = fake_convert
22
+ sensor.select_absorbance(Qt.Checked)
23
+ assert called.get('yes')
@@ -0,0 +1,50 @@
1
+ import pytest
2
+ import numpy as np
3
+ import pandas as pd
4
+ import os
5
+ from src import vizualization
6
+
7
+ def test_hex_rgba():
8
+ # Test the hex_rgba function for correct rgba output
9
+ hex_color = '#FF0000'
10
+ transparency = 0.5
11
+ expected = (255, 0, 0, 0.5)
12
+ result = vizualization.hex_rgba(hex_color, transparency)
13
+ assert result == expected
14
+
15
+ def test_create_masked_image(tmp_path, monkeypatch):
16
+ # Create dummy RGB and mask files
17
+ import cv2
18
+ rgb = np.ones((60, 60, 3), dtype=np.uint8) * 255
19
+ mask = np.zeros((10, 10, 1), dtype=np.uint8)
20
+ mask[2:8, 2:8, 0] = 1
21
+ rgb_path = tmp_path / 'RGB.png'
22
+ mask_path = tmp_path / 'mask.hdr'
23
+ cv2.imwrite(str(rgb_path), rgb)
24
+ # Patch io.load to return a dummy object with .data
25
+ class DummyMask:
26
+ def __init__(self, data):
27
+ self.data = data
28
+ monkeypatch.setattr(vizualization.io, 'load', lambda path: DummyMask(mask))
29
+ vizualization.create_masked_image(str(tmp_path))
30
+ assert os.path.exists(tmp_path / 'RGB_masked.png')
31
+
32
+ def test_viz(monkeypatch):
33
+ # Test viz function with minimal DataFrame
34
+ df = pd.DataFrame({
35
+ 'sample': ['A', 'B'],
36
+ 1000: [0.1, 0.2],
37
+ 1100: [0.2, 0.3]
38
+ })
39
+ df_plot = {'test': [df, 'Reflectance']}
40
+ # Patch fig.show to avoid opening browser
41
+ monkeypatch.setattr(vizualization.go.Figure, 'show', lambda self: None)
42
+ vizualization.viz('batch', df_plot, sensor='imaging', download=False)
43
+
44
+ def test_viz_image_data(monkeypatch, tmp_path):
45
+ # Test viz_image_data with dummy hylib_dict
46
+ df = pd.DataFrame(np.random.rand(5, 5), columns=[str(i) for i in range(5)])
47
+ hylib_dict = {'test': df}
48
+ # Patch fig.show to avoid opening browser
49
+ monkeypatch.setattr(vizualization.go.Figure, 'show', lambda self: None)
50
+ vizualization.viz_image_data('batch', hylib_dict, str(tmp_path), sensor='imaging', download=False)