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.
Files changed (32) hide show
  1. {pyckster-25.9.4 → pyckster-25.10.1}/CHANGES.md +2 -0
  2. {pyckster-25.9.4/pyckster.egg-info → pyckster-25.10.1}/PKG-INFO +1 -1
  3. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/__init__.py +1 -1
  4. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/core.py +472 -96
  5. {pyckster-25.9.4 → pyckster-25.10.1/pyckster.egg-info}/PKG-INFO +1 -1
  6. {pyckster-25.9.4 → pyckster-25.10.1}/LICENCE +0 -0
  7. {pyckster-25.9.4 → pyckster-25.10.1}/MANIFEST.in +0 -0
  8. {pyckster-25.9.4 → pyckster-25.10.1}/README.md +0 -0
  9. {pyckster-25.9.4 → pyckster-25.10.1}/images/pyckster.png +0 -0
  10. {pyckster-25.9.4 → pyckster-25.10.1}/images/pyckster.svg +0 -0
  11. {pyckster-25.9.4 → pyckster-25.10.1}/images/screenshot_01.png +0 -0
  12. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/__main__.py +0 -0
  13. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/bayesian_inversion.py +0 -0
  14. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/inversion_app.py +0 -0
  15. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/inversion_manager.py +0 -0
  16. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/inversion_visualizer.py +0 -0
  17. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/obspy_utils.py +0 -0
  18. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/pac_inversion.py +0 -0
  19. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/pyqtgraph_utils.py +0 -0
  20. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/surface_wave_analysis.py +0 -0
  21. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/surface_wave_profiling.py +0 -0
  22. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/sw_utils.py +0 -0
  23. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/tab_factory.py +0 -0
  24. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster/visualization_utils.py +0 -0
  25. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/SOURCES.txt +0 -0
  26. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/dependency_links.txt +0 -0
  27. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/entry_points.txt +0 -0
  28. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/not-zip-safe +0 -0
  29. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/requires.txt +0 -0
  30. {pyckster-25.9.4 → pyckster-25.10.1}/pyckster.egg-info/top_level.txt +0 -0
  31. {pyckster-25.9.4 → pyckster-25.10.1}/setup.cfg +0 -0
  32. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyckster
3
- Version: 25.9.4
3
+ Version: 25.10.1
4
4
  Summary: A PyQt5-based GUI for picking seismic traveltimes
5
5
  Home-page: https://gitlab.in2p3.fr/metis-geophysics/pyckster
6
6
  Author: Sylvain Pasquet
@@ -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.9.4"
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
- vertSplitter.addWidget(self.bottomPlotWidget)
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
- permanentMessage = QLabel(self.statusBar)
1039
- try:
1040
- from pyckster import __version__
1041
- permanentMessage.setText(f'S. Pasquet - 2025 (v{__version__})')
1042
- except ImportError:
1043
- # Fallback when running as a script or during development
1044
- permanentMessage.setText('S. Pasquet - 2025')
1045
- self.statusBar.addPermanentWidget(permanentMessage)
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 selected item
2167
- selectedItems = self.fileListWidget.selectedItems()
2439
+ # Get the current row index directly (this handles duplicates properly)
2440
+ selected_row = self.fileListWidget.currentRow()
2168
2441
 
2169
- # If an item is selected
2170
- if selectedItems:
2171
- selectedBaseName = selectedItems[0].text() # Get the text of the selected item
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
- if was_empty_workspace and len(fileNames_new) > 1:
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
- progress.setValue(len(fileNames_new)) # Ensure the dialog closes
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
- QMessageBox.information(self, "File Loaded", f"File '{os.path.basename(fileNames_new[0])}' loaded successfully.")
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
- self.statusBar.showMessage('Switching to traveltime plot',1000)
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
- self.statusBar.showMessage('Switching to source/trace diagram',1000)
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
- self.statusBar.showMessage('Switching to topography plot',1000)
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 uploaded stations
6357
- try:
6358
- source_index = np.where(np.array(uploaded_stations) == source)[0][0]
6359
- except IndexError:
6360
- QMessageBox.warning(self, "Source Not Found", f"Source {source} not found in uploaded stations.")
6361
- # Handle the case where the source is not found
6362
- source_index = None # or any other appropriate action
6363
-
6364
- # Find the corresponding picks for the current source
6365
- up_picks_tmp = [pick for pick in uploaded_picks if pick[0] == source_index + 1]
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 trace indices, picks and errors
6368
- trace_indices = [int(pick[1]) - 1 for pick in up_picks_tmp]
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 trace_index_all, pick, error in zip(trace_indices, picks, errors):
6385
- # Get trace position from uploaded_stations
6386
- trace = uploaded_stations[trace_index_all][0]
6387
-
6388
- # Find the trace index in the current file
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
- self.plotWidget.addItem(scatter1)
6404
-
6405
- self.pickSeismoItems[i][trace_index_source] = scatter1
6406
- self.picks[i][trace_index_source] = pick
6407
- self.error[i][trace_index_source] = error
6408
-
6409
- if pick > max_picked_time:
6410
- max_picked_time = pick
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')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyckster
3
- Version: 25.9.4
3
+ Version: 25.10.1
4
4
  Summary: A PyQt5-based GUI for picking seismic traveltimes
5
5
  Home-page: https://gitlab.in2p3.fr/metis-geophysics/pyckster
6
6
  Author: Sylvain Pasquet
File without changes
File without changes
File without changes
File without changes
File without changes