magpy-gui 0.1.4__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.
- magpy/__init__.py +16 -0
- magpy/__main__.py +5 -0
- magpy/app.py +49 -0
- magpy/cli.py +140 -0
- magpy/controllers/__init__.py +8 -0
- magpy/controllers/audio_controller.py +834 -0
- magpy/core/__init__.py +17 -0
- magpy/core/audio.py +312 -0
- magpy/core/measurements.py +581 -0
- magpy/core/selection.py +500 -0
- magpy/core/spectrogram.py +341 -0
- magpy/main_window.py +1231 -0
- magpy/screens/__init__.py +15 -0
- magpy/screens/base.py +67 -0
- magpy/screens/batch_screen.py +104 -0
- magpy/screens/pipeline_screen.py +503 -0
- magpy/screens/terminal.py +395 -0
- magpy/screens/training_screen.py +105 -0
- magpy/widgets/__init__.py +32 -0
- magpy/widgets/detection_dialog.py +614 -0
- magpy/widgets/detection_results.py +431 -0
- magpy/widgets/navigation_bar.py +117 -0
- magpy/widgets/node_graph/__init__.py +44 -0
- magpy/widgets/node_graph/connection.py +212 -0
- magpy/widgets/node_graph/graph_widget.py +434 -0
- magpy/widgets/node_graph/node.py +376 -0
- magpy/widgets/node_graph/node_palette.py +628 -0
- magpy/widgets/node_graph/parameter_panel.py +552 -0
- magpy/widgets/playback.py +381 -0
- magpy/widgets/properties_panel.py +295 -0
- magpy/widgets/selection_table.py +944 -0
- magpy/widgets/spectrogram.py +787 -0
- magpy/widgets/waveform.py +364 -0
- magpy/workers/__init__.py +19 -0
- magpy/workers/detection_worker.py +586 -0
- magpy_gui-0.1.4.dist-info/METADATA +44 -0
- magpy_gui-0.1.4.dist-info/RECORD +40 -0
- magpy_gui-0.1.4.dist-info/WHEEL +4 -0
- magpy_gui-0.1.4.dist-info/entry_points.txt +5 -0
- magpy_gui-0.1.4.dist-info/licenses/LICENSE +674 -0
magpy/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MagPy - A modern Python bioacoustics analysis application.
|
|
3
|
+
|
|
4
|
+
MagPy provides tools for viewing, analyzing, and annotating sound files
|
|
5
|
+
with a focus on bioacoustics research.
|
|
6
|
+
|
|
7
|
+
Package Structure:
|
|
8
|
+
- core: Core analysis modules (audio, spectrogram, measurements, selection)
|
|
9
|
+
- widgets: PyQt6 widgets (waveform, spectrogram, annotation_table, playback)
|
|
10
|
+
- screens: Multi-screen workspace views
|
|
11
|
+
- controllers: Bridge between GUI and bioamla core
|
|
12
|
+
- workers: QThread workers for long-running operations
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.2"
|
|
16
|
+
__author__ = "John McMeen"
|
magpy/__main__.py
ADDED
magpy/app.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main application entry point for MagPy GUI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from PyQt6.QtWidgets import QApplication
|
|
9
|
+
from PyQt6.QtCore import Qt
|
|
10
|
+
|
|
11
|
+
from magpy.main_window import MainWindow
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(filepath: Optional[str] = None) -> int:
|
|
15
|
+
"""
|
|
16
|
+
Launch the MagPy application.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
filepath: Optional audio file to open on startup.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Application exit code.
|
|
23
|
+
"""
|
|
24
|
+
# Enable high DPI support
|
|
25
|
+
QApplication.setHighDpiScaleFactorRoundingPolicy(
|
|
26
|
+
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
app = QApplication(sys.argv)
|
|
30
|
+
app.setApplicationName("MagPy")
|
|
31
|
+
app.setOrganizationName("MagPy")
|
|
32
|
+
app.setOrganizationDomain("bioamla.org")
|
|
33
|
+
|
|
34
|
+
# Create main window
|
|
35
|
+
window = MainWindow()
|
|
36
|
+
|
|
37
|
+
# Open file if provided
|
|
38
|
+
if filepath:
|
|
39
|
+
window.load_file(filepath)
|
|
40
|
+
elif len(sys.argv) > 1:
|
|
41
|
+
window.load_file(sys.argv[1])
|
|
42
|
+
|
|
43
|
+
window.show()
|
|
44
|
+
|
|
45
|
+
return app.exec()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
sys.exit(main())
|
magpy/cli.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for MagPy.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> int:
|
|
13
|
+
"""Main CLI entry point."""
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
prog="magpy",
|
|
16
|
+
description="MagPy Bioacoustics",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--version",
|
|
21
|
+
action="version",
|
|
22
|
+
version=f"%(prog)s {__version__}",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"file",
|
|
27
|
+
nargs="?",
|
|
28
|
+
help="Audio file to open",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--gui",
|
|
33
|
+
action="store_true",
|
|
34
|
+
default=True,
|
|
35
|
+
help="Launch GUI (default)",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--headless",
|
|
40
|
+
action="store_true",
|
|
41
|
+
help="Run in headless mode for batch processing",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--compute-spectrogram",
|
|
46
|
+
metavar="OUTPUT",
|
|
47
|
+
help="Compute spectrogram and save as image",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--measure",
|
|
52
|
+
metavar="SELECTION_TABLE",
|
|
53
|
+
help="Compute measurements for selections in the given table",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
args = parser.parse_args()
|
|
57
|
+
|
|
58
|
+
if args.headless:
|
|
59
|
+
return run_headless(args)
|
|
60
|
+
else:
|
|
61
|
+
from .app import main as gui_main
|
|
62
|
+
|
|
63
|
+
return gui_main(args.file)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def run_headless(args) -> int:
|
|
67
|
+
"""Run in headless mode for batch processing."""
|
|
68
|
+
if args.file is None:
|
|
69
|
+
print("Error: No input file specified", file=sys.stderr)
|
|
70
|
+
return 1
|
|
71
|
+
|
|
72
|
+
filepath = Path(args.file)
|
|
73
|
+
if not filepath.exists():
|
|
74
|
+
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
|
75
|
+
return 1
|
|
76
|
+
|
|
77
|
+
from .core import AudioFile, SpectrogramGenerator, SelectionTable, MeasurementCalculator
|
|
78
|
+
from .core.measurements import SelectionBounds
|
|
79
|
+
|
|
80
|
+
print(f"Loading: {filepath}")
|
|
81
|
+
audio = AudioFile(filepath)
|
|
82
|
+
print(f" Duration: {audio.duration:.2f}s")
|
|
83
|
+
print(f" Channels: {audio.num_channels}")
|
|
84
|
+
print(f" Sample rate: {audio.sample_rate} Hz")
|
|
85
|
+
|
|
86
|
+
if args.compute_spectrogram:
|
|
87
|
+
print(f"Computing spectrogram...")
|
|
88
|
+
generator = SpectrogramGenerator()
|
|
89
|
+
result = generator.compute(audio.data, audio.sample_rate)
|
|
90
|
+
|
|
91
|
+
import matplotlib.pyplot as plt
|
|
92
|
+
|
|
93
|
+
plt.figure(figsize=(12, 6))
|
|
94
|
+
plt.imshow(
|
|
95
|
+
result.spectrogram,
|
|
96
|
+
aspect="auto",
|
|
97
|
+
origin="lower",
|
|
98
|
+
extent=[0, result.duration, 0, result.frequencies[-1]],
|
|
99
|
+
cmap="viridis",
|
|
100
|
+
)
|
|
101
|
+
plt.colorbar(label="Power (dB)")
|
|
102
|
+
plt.xlabel("Time (s)")
|
|
103
|
+
plt.ylabel("Frequency (Hz)")
|
|
104
|
+
plt.title(f"Spectrogram: {filepath.name}")
|
|
105
|
+
plt.savefig(args.compute_spectrogram, dpi=150, bbox_inches="tight")
|
|
106
|
+
print(f"Saved spectrogram to: {args.compute_spectrogram}")
|
|
107
|
+
|
|
108
|
+
if args.measure:
|
|
109
|
+
selection_path = Path(args.measure)
|
|
110
|
+
if not selection_path.exists():
|
|
111
|
+
print(f"Error: Selection table not found: {selection_path}", file=sys.stderr)
|
|
112
|
+
return 1
|
|
113
|
+
|
|
114
|
+
print(f"Loading selection table: {selection_path}")
|
|
115
|
+
table = SelectionTable.load(selection_path)
|
|
116
|
+
print(f" Found {len(table)} selections")
|
|
117
|
+
|
|
118
|
+
calc = MeasurementCalculator()
|
|
119
|
+
|
|
120
|
+
for i, selection in enumerate(table):
|
|
121
|
+
bounds = SelectionBounds(
|
|
122
|
+
start_time=selection.begin_time,
|
|
123
|
+
end_time=selection.end_time,
|
|
124
|
+
low_freq=selection.low_freq,
|
|
125
|
+
high_freq=selection.high_freq,
|
|
126
|
+
)
|
|
127
|
+
result = calc.compute_all(audio.data, audio.sample_rate, bounds)
|
|
128
|
+
selection.measurements = result.to_dict()
|
|
129
|
+
print(f" Measured selection {i + 1}: {selection.duration:.3f}s")
|
|
130
|
+
|
|
131
|
+
# Save updated table
|
|
132
|
+
output_path = selection_path.with_stem(selection_path.stem + "_measured")
|
|
133
|
+
table.save(output_path)
|
|
134
|
+
print(f"Saved measurements to: {output_path}")
|
|
135
|
+
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
sys.exit(main())
|