advisor-scattering 0.5.2__py3-none-any.whl → 0.9.1__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.
- advisor/controllers/app_controller.py +2 -1
- advisor/domain/__init__.py +4 -0
- advisor/domain/core/lab.py +9 -2
- advisor/domain/core/lattice.py +2 -5
- advisor/domain/core/sample.py +9 -2
- advisor/domain/orientation.py +219 -0
- advisor/domain/orientation_calculator.py +174 -0
- advisor/features/__init__.py +7 -4
- advisor/features/scattering_geometry/domain/brillouin_calculator.py +133 -63
- advisor/features/scattering_geometry/domain/core.py +187 -134
- advisor/features/scattering_geometry/ui/components/hk_angles_components.py +175 -79
- advisor/features/scattering_geometry/ui/components/hkl_scan_components.py +42 -17
- advisor/features/scattering_geometry/ui/components/hkl_to_angles_components.py +175 -79
- advisor/features/scattering_geometry/ui/scattering_geometry_tab.py +57 -48
- advisor/ui/dialogs/__init__.py +7 -0
- advisor/ui/dialogs/diffraction_test_dialog.py +264 -0
- advisor/ui/init_window.py +33 -0
- {advisor_scattering-0.5.2.dist-info → advisor_scattering-0.9.1.dist-info}/METADATA +4 -12
- {advisor_scattering-0.5.2.dist-info → advisor_scattering-0.9.1.dist-info}/RECORD +22 -18
- {advisor_scattering-0.5.2.dist-info → advisor_scattering-0.9.1.dist-info}/WHEEL +1 -1
- {advisor_scattering-0.5.2.dist-info → advisor_scattering-0.9.1.dist-info}/entry_points.txt +0 -0
- {advisor_scattering-0.5.2.dist-info → advisor_scattering-0.9.1.dist-info}/top_level.txt +0 -0
|
@@ -15,9 +15,12 @@ from PyQt5.QtWidgets import (
|
|
|
15
15
|
QHeaderView,
|
|
16
16
|
QButtonGroup,
|
|
17
17
|
QMessageBox,
|
|
18
|
+
QDialog,
|
|
19
|
+
QScrollArea,
|
|
20
|
+
QFrame,
|
|
18
21
|
)
|
|
19
22
|
from PyQt5.QtCore import Qt, pyqtSignal
|
|
20
|
-
from PyQt5.QtGui import QColor, QBrush
|
|
23
|
+
from PyQt5.QtGui import QColor, QBrush, QFont
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
class HKAnglesControls(QWidget):
|
|
@@ -323,83 +326,102 @@ class HKAnglesControls(QWidget):
|
|
|
323
326
|
}
|
|
324
327
|
|
|
325
328
|
|
|
326
|
-
class
|
|
327
|
-
"""
|
|
329
|
+
class HKAnglesHistoryDialog(QDialog):
|
|
330
|
+
"""Dialog to display history of all HK angles calculation results."""
|
|
328
331
|
|
|
329
|
-
|
|
330
|
-
solutionSelected = pyqtSignal(dict)
|
|
331
|
-
|
|
332
|
-
def __init__(self, parent=None):
|
|
332
|
+
def __init__(self, history, parent=None):
|
|
333
333
|
super().__init__(parent)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
334
|
+
self.setWindowTitle("Calculation History (tth fixed)")
|
|
335
|
+
self.setMinimumSize(650, 400)
|
|
336
|
+
|
|
337
|
+
layout = QVBoxLayout(self)
|
|
338
|
+
|
|
339
|
+
# Create scroll area
|
|
340
|
+
scroll = QScrollArea()
|
|
341
|
+
scroll.setWidgetResizable(True)
|
|
342
|
+
scroll_content = QWidget()
|
|
343
|
+
scroll_layout = QVBoxLayout(scroll_content)
|
|
344
|
+
|
|
345
|
+
if not history:
|
|
346
|
+
no_history_label = QLabel("No history available.")
|
|
347
|
+
no_history_label.setAlignment(Qt.AlignCenter)
|
|
348
|
+
scroll_layout.addWidget(no_history_label)
|
|
349
|
+
else:
|
|
350
|
+
# Display history in reverse order (newest first)
|
|
351
|
+
for run_idx, entry in enumerate(reversed(history)):
|
|
352
|
+
run_num = len(history) - run_idx
|
|
353
|
+
run_frame = QFrame()
|
|
354
|
+
run_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
|
|
355
|
+
run_layout = QVBoxLayout(run_frame)
|
|
356
|
+
|
|
357
|
+
# Header with HKL values
|
|
358
|
+
header_label = QLabel(f"Run #{run_num}: H={entry['H']:.4f}, K={entry['K']:.4f}, L={entry['L']:.4f}")
|
|
359
|
+
header_font = QFont()
|
|
360
|
+
header_font.setBold(True)
|
|
361
|
+
header_label.setFont(header_font)
|
|
362
|
+
run_layout.addWidget(header_label)
|
|
363
|
+
|
|
364
|
+
# Number of solutions
|
|
365
|
+
num_solutions = entry.get('number_of_solutions', 1)
|
|
366
|
+
solutions_label = QLabel(f"Solutions found: {num_solutions}")
|
|
367
|
+
run_layout.addWidget(solutions_label)
|
|
368
|
+
|
|
369
|
+
# Create table for solutions
|
|
370
|
+
table = QTableWidget()
|
|
371
|
+
table.setColumnCount(5)
|
|
372
|
+
table.setHorizontalHeaderLabels(["#", "tth (°)", "θ (°)", "φ (°)", "χ (°)"])
|
|
373
|
+
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
374
|
+
table.verticalHeader().setVisible(False)
|
|
375
|
+
table.setMaximumHeight(100)
|
|
376
|
+
|
|
377
|
+
# Add rows for each solution
|
|
378
|
+
tth_list = entry.get('tth', [])
|
|
379
|
+
theta_list = entry.get('theta', [])
|
|
380
|
+
phi_list = entry.get('phi', [])
|
|
381
|
+
chi_list = entry.get('chi', [])
|
|
382
|
+
feasible_list = entry.get('feasible', [])
|
|
383
|
+
|
|
384
|
+
for i in range(len(tth_list)):
|
|
385
|
+
table.insertRow(i)
|
|
386
|
+
table.setItem(i, 0, QTableWidgetItem(f"{i + 1}"))
|
|
387
|
+
table.setItem(i, 1, QTableWidgetItem(f"{tth_list[i]:.2f}"))
|
|
388
|
+
table.setItem(i, 2, QTableWidgetItem(f"{theta_list[i]:.2f}"))
|
|
389
|
+
table.setItem(i, 3, QTableWidgetItem(f"{phi_list[i]:.2f}"))
|
|
390
|
+
table.setItem(i, 4, QTableWidgetItem(f"{chi_list[i]:.2f}"))
|
|
391
|
+
|
|
392
|
+
# Color based on feasibility
|
|
393
|
+
feasible = feasible_list[i] if i < len(feasible_list) else True
|
|
394
|
+
color = QColor(198, 239, 206) if feasible else QColor(255, 199, 206)
|
|
395
|
+
for col in range(5):
|
|
396
|
+
item = table.item(i, col)
|
|
397
|
+
if item:
|
|
398
|
+
item.setBackground(QBrush(color))
|
|
399
|
+
|
|
400
|
+
run_layout.addWidget(table)
|
|
401
|
+
scroll_layout.addWidget(run_frame)
|
|
402
|
+
|
|
403
|
+
scroll_layout.addStretch()
|
|
404
|
+
scroll.setWidget(scroll_content)
|
|
405
|
+
layout.addWidget(scroll)
|
|
406
|
+
|
|
407
|
+
# Close button
|
|
408
|
+
close_btn = QPushButton("Close")
|
|
409
|
+
close_btn.clicked.connect(self.accept)
|
|
410
|
+
layout.addWidget(close_btn)
|
|
393
411
|
|
|
394
412
|
|
|
395
413
|
class HKAnglesResultsWidget(QWidget):
|
|
396
|
-
"""
|
|
414
|
+
"""Results widget showing current solutions and history button."""
|
|
397
415
|
|
|
398
416
|
# Signal emitted when a solution is selected
|
|
399
417
|
solutionSelected = pyqtSignal(dict)
|
|
400
418
|
|
|
401
419
|
def __init__(self, parent=None):
|
|
402
420
|
super().__init__(parent)
|
|
421
|
+
|
|
422
|
+
# Store history of all results
|
|
423
|
+
self.history = []
|
|
424
|
+
self.current_result = None
|
|
403
425
|
|
|
404
426
|
# Main layout
|
|
405
427
|
layout = QVBoxLayout(self)
|
|
@@ -408,23 +430,97 @@ class HKAnglesResultsWidget(QWidget):
|
|
|
408
430
|
results_group = QGroupBox("Results")
|
|
409
431
|
results_layout = QVBoxLayout(results_group)
|
|
410
432
|
|
|
411
|
-
#
|
|
412
|
-
self.
|
|
413
|
-
self.
|
|
433
|
+
# Number of solutions label
|
|
434
|
+
self.solutions_label = QLabel("No results yet")
|
|
435
|
+
self.solutions_label.setAlignment(Qt.AlignCenter)
|
|
436
|
+
results_layout.addWidget(self.solutions_label)
|
|
437
|
+
|
|
438
|
+
# Current solutions display (table for up to 2 solutions)
|
|
439
|
+
self.results_table = QTableWidget()
|
|
440
|
+
self.results_table.setColumnCount(4)
|
|
441
|
+
self.results_table.setHorizontalHeaderLabels(["tth (°)", "θ (°)", "φ (°)", "χ (°)"])
|
|
442
|
+
self.results_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
443
|
+
self.results_table.verticalHeader().setVisible(False)
|
|
444
|
+
self.results_table.setMaximumHeight(90)
|
|
445
|
+
self.results_table.itemSelectionChanged.connect(self._on_selection_changed)
|
|
414
446
|
results_layout.addWidget(self.results_table)
|
|
415
447
|
|
|
416
|
-
#
|
|
417
|
-
self.
|
|
418
|
-
self.
|
|
419
|
-
self.
|
|
420
|
-
results_layout.addWidget(self.
|
|
448
|
+
# Show history button
|
|
449
|
+
self.history_button = QPushButton("Show History")
|
|
450
|
+
self.history_button.clicked.connect(self._show_history)
|
|
451
|
+
self.history_button.setObjectName("historyButton")
|
|
452
|
+
results_layout.addWidget(self.history_button)
|
|
421
453
|
|
|
422
454
|
layout.addWidget(results_group)
|
|
423
455
|
|
|
424
456
|
def display_results(self, results):
|
|
425
|
-
"""Display calculation results."""
|
|
426
|
-
|
|
457
|
+
"""Display current calculation results and add to history."""
|
|
458
|
+
if not results or not results.get("success", False):
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
self.current_result = results
|
|
462
|
+
|
|
463
|
+
# Add to history
|
|
464
|
+
self.history.append(results)
|
|
465
|
+
|
|
466
|
+
# Update solutions label
|
|
467
|
+
num_solutions = results.get('number_of_solutions', 1)
|
|
468
|
+
self.solutions_label.setText(f"Found {num_solutions} solution(s)")
|
|
469
|
+
|
|
470
|
+
# Clear and populate table
|
|
471
|
+
self.results_table.setRowCount(0)
|
|
472
|
+
|
|
473
|
+
tth_list = results.get('tth', [])
|
|
474
|
+
theta_list = results.get('theta', [])
|
|
475
|
+
phi_list = results.get('phi', [])
|
|
476
|
+
chi_list = results.get('chi', [])
|
|
477
|
+
feasible_list = results.get('feasible', [])
|
|
478
|
+
|
|
479
|
+
for i in range(len(tth_list)):
|
|
480
|
+
row = self.results_table.rowCount()
|
|
481
|
+
self.results_table.insertRow(row)
|
|
482
|
+
|
|
483
|
+
self.results_table.setItem(row, 0, QTableWidgetItem(f"{tth_list[i]:.2f}"))
|
|
484
|
+
self.results_table.setItem(row, 1, QTableWidgetItem(f"{theta_list[i]:.2f}"))
|
|
485
|
+
self.results_table.setItem(row, 2, QTableWidgetItem(f"{phi_list[i]:.2f}"))
|
|
486
|
+
self.results_table.setItem(row, 3, QTableWidgetItem(f"{chi_list[i]:.2f}"))
|
|
487
|
+
|
|
488
|
+
# Color based on feasibility
|
|
489
|
+
feasible = feasible_list[i] if i < len(feasible_list) else True
|
|
490
|
+
color = QColor(198, 239, 206) if feasible else QColor(255, 199, 206)
|
|
491
|
+
for col in range(4):
|
|
492
|
+
item = self.results_table.item(row, col)
|
|
493
|
+
if item:
|
|
494
|
+
item.setBackground(QBrush(color))
|
|
495
|
+
|
|
496
|
+
def _on_selection_changed(self):
|
|
497
|
+
"""Handle selection change in the table."""
|
|
498
|
+
current_row = self.results_table.currentRow()
|
|
499
|
+
if current_row >= 0 and self.current_result:
|
|
500
|
+
tth_list = self.current_result.get('tth', [])
|
|
501
|
+
theta_list = self.current_result.get('theta', [])
|
|
502
|
+
phi_list = self.current_result.get('phi', [])
|
|
503
|
+
chi_list = self.current_result.get('chi', [])
|
|
504
|
+
|
|
505
|
+
if current_row < len(tth_list):
|
|
506
|
+
selected_solution = {
|
|
507
|
+
'tth': tth_list[current_row],
|
|
508
|
+
'theta': theta_list[current_row],
|
|
509
|
+
'phi': phi_list[current_row],
|
|
510
|
+
'chi': chi_list[current_row],
|
|
511
|
+
'H': self.current_result.get('H'),
|
|
512
|
+
'K': self.current_result.get('K'),
|
|
513
|
+
'L': self.current_result.get('L'),
|
|
514
|
+
}
|
|
515
|
+
self.solutionSelected.emit(selected_solution)
|
|
516
|
+
|
|
517
|
+
def _show_history(self):
|
|
518
|
+
"""Show history dialog."""
|
|
519
|
+
dialog = HKAnglesHistoryDialog(self.history, self)
|
|
520
|
+
dialog.exec_()
|
|
427
521
|
|
|
428
522
|
def clear_results(self):
|
|
429
|
-
"""Clear
|
|
430
|
-
self.results_table.
|
|
523
|
+
"""Clear current results (but keep history)."""
|
|
524
|
+
self.results_table.setRowCount(0)
|
|
525
|
+
self.solutions_label.setText("No results yet")
|
|
526
|
+
self.current_result = None
|
|
@@ -373,25 +373,29 @@ class HKLScanControls(QWidget):
|
|
|
373
373
|
|
|
374
374
|
|
|
375
375
|
class HKLScanResultsTable(QTableWidget):
|
|
376
|
-
"""Table to display HKL scan results with multiple solutions."""
|
|
376
|
+
"""Table to display HKL scan results with multiple solutions per HKL point."""
|
|
377
377
|
|
|
378
378
|
def __init__(self, parent=None):
|
|
379
379
|
super().__init__(parent)
|
|
380
380
|
|
|
381
|
-
# Set up table
|
|
382
|
-
self.setColumnCount(
|
|
383
|
-
self.setHorizontalHeaderLabels(["H", "K", "L", "θ (°)", "φ (°)", "χ (°)", "β (°)"])
|
|
381
|
+
# Set up table - added Sol# column to show solution 1 or 2
|
|
382
|
+
self.setColumnCount(8)
|
|
383
|
+
self.setHorizontalHeaderLabels(["H", "K", "L", "Sol#", "θ (°)", "φ (°)", "χ (°)", "β (°)"])
|
|
384
384
|
self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
385
385
|
|
|
386
386
|
# Hide vertical header (row numbers)
|
|
387
387
|
self.verticalHeader().setVisible(False)
|
|
388
388
|
|
|
389
|
-
# Define colors for alternating groups
|
|
389
|
+
# Define colors for alternating HKL groups and solutions
|
|
390
390
|
self.group_colors = [
|
|
391
|
-
QColor(255, 255, 255), # White
|
|
392
|
-
QColor(230, 230, 240), #
|
|
393
|
-
QColor(166, 45, 45), # Red
|
|
391
|
+
QColor(255, 255, 255), # White - group 0
|
|
392
|
+
QColor(230, 230, 240), # Light gray - group 1
|
|
394
393
|
]
|
|
394
|
+
self.solution_colors = [
|
|
395
|
+
QColor(240, 248, 255), # Alice blue - solution 1
|
|
396
|
+
QColor(255, 250, 240), # Floral white - solution 2
|
|
397
|
+
]
|
|
398
|
+
self.infeasible_color = QColor(255, 199, 206) # Light red
|
|
395
399
|
|
|
396
400
|
# Enable sorting
|
|
397
401
|
self.setSortingEnabled(True)
|
|
@@ -411,7 +415,7 @@ class HKLScanResultsTable(QTableWidget):
|
|
|
411
415
|
self.last_results = None
|
|
412
416
|
|
|
413
417
|
def display_results(self, results):
|
|
414
|
-
"""Display results in the table."""
|
|
418
|
+
"""Display results in the table with visual separation for solution groups."""
|
|
415
419
|
self.setSortingEnabled(False) # Temporarily disable sorting
|
|
416
420
|
self.setRowCount(0) # Clear table
|
|
417
421
|
|
|
@@ -423,6 +427,7 @@ class HKLScanResultsTable(QTableWidget):
|
|
|
423
427
|
|
|
424
428
|
# Store results for later export
|
|
425
429
|
self.last_results = results
|
|
430
|
+
|
|
426
431
|
# Get data from results
|
|
427
432
|
h_values = results["H"]
|
|
428
433
|
k_values = results["K"]
|
|
@@ -431,8 +436,11 @@ class HKLScanResultsTable(QTableWidget):
|
|
|
431
436
|
theta_values = results["theta"]
|
|
432
437
|
phi_values = results["phi"]
|
|
433
438
|
chi_values = results["chi"]
|
|
439
|
+
feasible_values = results.get("feasible", [True] * len(h_values))
|
|
440
|
+
solution_groups = results.get("solution_group", list(range(len(h_values))))
|
|
441
|
+
solution_indices = results.get("solution_index", [1] * len(h_values))
|
|
434
442
|
|
|
435
|
-
# Add a row for each result
|
|
443
|
+
# Add a row for each result
|
|
436
444
|
for i in range(len(h_values)):
|
|
437
445
|
row_position = self.rowCount()
|
|
438
446
|
self.insertRow(row_position)
|
|
@@ -441,15 +449,25 @@ class HKLScanResultsTable(QTableWidget):
|
|
|
441
449
|
self.setItem(row_position, 0, QTableWidgetItem(f"{h_values[i]:.4f}"))
|
|
442
450
|
self.setItem(row_position, 1, QTableWidgetItem(f"{k_values[i]:.4f}"))
|
|
443
451
|
self.setItem(row_position, 2, QTableWidgetItem(f"{l_values[i]:.4f}"))
|
|
452
|
+
|
|
453
|
+
# Add solution number
|
|
454
|
+
sol_idx = solution_indices[i] if i < len(solution_indices) else 1
|
|
455
|
+
self.setItem(row_position, 3, QTableWidgetItem(f"{sol_idx}"))
|
|
444
456
|
|
|
445
457
|
# Add angle values
|
|
446
|
-
self.setItem(row_position,
|
|
447
|
-
self.setItem(row_position,
|
|
448
|
-
self.setItem(row_position,
|
|
449
|
-
self.setItem(row_position,
|
|
450
|
-
|
|
451
|
-
#
|
|
452
|
-
|
|
458
|
+
self.setItem(row_position, 4, QTableWidgetItem(f"{theta_values[i]:.1f}"))
|
|
459
|
+
self.setItem(row_position, 5, QTableWidgetItem(f"{phi_values[i]:.1f}"))
|
|
460
|
+
self.setItem(row_position, 6, QTableWidgetItem(f"{chi_values[i]:.1f}"))
|
|
461
|
+
self.setItem(row_position, 7, QTableWidgetItem(f"{tth_values[0]-theta_values[i]:.1f}"))
|
|
462
|
+
|
|
463
|
+
# Determine row color based on feasibility and group
|
|
464
|
+
if not feasible_values[i]:
|
|
465
|
+
row_color = self.infeasible_color
|
|
466
|
+
else:
|
|
467
|
+
# Alternate colors by HKL group
|
|
468
|
+
group_idx = solution_groups[i] if i < len(solution_groups) else i
|
|
469
|
+
row_color = self.group_colors[group_idx % 2]
|
|
470
|
+
|
|
453
471
|
for col in range(self.columnCount()):
|
|
454
472
|
item = self.item(row_position, col)
|
|
455
473
|
if item:
|
|
@@ -489,24 +507,31 @@ class HKLScanResultsTable(QTableWidget):
|
|
|
489
507
|
"H",
|
|
490
508
|
"K",
|
|
491
509
|
"L",
|
|
510
|
+
"Solution#",
|
|
492
511
|
"tth (deg)",
|
|
493
512
|
"theta (deg)",
|
|
494
513
|
"phi (deg)",
|
|
495
514
|
"chi (deg)",
|
|
515
|
+
"feasible",
|
|
496
516
|
]
|
|
497
517
|
)
|
|
498
518
|
|
|
499
519
|
# Write data
|
|
520
|
+
solution_indices = self.last_results.get("solution_index", [1] * len(self.last_results["tth"]))
|
|
521
|
+
feasible_values = self.last_results.get("feasible", [True] * len(self.last_results["tth"]))
|
|
522
|
+
|
|
500
523
|
for i in range(len(self.last_results["tth"])):
|
|
501
524
|
writer.writerow(
|
|
502
525
|
[
|
|
503
526
|
f"{self.last_results['H'][i]:.6f}",
|
|
504
527
|
f"{self.last_results['K'][i]:.6f}",
|
|
505
528
|
f"{self.last_results['L'][i]:.6f}",
|
|
529
|
+
f"{solution_indices[i]}",
|
|
506
530
|
f"{self.last_results['tth'][i]:.6f}",
|
|
507
531
|
f"{self.last_results['theta'][i]:.6f}",
|
|
508
532
|
f"{self.last_results['phi'][i]:.6f}",
|
|
509
533
|
f"{self.last_results['chi'][i]:.6f}",
|
|
534
|
+
f"{feasible_values[i]}",
|
|
510
535
|
]
|
|
511
536
|
)
|
|
512
537
|
|