pyckster 25.9.4__tar.gz → 25.10.1__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.
- {pyckster-25.9.4 → pyckster-25.10.1}/CHANGES.md +2 -0
- {pyckster-25.9.4/pyckster.egg-info → pyckster-25.10.1}/PKG-INFO +1 -1
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/__init__.py +1 -1
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/core.py +472 -96
- {pyckster-25.9.4 → pyckster-25.10.1/pyckster.egg-info}/PKG-INFO +1 -1
- {pyckster-25.9.4 → pyckster-25.10.1}/LICENCE +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/MANIFEST.in +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/README.md +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/images/pyckster.png +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/images/pyckster.svg +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/images/screenshot_01.png +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/__main__.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/bayesian_inversion.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/inversion_app.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/inversion_manager.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/inversion_visualizer.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/obspy_utils.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/pac_inversion.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/pyqtgraph_utils.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/surface_wave_analysis.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/surface_wave_profiling.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/sw_utils.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/tab_factory.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/visualization_utils.py +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/SOURCES.txt +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/dependency_links.txt +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/entry_points.txt +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/not-zip-safe +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/requires.txt +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/top_level.txt +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/setup.cfg +0 -0
- {pyckster-25.9.4 → pyckster-25.10.1}/setup.py +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
v25.10.1, Oct. 2025 -- Add bottom menu and coord display, fix picks loading (partly - still behaves weirdly with roll alongs), other small fixes
|
|
2
|
+
|
|
1
3
|
v25.9.4, Sep. 2025 -- Fix batch import and edit, fix view, add help, try icons
|
|
2
4
|
|
|
3
5
|
v25.9.3, Sep. 2025 -- Mouse and display controls improvements
|
|
@@ -15,7 +15,7 @@ except ImportError:
|
|
|
15
15
|
pass # matplotlib not available, that's fine
|
|
16
16
|
|
|
17
17
|
# Define version and metadata in one place
|
|
18
|
-
__version__ = "25.
|
|
18
|
+
__version__ = "25.10.1"
|
|
19
19
|
__author__ = "Sylvain Pasquet"
|
|
20
20
|
__email__ = "sylvain.pasquet@sorbonne-universite.fr"
|
|
21
21
|
__license__ = "GPLv3"
|
|
@@ -1004,6 +1004,12 @@ class MainWindow(QMainWindow):
|
|
|
1004
1004
|
|
|
1005
1005
|
vertSplitter.addWidget(plotContainer)
|
|
1006
1006
|
|
|
1007
|
+
# Create a container for the bottom plot widget and its menu bar
|
|
1008
|
+
bottomContainer = QWidget()
|
|
1009
|
+
bottomLayout = QVBoxLayout(bottomContainer)
|
|
1010
|
+
bottomLayout.setContentsMargins(0, 0, 0, 0)
|
|
1011
|
+
bottomLayout.setSpacing(0)
|
|
1012
|
+
|
|
1007
1013
|
# Create the bottom ViewBox for the acquisition setup / traveltimes view
|
|
1008
1014
|
self.bottomViewBox = CustomViewBox()
|
|
1009
1015
|
self.bottomViewBox.setBackgroundColor('w')
|
|
@@ -1015,7 +1021,15 @@ class MainWindow(QMainWindow):
|
|
|
1015
1021
|
|
|
1016
1022
|
# Add padding to the right side of the bottom plot
|
|
1017
1023
|
self.bottomPlotWidget.getPlotItem().layout.setContentsMargins(0, 0, 20, 0) # left, top, right, bottom
|
|
1018
|
-
|
|
1024
|
+
|
|
1025
|
+
# Add the plot widget to the container
|
|
1026
|
+
bottomLayout.addWidget(self.bottomPlotWidget)
|
|
1027
|
+
|
|
1028
|
+
# Create menu bar for bottom plot controls
|
|
1029
|
+
self.createBottomPlotMenuBar(bottomLayout)
|
|
1030
|
+
|
|
1031
|
+
# Add the bottom container to the vertical splitter
|
|
1032
|
+
vertSplitter.addWidget(bottomContainer)
|
|
1019
1033
|
|
|
1020
1034
|
# Set initial sizes for the splitters
|
|
1021
1035
|
horSplitter.setSizes([200, 800]) # Increased file list from 25 to 200, adjusted main area to 800
|
|
@@ -1035,14 +1049,14 @@ class MainWindow(QMainWindow):
|
|
|
1035
1049
|
|
|
1036
1050
|
# Set the title of the window
|
|
1037
1051
|
self.statusBar = QStatusBar(self)
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1052
|
+
|
|
1053
|
+
# Add coordinates label as permanent widget on the right side
|
|
1054
|
+
self.coordinatesLabel = QLabel("", self)
|
|
1055
|
+
self.coordinatesLabel.setMinimumWidth(200)
|
|
1056
|
+
self.coordinatesLabel.setAlignment(Qt.AlignCenter)
|
|
1057
|
+
self.coordinatesLabel.setStyleSheet("QLabel { border-left: 1px solid gray; padding: 2px; }")
|
|
1058
|
+
self.statusBar.addPermanentWidget(self.coordinatesLabel)
|
|
1059
|
+
|
|
1046
1060
|
self.setStatusBar(self.statusBar)
|
|
1047
1061
|
|
|
1048
1062
|
# Connect the mouseClickEvent signal to the handleAddPick slot
|
|
@@ -1057,6 +1071,9 @@ class MainWindow(QMainWindow):
|
|
|
1057
1071
|
# Connect the mouseClickEvent signal to the bottom plot
|
|
1058
1072
|
self.bottomPlotWidget.scene().sigMouseClicked.connect(self.bottomPlotClick)
|
|
1059
1073
|
|
|
1074
|
+
# Enable mouse tracking and connect mouse move events for coordinate display
|
|
1075
|
+
self.enableMouseTracking()
|
|
1076
|
+
|
|
1060
1077
|
# Add a QLabel to the MainWindow
|
|
1061
1078
|
self.label = QLabel(self)
|
|
1062
1079
|
|
|
@@ -1613,6 +1630,262 @@ class MainWindow(QMainWindow):
|
|
|
1613
1630
|
# Update the file list display initially
|
|
1614
1631
|
self.updateFileListDisplay()
|
|
1615
1632
|
|
|
1633
|
+
def createBottomPlotMenuBar(self, layout):
|
|
1634
|
+
"""Create a controls panel at the bottom of the bottom plot widget using seismo template"""
|
|
1635
|
+
# Create bottom controls panel (similar to wiggle controls)
|
|
1636
|
+
self.bottomControlsWidget = QWidget()
|
|
1637
|
+
self.bottomControlsWidget.setMaximumHeight(60) # Consistent with wiggle controls
|
|
1638
|
+
bottomControlsLayout = QHBoxLayout(self.bottomControlsWidget)
|
|
1639
|
+
bottomControlsLayout.setContentsMargins(10, 5, 10, 5)
|
|
1640
|
+
|
|
1641
|
+
# Helper function to add vertical separator
|
|
1642
|
+
def add_separator():
|
|
1643
|
+
separator = QFrame()
|
|
1644
|
+
separator.setFrameShape(QFrame.VLine)
|
|
1645
|
+
separator.setFrameShadow(QFrame.Sunken)
|
|
1646
|
+
separator.setLineWidth(1)
|
|
1647
|
+
separator.setMidLineWidth(0)
|
|
1648
|
+
bottomControlsLayout.addWidget(separator)
|
|
1649
|
+
|
|
1650
|
+
# Reset view button (matching seismo template)
|
|
1651
|
+
self.resetViewBottomButton = QPushButton("Reset View")
|
|
1652
|
+
self.resetViewBottomButton.clicked.connect(self.resetBottomView)
|
|
1653
|
+
self.resetViewBottomButton.setMinimumWidth(80)
|
|
1654
|
+
bottomControlsLayout.addWidget(self.resetViewBottomButton)
|
|
1655
|
+
|
|
1656
|
+
# Add separator after reset view button
|
|
1657
|
+
add_separator()
|
|
1658
|
+
|
|
1659
|
+
# View selection (matching seismo template style)
|
|
1660
|
+
bottomControlsLayout.addWidget(QLabel("View:"))
|
|
1661
|
+
self.bottomViewComboBox = QComboBox()
|
|
1662
|
+
self.bottomViewComboBox.addItems(["Layout View", "Traveltimes", "Topography"])
|
|
1663
|
+
self.bottomViewComboBox.setCurrentText("Layout View") # Default selection
|
|
1664
|
+
self.bottomViewComboBox.currentTextChanged.connect(self.onBottomViewChanged)
|
|
1665
|
+
bottomControlsLayout.addWidget(self.bottomViewComboBox)
|
|
1666
|
+
|
|
1667
|
+
# Add separator
|
|
1668
|
+
add_separator()
|
|
1669
|
+
|
|
1670
|
+
# Colormap control (only for Layout View, matching seismo template)
|
|
1671
|
+
self.colormapBottomLabel = QLabel("Colormap:")
|
|
1672
|
+
bottomControlsLayout.addWidget(self.colormapBottomLabel)
|
|
1673
|
+
self.timesColormapComboBox = QComboBox()
|
|
1674
|
+
self.populateTimesColormapMenu()
|
|
1675
|
+
bottomControlsLayout.addWidget(self.timesColormapComboBox)
|
|
1676
|
+
|
|
1677
|
+
# Add stretch to push everything to the left (matching template)
|
|
1678
|
+
bottomControlsLayout.addStretch()
|
|
1679
|
+
|
|
1680
|
+
# Add the controls widget to the layout
|
|
1681
|
+
layout.addWidget(self.bottomControlsWidget)
|
|
1682
|
+
|
|
1683
|
+
def populateTimesColormapMenu(self):
|
|
1684
|
+
"""Populate the times colormap combo box"""
|
|
1685
|
+
# Standard colormaps for times visualization
|
|
1686
|
+
colormaps = ['viridis', 'plasma', 'inferno', 'magma', 'cividis',
|
|
1687
|
+
'turbo', 'jet', 'hot', 'cool', 'spring', 'summer',
|
|
1688
|
+
'autumn', 'winter', 'copper', 'bone', 'gray',
|
|
1689
|
+
'RdYlBu', 'RdBu', 'coolwarm', 'seismic']
|
|
1690
|
+
|
|
1691
|
+
self.timesColormapComboBox.clear()
|
|
1692
|
+
for cmap in colormaps:
|
|
1693
|
+
self.timesColormapComboBox.addItem(cmap)
|
|
1694
|
+
|
|
1695
|
+
# Set default colormap (use existing colormap_str if available, otherwise default to plasma)
|
|
1696
|
+
default_colormap = getattr(self, 'colormap_str', 'plasma')
|
|
1697
|
+
self.timesColormapComboBox.setCurrentText(default_colormap)
|
|
1698
|
+
self.timesColormapComboBox.currentTextChanged.connect(self.onTimesColormapChanged)
|
|
1699
|
+
|
|
1700
|
+
def onBottomViewChanged(self, view_type):
|
|
1701
|
+
"""Handle bottom view type change from dropdown"""
|
|
1702
|
+
# Show/hide colormap controls based on view type
|
|
1703
|
+
if view_type == "Layout View":
|
|
1704
|
+
self.colormapBottomLabel.setVisible(True)
|
|
1705
|
+
self.timesColormapComboBox.setVisible(True)
|
|
1706
|
+
self.setPlotSetup()
|
|
1707
|
+
elif view_type == "Traveltimes":
|
|
1708
|
+
self.colormapBottomLabel.setVisible(False)
|
|
1709
|
+
self.timesColormapComboBox.setVisible(False)
|
|
1710
|
+
self.setPlotTravelTime()
|
|
1711
|
+
elif view_type == "Topography":
|
|
1712
|
+
self.colormapBottomLabel.setVisible(False)
|
|
1713
|
+
self.timesColormapComboBox.setVisible(False)
|
|
1714
|
+
self.setPlotTopo()
|
|
1715
|
+
|
|
1716
|
+
def resetBottomView(self):
|
|
1717
|
+
"""Reset the bottom view"""
|
|
1718
|
+
self.resetBottomLayoutView()
|
|
1719
|
+
|
|
1720
|
+
def onTimesColormapChanged(self, colormap_name):
|
|
1721
|
+
"""Handle times colormap change"""
|
|
1722
|
+
# Update the main colormap variable
|
|
1723
|
+
self.colormap_str = colormap_name
|
|
1724
|
+
self.colormap = pqg.colormap.get(self.colormap_str, source='matplotlib')
|
|
1725
|
+
# Show source info in status bar
|
|
1726
|
+
if self.streams and self.currentIndex is not None:
|
|
1727
|
+
self.statusBar.showMessage(f'FFID: {self.ffid[self.currentIndex]} | Source at {self.source_position[self.currentIndex]} m')
|
|
1728
|
+
# Refresh the plot if we're in layout view
|
|
1729
|
+
if hasattr(self, 'bottomViewComboBox') and self.bottomViewComboBox.currentText() == "Layout View":
|
|
1730
|
+
self.plotBottom()
|
|
1731
|
+
|
|
1732
|
+
def syncTimesColormapDropdown(self):
|
|
1733
|
+
"""Sync the times colormap dropdown with the current colormap_str"""
|
|
1734
|
+
if hasattr(self, 'timesColormapComboBox') and hasattr(self, 'colormap_str'):
|
|
1735
|
+
# Temporarily disconnect the signal to avoid infinite loop
|
|
1736
|
+
self.timesColormapComboBox.currentTextChanged.disconnect()
|
|
1737
|
+
self.timesColormapComboBox.setCurrentText(self.colormap_str)
|
|
1738
|
+
# Reconnect the signal
|
|
1739
|
+
self.timesColormapComboBox.currentTextChanged.connect(self.onTimesColormapChanged)
|
|
1740
|
+
|
|
1741
|
+
def resetBottomLayoutView(self):
|
|
1742
|
+
"""Reset the bottom layout view"""
|
|
1743
|
+
self.resetBottomView()
|
|
1744
|
+
if self.streams:
|
|
1745
|
+
self.plotBottom()
|
|
1746
|
+
|
|
1747
|
+
#######################################
|
|
1748
|
+
# Mouse tracking functions for coordinates display
|
|
1749
|
+
#######################################
|
|
1750
|
+
|
|
1751
|
+
def enableMouseTracking(self):
|
|
1752
|
+
"""Enable mouse tracking on plot widgets and connect signals"""
|
|
1753
|
+
# Enable mouse tracking on plot widgets
|
|
1754
|
+
self.plotWidget.setMouseTracking(True)
|
|
1755
|
+
self.bottomPlotWidget.setMouseTracking(True)
|
|
1756
|
+
|
|
1757
|
+
# Connect mouse move events to coordinate display
|
|
1758
|
+
self.plotWidget.scene().sigMouseMoved.connect(self.updateCoordinatesDisplay)
|
|
1759
|
+
self.bottomPlotWidget.scene().sigMouseMoved.connect(self.updateCoordinatesDisplay)
|
|
1760
|
+
|
|
1761
|
+
def updateCoordinatesDisplay(self, pos):
|
|
1762
|
+
"""Update coordinates display in status bar"""
|
|
1763
|
+
try:
|
|
1764
|
+
# Determine which plot the mouse is over
|
|
1765
|
+
scene = self.sender()
|
|
1766
|
+
if scene == self.plotWidget.scene():
|
|
1767
|
+
# Top plot (seismogram)
|
|
1768
|
+
view_coords = self.viewBox.mapSceneToView(pos)
|
|
1769
|
+
if hasattr(self, 'plotTypeX') and hasattr(self, 'currentIndex') and self.currentIndex is not None:
|
|
1770
|
+
if self.plotTypeX == 'trace_number':
|
|
1771
|
+
x_label = "Trace Number"
|
|
1772
|
+
x_value = f"{view_coords.x():.0f}"
|
|
1773
|
+
elif self.plotTypeX == 'trace_position':
|
|
1774
|
+
x_label = "Trace Position"
|
|
1775
|
+
x_value = f"{view_coords.x():.1f} m"
|
|
1776
|
+
# else:
|
|
1777
|
+
# x_label = "X"
|
|
1778
|
+
# x_value = f"{view_coords.x():.2f}"
|
|
1779
|
+
|
|
1780
|
+
y_label = "Time"
|
|
1781
|
+
y_value = f"{view_coords.y():.3f} s"
|
|
1782
|
+
|
|
1783
|
+
self.coordinatesLabel.setText(f"{x_label}: {x_value}, {y_label}: {y_value}")
|
|
1784
|
+
else:
|
|
1785
|
+
self.coordinatesLabel.setText(f"X: {view_coords.x():.2f}, Y: {view_coords.y():.3f}")
|
|
1786
|
+
|
|
1787
|
+
elif scene == self.bottomPlotWidget.scene():
|
|
1788
|
+
# Bottom plot (setup/layout/traveltime/topo)
|
|
1789
|
+
view_coords = self.bottomViewBox.mapSceneToView(pos)
|
|
1790
|
+
if hasattr(self, 'plotTypeX') and hasattr(self, 'currentIndex') and self.currentIndex is not None:
|
|
1791
|
+
|
|
1792
|
+
# X-axis label (common for all plot types)
|
|
1793
|
+
if self.plotTypeX == 'trace_number':
|
|
1794
|
+
x_label = "Trace Number"
|
|
1795
|
+
x_value = f"{view_coords.x():.0f}"
|
|
1796
|
+
elif self.plotTypeX == 'trace_position':
|
|
1797
|
+
x_label = "Trace Position"
|
|
1798
|
+
x_value = f"{view_coords.x():.1f} m"
|
|
1799
|
+
# else:
|
|
1800
|
+
# x_label = "X"
|
|
1801
|
+
# x_value = f"{view_coords.x():.2f}"
|
|
1802
|
+
|
|
1803
|
+
# Y-axis label and pick display depends on plot type
|
|
1804
|
+
if hasattr(self, 'bottomPlotType'):
|
|
1805
|
+
if self.bottomPlotType == 'traveltime':
|
|
1806
|
+
# Traveltime plot: Y = time
|
|
1807
|
+
y_label = "Time"
|
|
1808
|
+
y_value = f"{view_coords.y():.3f} s"
|
|
1809
|
+
self.coordinatesLabel.setText(f"{x_label}: {x_value}, {y_label}: {y_value}")
|
|
1810
|
+
|
|
1811
|
+
elif self.bottomPlotType == 'topo':
|
|
1812
|
+
# Topography plot: X = position, Y = elevation
|
|
1813
|
+
x_label = "Position"
|
|
1814
|
+
x_value = f"{view_coords.x():.1f} m"
|
|
1815
|
+
y_label = "Elevation"
|
|
1816
|
+
y_value = f"{view_coords.y():.1f} m"
|
|
1817
|
+
self.coordinatesLabel.setText(f"{x_label}: {x_value}, {y_label}: {y_value}")
|
|
1818
|
+
|
|
1819
|
+
else: # 'setup' plot
|
|
1820
|
+
# Setup plot: show pick time if available, otherwise show coordinates
|
|
1821
|
+
pick_time_text = ""
|
|
1822
|
+
try:
|
|
1823
|
+
# Get all positions and picks
|
|
1824
|
+
x_all, y_all, pick_all = self.getAllPositions()
|
|
1825
|
+
x_pick, y_pick, pick_values = self.getAllPicks(x_all, y_all, pick_all)
|
|
1826
|
+
|
|
1827
|
+
if len(x_pick) > 0:
|
|
1828
|
+
# Calculate distances to all picks
|
|
1829
|
+
pick_distances = []
|
|
1830
|
+
for i in range(len(x_pick)):
|
|
1831
|
+
dx = x_pick[i] - view_coords.x()
|
|
1832
|
+
dy = y_pick[i] - view_coords.y()
|
|
1833
|
+
distance = (dx**2 + dy**2)**0.5
|
|
1834
|
+
pick_distances.append(distance)
|
|
1835
|
+
|
|
1836
|
+
# Find closest pick within reasonable distance
|
|
1837
|
+
min_distance = min(pick_distances)
|
|
1838
|
+
|
|
1839
|
+
# Define tolerance based on view range (about 2% of the view range)
|
|
1840
|
+
x_range = self.bottomViewBox.viewRange()[0]
|
|
1841
|
+
y_range = self.bottomViewBox.viewRange()[1]
|
|
1842
|
+
x_tolerance = abs(x_range[1] - x_range[0]) * 0.02
|
|
1843
|
+
y_tolerance = abs(y_range[1] - y_range[0]) * 0.02
|
|
1844
|
+
tolerance = (x_tolerance**2 + y_tolerance**2)**0.5
|
|
1845
|
+
|
|
1846
|
+
if min_distance < tolerance:
|
|
1847
|
+
closest_idx = pick_distances.index(min_distance)
|
|
1848
|
+
pick_time = pick_values[closest_idx]
|
|
1849
|
+
pick_time_text = f", Pick: {pick_time:.3f} s"
|
|
1850
|
+
except Exception:
|
|
1851
|
+
# If there's any error finding picks, just show coordinates
|
|
1852
|
+
pass
|
|
1853
|
+
|
|
1854
|
+
# Y-axis label for setup plot
|
|
1855
|
+
if hasattr(self, 'plotTypeY'):
|
|
1856
|
+
if self.plotTypeY == 'ffid':
|
|
1857
|
+
y_label = "FFID"
|
|
1858
|
+
y_value = f"{view_coords.y():.0f}"
|
|
1859
|
+
elif self.plotTypeY == 'source_position':
|
|
1860
|
+
y_label = "Source Position"
|
|
1861
|
+
y_value = f"{view_coords.y():.1f} m"
|
|
1862
|
+
elif self.plotTypeY == 'offset':
|
|
1863
|
+
y_label = "Offset"
|
|
1864
|
+
y_value = f"{view_coords.y():.1f} m"
|
|
1865
|
+
else:
|
|
1866
|
+
y_label = "Y"
|
|
1867
|
+
y_value = f"{view_coords.y():.2f}"
|
|
1868
|
+
else:
|
|
1869
|
+
y_label = "Y"
|
|
1870
|
+
y_value = f"{view_coords.y():.2f}"
|
|
1871
|
+
|
|
1872
|
+
self.coordinatesLabel.setText(f"{x_label}: {x_value}, {y_label}: {y_value}{pick_time_text}")
|
|
1873
|
+
else:
|
|
1874
|
+
# Fallback if bottomPlotType not set
|
|
1875
|
+
self.coordinatesLabel.setText(f"X: {view_coords.x():.2f}, Y: {view_coords.y():.2f}")
|
|
1876
|
+
else:
|
|
1877
|
+
self.coordinatesLabel.setText(f"X: {view_coords.x():.2f}, Y: {view_coords.y():.2f}")
|
|
1878
|
+
else:
|
|
1879
|
+
# Clear coordinates if mouse is not over any plot
|
|
1880
|
+
self.coordinatesLabel.setText("")
|
|
1881
|
+
|
|
1882
|
+
except Exception as e:
|
|
1883
|
+
# If there's any error in coordinate calculation, just show basic coordinates
|
|
1884
|
+
try:
|
|
1885
|
+
self.coordinatesLabel.setText(f"X: {pos.x():.1f}, Y: {pos.y():.1f}")
|
|
1886
|
+
except:
|
|
1887
|
+
self.coordinatesLabel.setText("")
|
|
1888
|
+
|
|
1616
1889
|
def closeEvent(self, event):
|
|
1617
1890
|
"""
|
|
1618
1891
|
Show a confirmation dialog when the user tries to close the window.
|
|
@@ -2163,20 +2436,13 @@ class MainWindow(QMainWindow):
|
|
|
2163
2436
|
# Set the file update flag to True
|
|
2164
2437
|
self.update_file_flag = True
|
|
2165
2438
|
|
|
2166
|
-
# Get the
|
|
2167
|
-
|
|
2439
|
+
# Get the current row index directly (this handles duplicates properly)
|
|
2440
|
+
selected_row = self.fileListWidget.currentRow()
|
|
2168
2441
|
|
|
2169
|
-
# If
|
|
2170
|
-
if
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
# Find the index of the selected file path
|
|
2174
|
-
for index in range(self.fileListWidget.count()):
|
|
2175
|
-
# If the text of the item at the index is the same as the selected base name
|
|
2176
|
-
if self.fileListWidget.item(index).text() == selectedBaseName:
|
|
2177
|
-
self.currentFileName = self.fileNames[index] # Set the current file name
|
|
2178
|
-
self.currentIndex = index # Set the current index
|
|
2179
|
-
break
|
|
2442
|
+
# If a valid row is selected
|
|
2443
|
+
if selected_row >= 0 and selected_row < len(self.fileNames):
|
|
2444
|
+
self.currentFileName = self.fileNames[selected_row] # Set the current file name
|
|
2445
|
+
self.currentIndex = selected_row # Set the current index
|
|
2180
2446
|
|
|
2181
2447
|
# Plot the selected file
|
|
2182
2448
|
# Ensure plotting parameters are initialized for this file
|
|
@@ -2568,12 +2834,18 @@ class MainWindow(QMainWindow):
|
|
|
2568
2834
|
if fileNames_new:
|
|
2569
2835
|
# Set flag to prevent plotting during batch loading
|
|
2570
2836
|
was_empty_workspace = len(self.fileNames) == 0
|
|
2571
|
-
|
|
2837
|
+
# Enable batch loading for multiple files OR single files with multiple streams
|
|
2838
|
+
if was_empty_workspace and (len(fileNames_new) > 1):
|
|
2572
2839
|
self._batch_loading = True
|
|
2840
|
+
# For single files, we'll check for multiple streams after loading
|
|
2841
|
+
elif was_empty_workspace and len(fileNames_new) == 1:
|
|
2842
|
+
self._potential_batch_loading = True
|
|
2843
|
+
else:
|
|
2844
|
+
self._potential_batch_loading = False
|
|
2573
2845
|
|
|
2574
2846
|
progress = None
|
|
2575
2847
|
if len(fileNames_new) > 1:
|
|
2576
|
-
# Create and configure the progress dialog
|
|
2848
|
+
# Create and configure the progress dialog for multiple files
|
|
2577
2849
|
progress = QProgressDialog("Loading files...", "Cancel", 0, len(fileNames_new), self)
|
|
2578
2850
|
progress.setWindowTitle(f"Loading Files") # Explicitly set the window title
|
|
2579
2851
|
progress.setMinimumDuration(0) # Show immediately
|
|
@@ -2597,10 +2869,47 @@ class MainWindow(QMainWindow):
|
|
|
2597
2869
|
counter_files += 1
|
|
2598
2870
|
|
|
2599
2871
|
self.currentFileName = fileName
|
|
2600
|
-
self.loadFile() # Load the file
|
|
2601
2872
|
|
|
2873
|
+
# Show loading message in status bar (simple and reliable)
|
|
2874
|
+
if not progress: # Only if we don't have a multi-file progress dialog
|
|
2875
|
+
print(f"Showing status message for {fileName}") # Debug
|
|
2876
|
+
# Show loading message in status bar
|
|
2877
|
+
self.statusBar.showMessage(f"Loading {os.path.basename(fileName)}...")
|
|
2878
|
+
QApplication.processEvents()
|
|
2879
|
+
|
|
2880
|
+
self.loadFile() # Load the file (this is the slow part)
|
|
2881
|
+
|
|
2882
|
+
print("File loading completed") # Debug
|
|
2883
|
+
# Clear status bar message
|
|
2884
|
+
if not progress:
|
|
2885
|
+
self.statusBar.clearMessage()
|
|
2886
|
+
|
|
2887
|
+
# Check if we should enable batch loading for single file with multiple streams
|
|
2888
|
+
if hasattr(self, '_potential_batch_loading') and self._potential_batch_loading and len(self.stream) > 1:
|
|
2889
|
+
self._batch_loading = True
|
|
2890
|
+
self._potential_batch_loading = False
|
|
2891
|
+
|
|
2892
|
+
# Create progress bar for single file with multiple streams
|
|
2893
|
+
if progress is None: # Only if we don't already have a progress bar
|
|
2894
|
+
progress = QProgressDialog("Processing streams...", "Cancel", 0, len(self.stream), self)
|
|
2895
|
+
progress.setWindowTitle(f"Processing {len(self.stream)} streams from {os.path.basename(fileName)}")
|
|
2896
|
+
progress.setMinimumDuration(0) # Show immediately
|
|
2897
|
+
progress.setWindowModality(QtCore.Qt.WindowModal)
|
|
2898
|
+
progress.setValue(0)
|
|
2899
|
+
progress.show()
|
|
2900
|
+
progress.raise_() # Bring to front
|
|
2901
|
+
progress.activateWindow() # Activate the window
|
|
2902
|
+
QApplication.processEvents() # Ensure the dialog is displayed
|
|
2602
2903
|
|
|
2603
2904
|
for j in range(len(self.stream)):
|
|
2905
|
+
# Update progress for stream processing
|
|
2906
|
+
if progress:
|
|
2907
|
+
progress.setValue(j)
|
|
2908
|
+
if progress.wasCanceled():
|
|
2909
|
+
break
|
|
2910
|
+
progress.setLabelText(f"Processing stream {j+1} of {len(self.stream)}...")
|
|
2911
|
+
QApplication.processEvents()
|
|
2912
|
+
|
|
2604
2913
|
fileName_to_check = fileName
|
|
2605
2914
|
if len(self.stream) > 1:
|
|
2606
2915
|
fileName_to_check = fileName + f'_{j+1}'
|
|
@@ -2626,7 +2935,10 @@ class MainWindow(QMainWindow):
|
|
|
2626
2935
|
msg.setText(f'FFID set to {ffid}')
|
|
2627
2936
|
flag = False
|
|
2628
2937
|
else:
|
|
2938
|
+
# User declined FFID increment, skip this stream
|
|
2629
2939
|
continue
|
|
2940
|
+
|
|
2941
|
+
# Only proceed with loading if no FFID conflict or user approved increment
|
|
2630
2942
|
counter_stream += 1
|
|
2631
2943
|
if firstNewFile is None: # Get the first new file
|
|
2632
2944
|
firstNewFile = fileName_to_check
|
|
@@ -2645,6 +2957,11 @@ class MainWindow(QMainWindow):
|
|
|
2645
2957
|
self.airWaveItems.append([None,None,None]) # Append the air wave items to the list
|
|
2646
2958
|
|
|
2647
2959
|
self.loadStream() # Load the file
|
|
2960
|
+
|
|
2961
|
+
# Update progress after stream is fully loaded
|
|
2962
|
+
if progress and len(self.stream) > 1:
|
|
2963
|
+
progress.setValue(j + 1) # +1 because j is 0-indexed
|
|
2964
|
+
QApplication.processEvents()
|
|
2648
2965
|
|
|
2649
2966
|
else:
|
|
2650
2967
|
QMessageBox.information(self, "File Already Loaded", f"{os.path.basename(fileName)} is already loaded.")
|
|
@@ -2665,7 +2982,15 @@ class MainWindow(QMainWindow):
|
|
|
2665
2982
|
QMessageBox.information(self, "File Already Loaded", f"{os.path.basename(fileName)} is already loaded.")
|
|
2666
2983
|
|
|
2667
2984
|
if progress:
|
|
2668
|
-
|
|
2985
|
+
# Close progress dialog - handle both multi-file and multi-stream cases
|
|
2986
|
+
if len(fileNames_new) > 1:
|
|
2987
|
+
progress.setValue(len(fileNames_new)) # Multi-file case
|
|
2988
|
+
else:
|
|
2989
|
+
# Single file case - set to number of streams processed
|
|
2990
|
+
if hasattr(self, 'stream') and self.stream:
|
|
2991
|
+
progress.setValue(len(self.stream))
|
|
2992
|
+
else:
|
|
2993
|
+
progress.setValue(1) # Fallback for single stream
|
|
2669
2994
|
|
|
2670
2995
|
if counter_stream > 0:
|
|
2671
2996
|
if len(fileNames_new) > 1:
|
|
@@ -2674,7 +2999,11 @@ class MainWindow(QMainWindow):
|
|
|
2674
2999
|
else:
|
|
2675
3000
|
QMessageBox.information(self, "Files Loaded", f"{counter_files} file(s) successfully loaded")
|
|
2676
3001
|
else:
|
|
2677
|
-
|
|
3002
|
+
# Single file case - show stream count if multiple streams
|
|
3003
|
+
if counter_stream > 1:
|
|
3004
|
+
QMessageBox.information(self, "File Loaded", f"File '{os.path.basename(fileNames_new[0])}' loaded successfully.\n{counter_stream} streams loaded from this file.")
|
|
3005
|
+
else:
|
|
3006
|
+
QMessageBox.information(self, "File Loaded", f"File '{os.path.basename(fileNames_new[0])}' loaded successfully.")
|
|
2678
3007
|
elif counter_files > 0:
|
|
2679
3008
|
QMessageBox.information(self, "Files Loaded", f"{counter_files} file(s) successfully loaded")
|
|
2680
3009
|
else:
|
|
@@ -2695,6 +3024,10 @@ class MainWindow(QMainWindow):
|
|
|
2695
3024
|
self.fileListWidget.setCurrentRow(self.currentIndex)
|
|
2696
3025
|
self.plotSeismo()
|
|
2697
3026
|
self.plotBottom()
|
|
3027
|
+
|
|
3028
|
+
# Clean up potential batch loading flag
|
|
3029
|
+
if hasattr(self, '_potential_batch_loading'):
|
|
3030
|
+
self._potential_batch_loading = False
|
|
2698
3031
|
|
|
2699
3032
|
def loadFile(self):
|
|
2700
3033
|
# Show loading message in the status bar
|
|
@@ -4023,8 +4356,6 @@ class MainWindow(QMainWindow):
|
|
|
4023
4356
|
self.colormap_str = selected_cmap
|
|
4024
4357
|
self.mpl_pick_colormap = selected_cmap
|
|
4025
4358
|
|
|
4026
|
-
# Optionally, print or log the new colormap
|
|
4027
|
-
self.statusBar.showMessage(f"Selected colormap: {selected_cmap}", 2000)
|
|
4028
4359
|
self.update_pick_flag = True
|
|
4029
4360
|
self.plotBottom() # Refresh the plot with the new colormap
|
|
4030
4361
|
|
|
@@ -4051,7 +4382,6 @@ class MainWindow(QMainWindow):
|
|
|
4051
4382
|
# Sync wiggle control
|
|
4052
4383
|
if hasattr(self, 'maxTimeWiggleSpinbox'):
|
|
4053
4384
|
self.maxTimeWiggleSpinbox.setValue(self.max_time)
|
|
4054
|
-
self.statusBar.showMessage(f"Maximum time set to {self.max_time} s", 2000)
|
|
4055
4385
|
self.resetSeismoView()
|
|
4056
4386
|
|
|
4057
4387
|
else:
|
|
@@ -4080,7 +4410,6 @@ class MainWindow(QMainWindow):
|
|
|
4080
4410
|
# Sync visible control
|
|
4081
4411
|
if hasattr(self, 'gainWiggleSpinbox'):
|
|
4082
4412
|
self.gainWiggleSpinbox.setValue(self.gain)
|
|
4083
|
-
self.statusBar.showMessage(f"Gain set to {self.gain}", 2000)
|
|
4084
4413
|
self.plotSeismo()
|
|
4085
4414
|
|
|
4086
4415
|
else:
|
|
@@ -4451,7 +4780,6 @@ class MainWindow(QMainWindow):
|
|
|
4451
4780
|
# Sync visible control
|
|
4452
4781
|
if hasattr(self, 'fillWiggleCombo'):
|
|
4453
4782
|
self.fillWiggleCombo.setCurrentText('Positive')
|
|
4454
|
-
self.statusBar.showMessage('Filling positive amplitudes',1000)
|
|
4455
4783
|
if self.streams:
|
|
4456
4784
|
self.plotSeismo()
|
|
4457
4785
|
|
|
@@ -4469,7 +4797,6 @@ class MainWindow(QMainWindow):
|
|
|
4469
4797
|
# Sync visible control
|
|
4470
4798
|
if hasattr(self, 'fillWiggleCombo'):
|
|
4471
4799
|
self.fillWiggleCombo.setCurrentText('Negative')
|
|
4472
|
-
self.statusBar.showMessage('Filling negative amplitudes',1000)
|
|
4473
4800
|
if self.streams:
|
|
4474
4801
|
self.plotSeismo()
|
|
4475
4802
|
|
|
@@ -4487,7 +4814,6 @@ class MainWindow(QMainWindow):
|
|
|
4487
4814
|
# Sync visible control
|
|
4488
4815
|
if hasattr(self, 'fillWiggleCombo'):
|
|
4489
4816
|
self.fillWiggleCombo.setCurrentText('None')
|
|
4490
|
-
self.statusBar.showMessage('No fill',1000)
|
|
4491
4817
|
if self.streams:
|
|
4492
4818
|
self.plotSeismo()
|
|
4493
4819
|
|
|
@@ -4779,7 +5105,6 @@ class MainWindow(QMainWindow):
|
|
|
4779
5105
|
self.syncWiggleControls()
|
|
4780
5106
|
self.updateControlsForDisplayMode()
|
|
4781
5107
|
|
|
4782
|
-
self.statusBar.showMessage('Switching to wiggle plot',1000)
|
|
4783
5108
|
if self.streams:
|
|
4784
5109
|
self.update_file_flag = True
|
|
4785
5110
|
self.plotSeismo()
|
|
@@ -4794,7 +5119,6 @@ class MainWindow(QMainWindow):
|
|
|
4794
5119
|
self.syncWiggleControls()
|
|
4795
5120
|
self.updateControlsForDisplayMode()
|
|
4796
5121
|
|
|
4797
|
-
self.statusBar.showMessage('Switching to wiggle plot',1000)
|
|
4798
5122
|
if self.streams:
|
|
4799
5123
|
self.update_file_flag = True
|
|
4800
5124
|
self.plotSeismo()
|
|
@@ -4810,7 +5134,6 @@ class MainWindow(QMainWindow):
|
|
|
4810
5134
|
self.syncWiggleControls()
|
|
4811
5135
|
self.updateControlsForDisplayMode()
|
|
4812
5136
|
|
|
4813
|
-
self.statusBar.showMessage('Switching to image plot',1000)
|
|
4814
5137
|
if self.streams:
|
|
4815
5138
|
self.update_file_flag = True
|
|
4816
5139
|
self.plotSeismo()
|
|
@@ -4825,7 +5148,6 @@ class MainWindow(QMainWindow):
|
|
|
4825
5148
|
self.syncWiggleControls()
|
|
4826
5149
|
self.updateControlsForDisplayMode()
|
|
4827
5150
|
|
|
4828
|
-
self.statusBar.showMessage('Switching to image plot',1000)
|
|
4829
5151
|
if self.streams:
|
|
4830
5152
|
self.update_file_flag = True
|
|
4831
5153
|
self.plotSeismo()
|
|
@@ -4874,7 +5196,12 @@ class MainWindow(QMainWindow):
|
|
|
4874
5196
|
self.bottomPlotSetupAction.setChecked(False)
|
|
4875
5197
|
self.bottomPlotTopographyAction.setChecked(False)
|
|
4876
5198
|
self.bottomPlotTravelTimeAction.setChecked(True)
|
|
4877
|
-
|
|
5199
|
+
# Sync with dropdown
|
|
5200
|
+
if hasattr(self, 'bottomViewComboBox'):
|
|
5201
|
+
self.bottomViewComboBox.setCurrentText("Traveltimes")
|
|
5202
|
+
# Show source info in status bar
|
|
5203
|
+
if self.streams and self.currentIndex is not None:
|
|
5204
|
+
self.statusBar.showMessage(f'FFID: {self.ffid[self.currentIndex]} | Source at {self.source_position[self.currentIndex]} m')
|
|
4878
5205
|
if self.streams:
|
|
4879
5206
|
self.update_file_flag = True
|
|
4880
5207
|
self.plotBottom()
|
|
@@ -4884,7 +5211,12 @@ class MainWindow(QMainWindow):
|
|
|
4884
5211
|
self.bottomPlotTravelTimeAction.setChecked(False)
|
|
4885
5212
|
self.bottomPlotTopographyAction.setChecked(False)
|
|
4886
5213
|
self.bottomPlotSetupAction.setChecked(True)
|
|
4887
|
-
|
|
5214
|
+
# Sync with dropdown
|
|
5215
|
+
if hasattr(self, 'bottomViewComboBox'):
|
|
5216
|
+
self.bottomViewComboBox.setCurrentText("Layout View")
|
|
5217
|
+
# Show source info in status bar
|
|
5218
|
+
if self.streams and self.currentIndex is not None:
|
|
5219
|
+
self.statusBar.showMessage(f'FFID: {self.ffid[self.currentIndex]} | Source at {self.source_position[self.currentIndex]} m')
|
|
4888
5220
|
if self.streams:
|
|
4889
5221
|
self.update_file_flag = True
|
|
4890
5222
|
self.plotBottom()
|
|
@@ -4894,7 +5226,12 @@ class MainWindow(QMainWindow):
|
|
|
4894
5226
|
self.bottomPlotTravelTimeAction.setChecked(False)
|
|
4895
5227
|
self.bottomPlotSetupAction.setChecked(False)
|
|
4896
5228
|
self.bottomPlotTopographyAction.setChecked(True)
|
|
4897
|
-
|
|
5229
|
+
# Sync with dropdown
|
|
5230
|
+
if hasattr(self, 'bottomViewComboBox'):
|
|
5231
|
+
self.bottomViewComboBox.setCurrentText("Topography")
|
|
5232
|
+
# Show source info in status bar
|
|
5233
|
+
if self.streams and self.currentIndex is not None:
|
|
5234
|
+
self.statusBar.showMessage(f'FFID: {self.ffid[self.currentIndex]} | Source at {self.source_position[self.currentIndex]} m')
|
|
4898
5235
|
if self.streams:
|
|
4899
5236
|
self.update_file_flag = True
|
|
4900
5237
|
self.plotBottom()
|
|
@@ -4906,7 +5243,6 @@ class MainWindow(QMainWindow):
|
|
|
4906
5243
|
# Sync wiggle control
|
|
4907
5244
|
if hasattr(self, 'plotTracesWiggleCombo'):
|
|
4908
5245
|
self.plotTracesWiggleCombo.setCurrentText("Position")
|
|
4909
|
-
self.statusBar.showMessage('Switching to trace position',1000)
|
|
4910
5246
|
if len(self.streams[self.currentIndex]) == 1:
|
|
4911
5247
|
self.mean_dg = 1
|
|
4912
5248
|
else:
|
|
@@ -4919,7 +5255,6 @@ class MainWindow(QMainWindow):
|
|
|
4919
5255
|
|
|
4920
5256
|
# def setFileTraceNumber(self):
|
|
4921
5257
|
# self.plotTypeX = 'file_trace_number'
|
|
4922
|
-
# self.statusBar.showMessage('Switching to file trace number',1000)
|
|
4923
5258
|
# self.mean_dg = 1
|
|
4924
5259
|
# self.x_label = 'Trace number in file'
|
|
4925
5260
|
# if self.streams:
|
|
@@ -4933,7 +5268,6 @@ class MainWindow(QMainWindow):
|
|
|
4933
5268
|
# Sync wiggle control
|
|
4934
5269
|
if hasattr(self, 'plotTracesWiggleCombo'):
|
|
4935
5270
|
self.plotTracesWiggleCombo.setCurrentText("Number")
|
|
4936
|
-
self.statusBar.showMessage('Switching to trace number',1000)
|
|
4937
5271
|
self.mean_dg = 1
|
|
4938
5272
|
self.x_label = 'Trace Number'
|
|
4939
5273
|
if self.streams:
|
|
@@ -4949,7 +5283,6 @@ class MainWindow(QMainWindow):
|
|
|
4949
5283
|
# Sync wiggle control
|
|
4950
5284
|
if hasattr(self, 'plotSourcesWiggleCombo'):
|
|
4951
5285
|
self.plotSourcesWiggleCombo.setCurrentText("Position")
|
|
4952
|
-
self.statusBar.showMessage('Switching to source position',1000)
|
|
4953
5286
|
if len(self.streams) == 1:
|
|
4954
5287
|
self.mean_ds = 1
|
|
4955
5288
|
else:
|
|
@@ -4967,7 +5300,6 @@ class MainWindow(QMainWindow):
|
|
|
4967
5300
|
# Sync wiggle control
|
|
4968
5301
|
if hasattr(self, 'plotSourcesWiggleCombo'):
|
|
4969
5302
|
self.plotSourcesWiggleCombo.setCurrentText("FFID")
|
|
4970
|
-
self.statusBar.showMessage('Switching to FFID',1000)
|
|
4971
5303
|
self.mean_ds = 1
|
|
4972
5304
|
self.y_label = 'FFID'
|
|
4973
5305
|
if self.streams:
|
|
@@ -4982,7 +5314,6 @@ class MainWindow(QMainWindow):
|
|
|
4982
5314
|
# Sync wiggle control
|
|
4983
5315
|
if hasattr(self, 'plotSourcesWiggleCombo'):
|
|
4984
5316
|
self.plotSourcesWiggleCombo.setCurrentText("Offset")
|
|
4985
|
-
self.statusBar.showMessage('Switching to offset',1000)
|
|
4986
5317
|
if len(self.streams) == 1:
|
|
4987
5318
|
self.mean_ds = 1
|
|
4988
5319
|
else:
|
|
@@ -5233,7 +5564,8 @@ class MainWindow(QMainWindow):
|
|
|
5233
5564
|
fillLevel=fillLevel, fillBrush=self.fill_brush)
|
|
5234
5565
|
|
|
5235
5566
|
# Plot the picks
|
|
5236
|
-
if not np.isnan(self.picks[self.currentIndex][i])
|
|
5567
|
+
if (not np.isnan(self.picks[self.currentIndex][i]) and
|
|
5568
|
+
self.pickSeismoItems[self.currentIndex][i] is not None):
|
|
5237
5569
|
self.updatePickPosition(i)
|
|
5238
5570
|
scatter = self.pickSeismoItems[self.currentIndex][i]
|
|
5239
5571
|
self.plotWidget.addItem(scatter)
|
|
@@ -5302,7 +5634,8 @@ class MainWindow(QMainWindow):
|
|
|
5302
5634
|
|
|
5303
5635
|
# Plot the picks
|
|
5304
5636
|
for i in range(len(self.streams[self.currentIndex])):
|
|
5305
|
-
if not np.isnan(self.picks[self.currentIndex][i])
|
|
5637
|
+
if (not np.isnan(self.picks[self.currentIndex][i]) and
|
|
5638
|
+
self.pickSeismoItems[self.currentIndex][i] is not None):
|
|
5306
5639
|
self.updatePickPosition(i)
|
|
5307
5640
|
scatter = self.pickSeismoItems[self.currentIndex][i]
|
|
5308
5641
|
self.plotWidget.addItem(scatter)
|
|
@@ -6230,8 +6563,43 @@ class MainWindow(QMainWindow):
|
|
|
6230
6563
|
else:
|
|
6231
6564
|
QMessageBox.warning(self, "No File Saved", "No file was saved!")
|
|
6232
6565
|
|
|
6566
|
+
class TraceNotFoundDialog(QDialog):
|
|
6567
|
+
"""Custom dialog for handling trace not found with 'Yes to All' option"""
|
|
6568
|
+
def __init__(self, parent, message):
|
|
6569
|
+
super().__init__(parent)
|
|
6570
|
+
self.setWindowTitle("Trace Not Found")
|
|
6571
|
+
self.setModal(True)
|
|
6572
|
+
|
|
6573
|
+
layout = QVBoxLayout(self)
|
|
6574
|
+
|
|
6575
|
+
# Add the message
|
|
6576
|
+
message_label = QLabel(message)
|
|
6577
|
+
layout.addWidget(message_label)
|
|
6578
|
+
|
|
6579
|
+
# Add checkbox for "Don't show this again"
|
|
6580
|
+
self.dont_show_checkbox = QCheckBox("Don't show this message again for remaining traces")
|
|
6581
|
+
layout.addWidget(self.dont_show_checkbox)
|
|
6582
|
+
|
|
6583
|
+
# Add buttons
|
|
6584
|
+
button_layout = QHBoxLayout()
|
|
6585
|
+
self.ok_button = QPushButton("OK")
|
|
6586
|
+
self.ok_button.clicked.connect(self.accept)
|
|
6587
|
+
button_layout.addWidget(self.ok_button)
|
|
6588
|
+
|
|
6589
|
+
layout.addLayout(button_layout)
|
|
6590
|
+
|
|
6591
|
+
# Set focus on OK button
|
|
6592
|
+
self.ok_button.setDefault(True)
|
|
6593
|
+
self.ok_button.setFocus()
|
|
6594
|
+
|
|
6595
|
+
def dont_show_again(self):
|
|
6596
|
+
return self.dont_show_checkbox.isChecked()
|
|
6597
|
+
|
|
6233
6598
|
def loadPicks(self, fname=None, verbose=False):
|
|
6234
6599
|
# Load picks from a pygimli .sgt file
|
|
6600
|
+
|
|
6601
|
+
# Flag to control whether to show trace not found dialogs
|
|
6602
|
+
skip_trace_not_found_dialogs = False
|
|
6235
6603
|
|
|
6236
6604
|
# The first argument returned is the filename and path
|
|
6237
6605
|
if fname is None or not fname:
|
|
@@ -6324,8 +6692,6 @@ class MainWindow(QMainWindow):
|
|
|
6324
6692
|
error = float(parts[err_ind])
|
|
6325
6693
|
uploaded_picks.append((source, trace, pick, error))
|
|
6326
6694
|
|
|
6327
|
-
self.statusBar.showMessage(f'Picking loaded from: {fname}.', 10000)
|
|
6328
|
-
|
|
6329
6695
|
if self.currentFileName is not None:
|
|
6330
6696
|
# Get current file index
|
|
6331
6697
|
n_picks_total = 0
|
|
@@ -6341,6 +6707,13 @@ class MainWindow(QMainWindow):
|
|
|
6341
6707
|
progress.show()
|
|
6342
6708
|
QApplication.processEvents() # Ensure the dialog is displayed
|
|
6343
6709
|
|
|
6710
|
+
# SGT files can be organized by shot order or by station order
|
|
6711
|
+
# We need to match picks based on source positions in the SGT geometry section
|
|
6712
|
+
print("SGT file detected - matching picks by source position from geometry section")
|
|
6713
|
+
|
|
6714
|
+
# Create a mapping from source positions to source indices in the SGT file
|
|
6715
|
+
sgt_source_positions = [station[0] for station in uploaded_stations]
|
|
6716
|
+
|
|
6344
6717
|
# Loop over files in self.fileNames
|
|
6345
6718
|
for i, _ in enumerate(self.fileNames):
|
|
6346
6719
|
# Update the progress dialog
|
|
@@ -6353,19 +6726,24 @@ class MainWindow(QMainWindow):
|
|
|
6353
6726
|
# Loop over uploaded picks
|
|
6354
6727
|
if source is not None:
|
|
6355
6728
|
|
|
6356
|
-
# Find the source index in the
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6729
|
+
# Find the source index in the SGT geometry section by matching positions
|
|
6730
|
+
source_index_in_sgt = None
|
|
6731
|
+
for idx, sgt_source_pos in enumerate(sgt_source_positions):
|
|
6732
|
+
if abs(sgt_source_pos - source) < 0.01: # Small tolerance for floating point comparison
|
|
6733
|
+
source_index_in_sgt = idx + 1 # SGT source numbers are 1-indexed
|
|
6734
|
+
break
|
|
6735
|
+
|
|
6736
|
+
if source_index_in_sgt is not None:
|
|
6737
|
+
# Find picks for this source in the SGT file
|
|
6738
|
+
up_picks_tmp = [pick for pick in uploaded_picks if pick[0] == source_index_in_sgt]
|
|
6739
|
+
else:
|
|
6740
|
+
up_picks_tmp = []
|
|
6741
|
+
if verbose:
|
|
6742
|
+
print(f"Warning: Source at position {source} not found in SGT geometry section")
|
|
6366
6743
|
|
|
6367
|
-
# Unpack the picks to get the
|
|
6368
|
-
|
|
6744
|
+
# Unpack the picks to get the geophone numbers, picks and errors
|
|
6745
|
+
# Note: In SGT files, pick[1] is the geophone number (1-indexed) referring to coordinate list
|
|
6746
|
+
sgt_geophone_numbers = [int(pick[1]) for pick in up_picks_tmp]
|
|
6369
6747
|
picks = [pick[2] for pick in up_picks_tmp]
|
|
6370
6748
|
errors = [pick[3] for pick in up_picks_tmp]
|
|
6371
6749
|
|
|
@@ -6381,33 +6759,38 @@ class MainWindow(QMainWindow):
|
|
|
6381
6759
|
# Access the appropriate attribute based on self.plotTypeX (shot_trace_number, file_trace_number, trace_position)
|
|
6382
6760
|
plot_data_x = self.plotTypeDict.get(self.plotTypeX, [])
|
|
6383
6761
|
|
|
6384
|
-
for
|
|
6385
|
-
# Get
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
trace_indices_source = np.where(np.array(self.trace_position[i]) == trace)[0]
|
|
6390
|
-
if trace_indices_source.size == 0:
|
|
6391
|
-
QMessageBox.warning(self, "Trace Not Found", f"Trace {trace} not found in trace_position for source {i}")
|
|
6392
|
-
continue
|
|
6393
|
-
|
|
6394
|
-
trace_index_source = trace_indices_source[0]
|
|
6395
|
-
|
|
6396
|
-
scatter1 = pqg.ScatterPlotItem(x=[plot_data_x[i][trace_index_source]],
|
|
6397
|
-
y=[pick], pen='r', symbol='+')
|
|
6398
|
-
|
|
6399
|
-
if i == self.currentIndex:
|
|
6400
|
-
if ~np.isnan(self.picks[i][trace_index_source]):
|
|
6401
|
-
self.plotWidget.removeItem(self.pickSeismoItems[i][trace_index_source])
|
|
6762
|
+
for sgt_geophone_number, pick, error in zip(sgt_geophone_numbers, picks, errors):
|
|
6763
|
+
# Get the geophone position from the SGT coordinate list
|
|
6764
|
+
# SGT geophone numbers are 1-indexed, so subtract 1 for 0-indexed array
|
|
6765
|
+
if 1 <= sgt_geophone_number <= len(uploaded_stations):
|
|
6766
|
+
geophone_position = uploaded_stations[sgt_geophone_number - 1][0] # x-coordinate
|
|
6402
6767
|
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6768
|
+
# Find this geophone position in the current shot's trace positions
|
|
6769
|
+
trace_index_in_shot = None
|
|
6770
|
+
for trace_idx, trace_pos in enumerate(self.trace_position[i]):
|
|
6771
|
+
if abs(trace_pos - geophone_position) < 0.01: # Small tolerance for floating point
|
|
6772
|
+
trace_index_in_shot = trace_idx
|
|
6773
|
+
break
|
|
6774
|
+
|
|
6775
|
+
if trace_index_in_shot is not None:
|
|
6776
|
+
# Create scatter plot item for the pick
|
|
6777
|
+
scatter1 = pqg.ScatterPlotItem(x=[plot_data_x[i][trace_index_in_shot]],
|
|
6778
|
+
y=[pick], pen='r', symbol='+')
|
|
6779
|
+
|
|
6780
|
+
if i == self.currentIndex:
|
|
6781
|
+
if ~np.isnan(self.picks[i][trace_index_in_shot]):
|
|
6782
|
+
self.plotWidget.removeItem(self.pickSeismoItems[i][trace_index_in_shot])
|
|
6783
|
+
|
|
6784
|
+
self.plotWidget.addItem(scatter1)
|
|
6785
|
+
|
|
6786
|
+
self.pickSeismoItems[i][trace_index_in_shot] = scatter1
|
|
6787
|
+
self.picks[i][trace_index_in_shot] = pick
|
|
6788
|
+
self.error[i][trace_index_in_shot] = error
|
|
6789
|
+
|
|
6790
|
+
if pick > max_picked_time:
|
|
6791
|
+
max_picked_time = pick
|
|
6792
|
+
# If trace not found in this shot, simply skip (normal for roll-along surveys)
|
|
6793
|
+
# If geophone number is out of range, silently skip (invalid SGT data)
|
|
6411
6794
|
|
|
6412
6795
|
progress.setValue(len(self.fileNames)) # Set progress to maximum
|
|
6413
6796
|
|
|
@@ -6442,8 +6825,6 @@ class MainWindow(QMainWindow):
|
|
|
6442
6825
|
self.update_pick_flag = True
|
|
6443
6826
|
self.removeColorBar()
|
|
6444
6827
|
self.plotBottom()
|
|
6445
|
-
else:
|
|
6446
|
-
self.statusBar.showMessage('Keeping picks safe', 2000)
|
|
6447
6828
|
|
|
6448
6829
|
def clearCurrentPicks(self):
|
|
6449
6830
|
# Show warning message box
|
|
@@ -6462,8 +6843,6 @@ class MainWindow(QMainWindow):
|
|
|
6462
6843
|
self.pickSeismoItems[self.currentIndex][i] = None
|
|
6463
6844
|
self.update_pick_flag = True
|
|
6464
6845
|
self.plotBottom()
|
|
6465
|
-
else:
|
|
6466
|
-
self.statusBar.showMessage('Keeping picks safe', 2000)
|
|
6467
6846
|
|
|
6468
6847
|
def clearPicksAboveBelowThreshold(self):
|
|
6469
6848
|
if self.streams:
|
|
@@ -7266,9 +7645,6 @@ class MainWindow(QMainWindow):
|
|
|
7266
7645
|
|
|
7267
7646
|
# Save the figure
|
|
7268
7647
|
plt.savefig(final_fname, format=format_type, dpi=self.mpl_dpi, bbox_inches='tight')
|
|
7269
|
-
|
|
7270
|
-
# Display message
|
|
7271
|
-
self.statusBar.showMessage(f'Figure saved at: {final_fname}', 10000)
|
|
7272
7648
|
|
|
7273
7649
|
def exportSeismoPlot(self):
|
|
7274
7650
|
self.exportMplPlot('seismo')
|
|
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
|
|
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
|
|
File without changes
|