crimeacs commited on
Commit
a237ee5
·
1 Parent(s): b1e598e

fixed uploading bug (1,...) shape

Browse files
Gradio_app.ipynb CHANGED
@@ -2,14 +2,14 @@
2
  "cells": [
3
  {
4
  "cell_type": "code",
5
- "execution_count": 55,
6
  "metadata": {},
7
  "outputs": [
8
  {
9
  "name": "stdout",
10
  "output_type": "stream",
11
  "text": [
12
- "Running on local URL: http://127.0.0.1:7901\n",
13
  "\n",
14
  "To create a public link, set `share=True` in `launch()`.\n"
15
  ]
@@ -17,7 +17,7 @@
17
  {
18
  "data": {
19
  "text/html": [
20
- "<div><iframe src=\"http://127.0.0.1:7901/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
21
  ],
22
  "text/plain": [
23
  "<IPython.core.display.HTML object>"
@@ -30,9 +30,23 @@
30
  "data": {
31
  "text/plain": []
32
  },
33
- "execution_count": 55,
34
  "metadata": {},
35
  "output_type": "execute_result"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  }
37
  ],
38
  "source": [
@@ -66,6 +80,9 @@
66
  "\n",
67
  "def make_prediction(waveform):\n",
68
  " waveform = np.load(waveform)\n",
 
 
 
69
  " processed_input = prepare_waveform(waveform)\n",
70
  " \n",
71
  " # Make prediction\n",
@@ -77,7 +94,7 @@
77
  "\n",
78
  " return processed_input, p_phase, s_phase\n",
79
  "\n",
80
- "def mark_phases(waveform, uploaded_file):\n",
81
  "\n",
82
  " if uploaded_file is not None:\n",
83
  " waveform = uploaded_file.name\n",
@@ -101,19 +118,28 @@
101
  " ax[1].set_ylabel('N')\n",
102
  " ax[2].set_ylabel('E')\n",
103
  "\n",
104
- " p_phase_plot = p_phase*processed_input.shape[-1]\n",
105
- " p_kde = gaussian_kde(p_phase_plot)\n",
106
- " p_dist_space = np.linspace( min(p_phase_plot)-10, max(p_phase_plot)+10, 500 )\n",
107
- " ax[-1].plot( p_dist_space, p_kde(p_dist_space), color='r')\n",
108
- "\n",
109
- " s_phase_plot = s_phase*processed_input.shape[-1]\n",
110
- " s_kde = gaussian_kde(s_phase_plot)\n",
111
- " s_dist_space = np.linspace( min(s_phase_plot)-10, max(s_phase_plot)+10, 500 )\n",
112
- " ax[-1].plot( s_dist_space, s_kde(s_dist_space), color='b')\n",
113
- "\n",
114
- " for a in ax:\n",
115
- " a.axvline(p_phase.mean()*processed_input.shape[-1], color='r', linestyle='--', label='P')\n",
116
- " a.axvline(s_phase.mean()*processed_input.shape[-1], color='b', linestyle='--', label='S')\n",
 
 
 
 
 
 
 
 
 
117
  "\n",
118
  " ax[-1].set_xlabel('Time, samples')\n",
119
  " ax[-1].set_ylabel('Uncert., samples')\n",
@@ -401,6 +427,109 @@
401
  "\n",
402
  " return image, output_picks, output_csv\n",
403
  "\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  "model = torch.jit.load(\"model.pt\")\n",
405
  "\n",
406
  "with gr.Blocks() as demo:\n",
@@ -433,25 +562,6 @@
433
  " </ul>\n",
434
  "</div>\n",
435
  "\"\"\")\n",
436
- "\n",
437
- " with gr.Tab(\"Try on a single station\"):\n",
438
- " with gr.Row(): \n",
439
- " # Define the input and output types for Gradio\n",
440
- " inputs = gr.Dropdown(\n",
441
- " [\"data/sample/sample_0.npy\", \n",
442
- " \"data/sample/sample_1.npy\", \n",
443
- " \"data/sample/sample_2.npy\"], \n",
444
- " label=\"Sample waveform\", \n",
445
- " info=\"Select one of the samples\",\n",
446
- " value = \"data/sample/sample_0.npy\"\n",
447
- " )\n",
448
- "\n",
449
- " upload = gr.File(label=\"Or upload your own waveform\")\n",
450
- "\n",
451
- " button = gr.Button(\"Predict phases\")\n",
452
- " outputs = gr.Image(label='Waveform with Phases Marked', type='numpy', interactive=False)\n",
453
- " \n",
454
- " button.click(mark_phases, inputs=[inputs, upload], outputs=outputs)\n",
455
  " \n",
456
  " with gr.Tab(\"Select earthquake from catalogue\"):\n",
457
  "\n",
@@ -559,10 +669,134 @@
559
  " velocity_inputs, max_waveforms_inputs,\n",
560
  " P_thres_inputs, S_thres_inputs],\n",
561
  " outputs=[output_image, output_picks, output_csv])\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  "\n",
563
  "demo.launch()"
564
  ]
565
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
  {
567
  "cell_type": "code",
568
  "execution_count": null,
@@ -573,7 +807,7 @@
573
  ],
574
  "metadata": {
575
  "kernelspec": {
576
- "display_name": "phasehunter",
577
  "language": "python",
578
  "name": "python3"
579
  },
@@ -587,14 +821,9 @@
587
  "name": "python",
588
  "nbconvert_exporter": "python",
589
  "pygments_lexer": "ipython3",
590
- "version": "3.11.2"
591
  },
592
- "orig_nbformat": 4,
593
- "vscode": {
594
- "interpreter": {
595
- "hash": "6bf57068982d7b420bddaaf1d0614a7795947176033057024cf47d8ca2c1c4cd"
596
- }
597
- }
598
  },
599
  "nbformat": 4,
600
  "nbformat_minor": 2
 
2
  "cells": [
3
  {
4
  "cell_type": "code",
5
+ "execution_count": 13,
6
  "metadata": {},
7
  "outputs": [
8
  {
9
  "name": "stdout",
10
  "output_type": "stream",
11
  "text": [
12
+ "Running on local URL: http://127.0.0.1:7866\n",
13
  "\n",
14
  "To create a public link, set `share=True` in `launch()`.\n"
15
  ]
 
17
  {
18
  "data": {
19
  "text/html": [
20
+ "<div><iframe src=\"http://127.0.0.1:7866/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
21
  ],
22
  "text/plain": [
23
  "<IPython.core.display.HTML object>"
 
30
  "data": {
31
  "text/plain": []
32
  },
33
+ "execution_count": 13,
34
  "metadata": {},
35
  "output_type": "execute_result"
36
+ },
37
+ {
38
+ "name": "stderr",
39
+ "output_type": "stream",
40
+ "text": [
41
+ "No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.\n"
42
+ ]
43
+ },
44
+ {
45
+ "name": "stdout",
46
+ "output_type": "stream",
47
+ "text": [
48
+ "0.13119522482156754\n"
49
+ ]
50
  }
51
  ],
52
  "source": [
 
80
  "\n",
81
  "def make_prediction(waveform):\n",
82
  " waveform = np.load(waveform)\n",
83
+ " if len(waveform.shape) == 1:\n",
84
+ " waveform = waveform.reshape(1, waveform.shape[0])\n",
85
+ "\n",
86
  " processed_input = prepare_waveform(waveform)\n",
87
  " \n",
88
  " # Make prediction\n",
 
94
  "\n",
95
  " return processed_input, p_phase, s_phase\n",
96
  "\n",
97
+ "def mark_phases(waveform, uploaded_file, p_thres, s_thres):\n",
98
  "\n",
99
  " if uploaded_file is not None:\n",
100
  " waveform = uploaded_file.name\n",
 
118
  " ax[1].set_ylabel('N')\n",
119
  " ax[2].set_ylabel('E')\n",
120
  "\n",
121
+ " print(p_phase.std().item()*60)\n",
122
+ " do_we_have_p = (p_phase.std().item()*60 < p_thres)\n",
123
+ " if do_we_have_p:\n",
124
+ " p_phase_plot = p_phase*processed_input.shape[-1]\n",
125
+ " p_kde = gaussian_kde(p_phase_plot)\n",
126
+ " p_dist_space = np.linspace( min(p_phase_plot)-10, max(p_phase_plot)+10, 500 )\n",
127
+ " ax[-1].plot( p_dist_space, p_kde(p_dist_space), color='r')\n",
128
+ " else:\n",
129
+ " ax[-1].text(0.5, 0.75, 'No P phase detected', horizontalalignment='center', verticalalignment='center', transform=ax[-1].transAxes)\n",
130
+ "\n",
131
+ " do_we_have_s = (s_phase.std().item()*60 < s_thres)\n",
132
+ " if do_we_have_s:\n",
133
+ " s_phase_plot = s_phase*processed_input.shape[-1]\n",
134
+ " s_kde = gaussian_kde(s_phase_plot)\n",
135
+ " s_dist_space = np.linspace( min(s_phase_plot)-10, max(s_phase_plot)+10, 500 )\n",
136
+ " ax[-1].plot( s_dist_space, s_kde(s_dist_space), color='b')\n",
137
+ "\n",
138
+ " for a in ax:\n",
139
+ " a.axvline(p_phase.mean()*processed_input.shape[-1], color='r', linestyle='--', label='P', alpha=do_we_have_p)\n",
140
+ " a.axvline(s_phase.mean()*processed_input.shape[-1], color='b', linestyle='--', label='S', alpha=do_we_have_s)\n",
141
+ " else:\n",
142
+ " ax[-1].text(0.5, 0.25, 'No S phase detected', horizontalalignment='center', verticalalignment='center', transform=ax[-1].transAxes)\n",
143
  "\n",
144
  " ax[-1].set_xlabel('Time, samples')\n",
145
  " ax[-1].set_ylabel('Uncert., samples')\n",
 
427
  "\n",
428
  " return image, output_picks, output_csv\n",
429
  "\n",
430
+ "import numpy as np\n",
431
+ "from matplotlib import colors, cm\n",
432
+ "\n",
433
+ "# Function to find the closest index for a given value in an array\n",
434
+ "def find_closest_index(array, value):\n",
435
+ " return np.argmin(np.abs(array - value))\n",
436
+ "\n",
437
+ "def compute_velocity_model(azimuth, elevation):\n",
438
+ " filename = output_csv\n",
439
+ " df = pd.read_csv(filename)\n",
440
+ "\n",
441
+ " # Current EQ location\n",
442
+ " filename = filename.split(\"/\")[-1]\n",
443
+ " eq_lat = float(filename.split(\"_\")[0])\n",
444
+ " eq_lon = float(filename.split(\"_\")[1])\n",
445
+ "\n",
446
+ " ## FIX THIS LATTER\n",
447
+ " eq_depth = 10 ##FIX THIS LATTER\n",
448
+ " ## FIX THIS LATTER\n",
449
+ "\n",
450
+ " # Define the region of interest (latitude, longitude, and depth ranges)\n",
451
+ " lat_range = (np.min([df.st_lat.min(), eq_lat]), np.max([df.st_lat.max(), eq_lat]))\n",
452
+ " lon_range = (np.min([df.st_lon.min(), eq_lon]), np.max([df.st_lon.max(), eq_lon]))\n",
453
+ " depth_range = (0, 50)\n",
454
+ "\n",
455
+ " # Define the number of nodes in each dimension\n",
456
+ " n_lat = 10\n",
457
+ " n_lon = 10\n",
458
+ " n_depth = 10\n",
459
+ " num_points = 100\n",
460
+ "\n",
461
+ " # Create the grid\n",
462
+ " lat_values = np.linspace(lat_range[0], lat_range[1], n_lat)\n",
463
+ " lon_values = np.linspace(lon_range[0], lon_range[1], n_lon)\n",
464
+ " depth_values = np.linspace(depth_range[0], depth_range[1], n_depth)\n",
465
+ "\n",
466
+ " # Initialize the velocity model with constant values\n",
467
+ " initial_velocity = 0 # km/s, this can be P-wave or S-wave velocity\n",
468
+ " velocity_model = np.full((n_lat, n_lon, n_depth), initial_velocity, dtype=float)\n",
469
+ "\n",
470
+ " # Loop through the stations and update the velocity model\n",
471
+ " for i in range(len(df)):\n",
472
+ " if ~np.isnan(df['velocity_p, km/s'].iloc[i]):\n",
473
+ " # Interpolate coordinates along the great circle path between the earthquake and the station\n",
474
+ " lon_deg = np.linspace(df.st_lon.iloc[i], eq_lon, num_points)\n",
475
+ " lat_deg = np.linspace(df.st_lat.iloc[i], eq_lat, num_points)\n",
476
+ " depth_interpolated = np.interp(np.linspace(0, 1, num_points), [0, 1], [eq_depth, 0])\n",
477
+ "\n",
478
+ " # Loop through the interpolated coordinates and update the grid cells with the average P-wave velocity\n",
479
+ " for lat, lon, depth in zip(lat_deg, lon_deg, depth_interpolated):\n",
480
+ " lat_index = find_closest_index(lat_values, lat)\n",
481
+ " lon_index = find_closest_index(lon_values, lon)\n",
482
+ " depth_index = find_closest_index(depth_values, depth)\n",
483
+ " \n",
484
+ " if velocity_model[lat_index, lon_index, depth_index] == initial_velocity:\n",
485
+ " velocity_model[lat_index, lon_index, depth_index] = df['velocity_p, km/s'].iloc[i]\n",
486
+ " else:\n",
487
+ " velocity_model[lat_index, lon_index, depth_index] = (velocity_model[lat_index, lon_index, depth_index] +\n",
488
+ " df['velocity_p, km/s'].iloc[i]) / 2\n",
489
+ " \n",
490
+ " # Create the figure and axis\n",
491
+ " fig = plt.figure(figsize=(8, 8))\n",
492
+ " ax = fig.add_subplot(111, projection='3d')\n",
493
+ "\n",
494
+ " # Set the plot limits\n",
495
+ " ax.set_xlim3d(lat_range[0], lat_range[1])\n",
496
+ " ax.set_ylim3d(lon_range[0], lon_range[1])\n",
497
+ " ax.set_zlim3d(depth_range[1], depth_range[0])\n",
498
+ "\n",
499
+ " ax.set_xlabel('Latitude')\n",
500
+ " ax.set_ylabel('Longitude')\n",
501
+ " ax.set_zlabel('Depth (km)')\n",
502
+ " ax.set_title('Velocity Model')\n",
503
+ " \n",
504
+ " # Create the meshgrid\n",
505
+ " x, y, z = np.meshgrid(\n",
506
+ " np.linspace(lat_range[0], lat_range[1], velocity_model.shape[0]+1),\n",
507
+ " np.linspace(lon_range[0], lon_range[1], velocity_model.shape[1]+1),\n",
508
+ " np.linspace(depth_range[0], depth_range[1], velocity_model.shape[2]+1),\n",
509
+ " indexing='ij'\n",
510
+ " )\n",
511
+ "\n",
512
+ " # Create the color array\n",
513
+ " norm = plt.Normalize(vmin=velocity_model.min(), vmax=velocity_model.max())\n",
514
+ " colors = plt.cm.plasma(norm(velocity_model))\n",
515
+ "\n",
516
+ " # Plot the voxels\n",
517
+ " ax.voxels(x, y, z, velocity_model > 0, facecolors=colors, alpha=0.5, edgecolor='k')\n",
518
+ "\n",
519
+ " # Set the view angle\n",
520
+ " ax.view_init(elev=elevation, azim=azimuth)\n",
521
+ "\n",
522
+ " m = cm.ScalarMappable(cmap=plt.cm.plasma, norm=norm)\n",
523
+ " m.set_array([])\n",
524
+ " plt.colorbar(m)\n",
525
+ "\n",
526
+ " # Show the plot\n",
527
+ " fig.canvas.draw();\n",
528
+ " image = np.array(fig.canvas.renderer.buffer_rgba())\n",
529
+ " plt.close(fig)\n",
530
+ "\n",
531
+ " return image\n",
532
+ "\n",
533
  "model = torch.jit.load(\"model.pt\")\n",
534
  "\n",
535
  "with gr.Blocks() as demo:\n",
 
562
  " </ul>\n",
563
  "</div>\n",
564
  "\"\"\")\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  " \n",
566
  " with gr.Tab(\"Select earthquake from catalogue\"):\n",
567
  "\n",
 
669
  " velocity_inputs, max_waveforms_inputs,\n",
670
  " P_thres_inputs, S_thres_inputs],\n",
671
  " outputs=[output_image, output_picks, output_csv])\n",
672
+ " \n",
673
+ " with gr.Row():\n",
674
+ " with gr.Column(scale=2):\n",
675
+ " inputs_vel_model = [\n",
676
+ " ## FIX FILE NAME ISSUE\n",
677
+ " gr.Slider(minimum=-180, maximum=180, value=0, step=5, label=\"Azimuth\", interactive=True),\n",
678
+ " gr.Slider(minimum=-90, maximum=90, value=30, step=5, label=\"Elevation\", interactive=True)\n",
679
+ " ]\n",
680
+ " button = gr.Button(\"Look at 3D Velocities\")\n",
681
+ " outputs_vel_model = gr.Image(label=\"3D Velocity Model\")\n",
682
+ "\n",
683
+ " button.click(compute_velocity_model, \n",
684
+ " inputs=inputs_vel_model, \n",
685
+ " outputs=outputs_vel_model)\n",
686
+ "\n",
687
+ " with gr.Tab(\"Try on a single station\"):\n",
688
+ " with gr.Row(): \n",
689
+ " # Define the input and output types for Gradio\n",
690
+ " inputs = gr.Dropdown(\n",
691
+ " [\"data/sample/sample_0.npy\", \n",
692
+ " \"data/sample/sample_1.npy\", \n",
693
+ " \"data/sample/sample_2.npy\"], \n",
694
+ " label=\"Sample waveform\", \n",
695
+ " info=\"Select one of the samples\",\n",
696
+ " value = \"data/sample/sample_0.npy\"\n",
697
+ " )\n",
698
+ " with gr.Column(scale=1):\n",
699
+ " P_thres_inputs = gr.Slider(minimum=0.01,\n",
700
+ " maximum=1,\n",
701
+ " value=0.1,\n",
702
+ " label=\"P uncertainty threshold, s\",\n",
703
+ " step=0.01,\n",
704
+ " info=\"Acceptable uncertainty for P picks expressed in std() seconds\",\n",
705
+ " interactive=True,\n",
706
+ " )\n",
707
+ " \n",
708
+ " S_thres_inputs = gr.Slider(minimum=0.01,\n",
709
+ " maximum=1,\n",
710
+ " value=0.2,\n",
711
+ " label=\"S uncertainty threshold, s\",\n",
712
+ " step=0.01,\n",
713
+ " info=\"Acceptable uncertainty for S picks expressed in std() seconds\",\n",
714
+ " interactive=True,\n",
715
+ " )\n",
716
+ "\n",
717
+ " upload = gr.File(label=\"Or upload your own waveform\")\n",
718
+ "\n",
719
+ " button = gr.Button(\"Predict phases\")\n",
720
+ " outputs = gr.Image(label='Waveform with Phases Marked', type='numpy', interactive=False)\n",
721
+ " \n",
722
+ " button.click(mark_phases, inputs=[inputs, upload, P_thres_inputs, S_thres_inputs], outputs=outputs)\n",
723
+ "\n",
724
+ " \n",
725
+ "\n",
726
  "\n",
727
  "demo.launch()"
728
  ]
729
  },
730
+ {
731
+ "cell_type": "code",
732
+ "execution_count": 33,
733
+ "metadata": {},
734
+ "outputs": [],
735
+ "source": [
736
+ "output_csv.value"
737
+ ]
738
+ },
739
+ {
740
+ "cell_type": "code",
741
+ "execution_count": 6,
742
+ "metadata": {},
743
+ "outputs": [],
744
+ "source": [
745
+ "np.save(\"test.npy\", np.random.randint(0,10, size=(6000)))"
746
+ ]
747
+ },
748
+ {
749
+ "cell_type": "code",
750
+ "execution_count": 24,
751
+ "metadata": {},
752
+ "outputs": [
753
+ {
754
+ "name": "stdout",
755
+ "output_type": "stream",
756
+ "text": [
757
+ "Running on local URL: http://127.0.0.1:7869\n",
758
+ "\n",
759
+ "To create a public link, set `share=True` in `launch()`.\n"
760
+ ]
761
+ },
762
+ {
763
+ "data": {
764
+ "text/html": [
765
+ "<div><iframe src=\"http://127.0.0.1:7869/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
766
+ ],
767
+ "text/plain": [
768
+ "<IPython.core.display.HTML object>"
769
+ ]
770
+ },
771
+ "metadata": {},
772
+ "output_type": "display_data"
773
+ },
774
+ {
775
+ "data": {
776
+ "text/plain": []
777
+ },
778
+ "execution_count": 24,
779
+ "metadata": {},
780
+ "output_type": "execute_result"
781
+ },
782
+ {
783
+ "name": "stderr",
784
+ "output_type": "stream",
785
+ "text": [
786
+ "/var/folders/ky/4j6xbvhs5m583jflkhyzxf9h0000gn/T/ipykernel_19576/4006067033.py:95: MatplotlibDeprecationWarning: Unable to determine Axes to steal space for Colorbar. Using gca(), but will raise in the future. Either provide the *cax* argument to use as the Axes for the Colorbar, provide the *ax* argument to steal space from it, or add *mappable* to an Axes.\n",
787
+ " plt.colorbar(m)\n"
788
+ ]
789
+ }
790
+ ],
791
+ "source": [
792
+ "import matplotlib.pyplot as plt\n",
793
+ "import numpy as np\n",
794
+ "import gradio as gr\n",
795
+ " \n",
796
+ "# Define the Gradio interface\n",
797
+ "\n"
798
+ ]
799
+ },
800
  {
801
  "cell_type": "code",
802
  "execution_count": null,
 
807
  ],
808
  "metadata": {
809
  "kernelspec": {
810
+ "display_name": "Python 3",
811
  "language": "python",
812
  "name": "python3"
813
  },
 
821
  "name": "python",
822
  "nbconvert_exporter": "python",
823
  "pygments_lexer": "ipython3",
824
+ "version": "3.9.8"
825
  },
826
+ "orig_nbformat": 4
 
 
 
 
 
827
  },
828
  "nbformat": 4,
829
  "nbformat_minor": 2
app.py CHANGED
@@ -13,7 +13,11 @@ import earthpy.spatial as es
13
 
14
  import obspy
15
  from obspy.clients.fdsn import Client
16
- from obspy.clients.fdsn.header import FDSNNoDataException, FDSNTimeoutException, FDSNInternalServerException
 
 
 
 
17
  from obspy.geodetics.base import locations2degrees
18
  from obspy.taup import TauPyModel
19
  from obspy.taup.helper_classes import SlownessModelError
@@ -26,10 +30,14 @@ from mpl_toolkits.axes_grid1 import ImageGrid
26
 
27
  from glob import glob
28
 
 
29
  def make_prediction(waveform):
30
  waveform = np.load(waveform)
 
 
 
31
  processed_input = prepare_waveform(waveform)
32
-
33
  # Make prediction
34
  with torch.inference_mode():
35
  output = model(processed_input)
@@ -39,7 +47,8 @@ def make_prediction(waveform):
39
 
40
  return processed_input, p_phase, s_phase
41
 
42
- def mark_phases(waveform, uploaded_file):
 
43
 
44
  if uploaded_file is not None:
45
  waveform = uploaded_file.name
@@ -47,41 +56,76 @@ def mark_phases(waveform, uploaded_file):
47
  processed_input, p_phase, s_phase = make_prediction(waveform)
48
 
49
  # Create a plot of the waveform with the phases marked
50
- if sum(processed_input[0][2] == 0): #if input is 1C
51
  fig, ax = plt.subplots(nrows=2, figsize=(10, 2), sharex=True)
52
 
53
- ax[0].plot(processed_input[0][0], color='black', lw=1)
54
- ax[0].set_ylabel('Norm. Ampl.')
55
 
56
- else: #if input is 3C
57
  fig, ax = plt.subplots(nrows=4, figsize=(10, 6), sharex=True)
58
- ax[0].plot(processed_input[0][0], color='black', lw=1)
59
- ax[1].plot(processed_input[0][1], color='black', lw=1)
60
- ax[2].plot(processed_input[0][2], color='black', lw=1)
61
-
62
- ax[0].set_ylabel('Z')
63
- ax[1].set_ylabel('N')
64
- ax[2].set_ylabel('E')
65
-
66
- p_phase_plot = p_phase*processed_input.shape[-1]
67
- p_kde = gaussian_kde(p_phase_plot)
68
- p_dist_space = np.linspace( min(p_phase_plot)-10, max(p_phase_plot)+10, 500 )
69
- ax[-1].plot( p_dist_space, p_kde(p_dist_space), color='r')
70
-
71
- s_phase_plot = s_phase*processed_input.shape[-1]
72
- s_kde = gaussian_kde(s_phase_plot)
73
- s_dist_space = np.linspace( min(s_phase_plot)-10, max(s_phase_plot)+10, 500 )
74
- ax[-1].plot( s_dist_space, s_kde(s_dist_space), color='b')
75
-
76
- for a in ax:
77
- a.axvline(p_phase.mean()*processed_input.shape[-1], color='r', linestyle='--', label='P')
78
- a.axvline(s_phase.mean()*processed_input.shape[-1], color='b', linestyle='--', label='S')
79
-
80
- ax[-1].set_xlabel('Time, samples')
81
- ax[-1].set_ylabel('Uncert., samples')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  ax[-1].legend()
83
 
84
- plt.subplots_adjust(hspace=0., wspace=0.)
85
 
86
  # Convert the plot to an image and return it
87
  fig.canvas.draw()
@@ -89,6 +133,7 @@ def mark_phases(waveform, uploaded_file):
89
  plt.close(fig)
90
  return image
91
 
 
92
  def bin_distances(distances, bin_size=10):
93
  # Bin the distances into groups of `bin_size` kilometers
94
  binned_distances = {}
@@ -104,9 +149,10 @@ def bin_distances(distances, bin_size=10):
104
  for bin_index in binned_distances:
105
  first_distance, first_distance_index = binned_distances[bin_index]
106
  first_distances.append(first_distance_index)
107
-
108
  return first_distances
109
 
 
110
  def variance_coefficient(residuals):
111
  # calculate the variance of the residuals
112
  var = residuals.var()
@@ -114,9 +160,21 @@ def variance_coefficient(residuals):
114
  coeff = 1 - (var / (residuals.max() - residuals.min()))
115
  return coeff
116
 
117
- def predict_on_section(client_name, timestamp, eq_lat, eq_lon, radius_km, source_depth_km, velocity_model, max_waveforms, conf_thres_P, conf_thres_S):
 
 
 
 
 
 
 
 
 
 
 
 
118
  distances, t0s, st_lats, st_lons, waveforms, names = [], [], [], [], [], []
119
-
120
  taup_model = TauPyModel(model=velocity_model)
121
  client = Client(client_name)
122
 
@@ -130,60 +188,92 @@ def predict_on_section(client_name, timestamp, eq_lat, eq_lon, radius_km, source
130
  endtime = starttime + 120
131
 
132
  try:
133
- print('Starting to download inventory')
134
- inv = client.get_stations(network="*", station="*", location="*", channel="*H*",
135
- starttime=starttime, endtime=endtime,
136
- minlatitude=(eq_lat-window), maxlatitude=(eq_lat+window),
137
- minlongitude=(eq_lon-window), maxlongitude=(eq_lon+window),
138
- level='station')
139
- print('Finished downloading inventory')
140
-
141
- except (IndexError, FDSNNoDataException, FDSNTimeoutException, FDSNInternalServerException):
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  fig, ax = plt.subplots()
143
- ax.text(0.5,0.5,'Something is wrong with the data provider, try another')
144
- fig.canvas.draw();
145
  image = np.array(fig.canvas.renderer.buffer_rgba())
146
  plt.close(fig)
147
  return image
148
-
149
  waveforms = []
150
  cached_waveforms = glob("data/cached/*.mseed")
151
 
152
  for network in inv:
153
- if network.code == 'SY':
154
  continue
155
  for station in network:
156
  print(f"Processing {network.code}.{station.code}...")
157
- distance = locations2degrees(eq_lat, eq_lon, station.latitude, station.longitude)
 
 
158
 
159
- arrivals = taup_model.get_travel_times(source_depth_in_km=source_depth_km,
160
- distance_in_degree=distance,
161
- phase_list=["P", "S"])
 
 
162
 
163
  if len(arrivals) > 0:
164
 
165
  starttime = obspy.UTCDateTime(timestamp) + arrivals[0].time - 15
166
  endtime = starttime + 60
167
  try:
168
- filename=f'{network.code}_{station.code}_{starttime}'
169
  if f"data/cached/{filename}.mseed" not in cached_waveforms:
170
- print(f'Downloading waveform for {filename}')
171
- waveform = client.get_waveforms(network=network.code, station=station.code, location="*", channel="*",
172
- starttime=starttime, endtime=endtime)
173
- waveform.write(f"data/cached/{network.code}_{station.code}_{starttime}.mseed", format="MSEED")
174
- print('Finished downloading and caching waveform')
 
 
 
 
 
 
 
 
 
175
  else:
176
- print('Reading cached waveform')
177
- waveform = obspy.read(f"data/cached/{network.code}_{station.code}_{starttime}.mseed")
178
-
179
-
180
- except (IndexError, FDSNNoDataException, FDSNTimeoutException, FDSNInternalServerException):
181
- print(f'Skipping {network.code}_{station.code}_{starttime}')
 
 
 
 
 
 
182
  continue
183
-
184
  waveform = waveform.select(channel="H[BH][ZNE]")
185
  waveform = waveform.merge(fill_value=0)
186
- waveform = waveform[:3].sort(keys=['channel'], reverse=True)
187
 
188
  len_check = [len(x.data) for x in waveform]
189
  if len(set(len_check)) > 1:
@@ -191,7 +281,9 @@ def predict_on_section(client_name, timestamp, eq_lat, eq_lon, radius_km, source
191
 
192
  if len(waveform) == 3:
193
  try:
194
- waveform = prepare_waveform(np.stack([x.data for x in waveform]))
 
 
195
 
196
  distances.append(distance)
197
  t0s.append(starttime)
@@ -200,32 +292,32 @@ def predict_on_section(client_name, timestamp, eq_lat, eq_lon, radius_km, source
200
  waveforms.append(waveform)
201
  names.append(f"{network.code}.{station.code}")
202
 
203
- print(f"Added {network.code}.{station.code} to the list of waveforms")
 
 
204
 
205
  except:
206
  continue
207
-
208
-
209
  # If there are no waveforms, return an empty plot
210
  if len(waveforms) == 0:
211
- print('No waveforms found')
212
  fig, ax = plt.subplots()
213
- ax.text(0.5,0.5,'No waveforms found')
214
- fig.canvas.draw();
215
  image = np.array(fig.canvas.renderer.buffer_rgba())
216
  plt.close(fig)
217
  output_picks = pd.DataFrame()
218
- output_picks.to_csv('data/picks.csv', index=False)
219
- output_csv = 'data/picks.csv'
220
  return image, output_picks, output_csv
221
-
222
 
223
- first_distances = bin_distances(distances, bin_size=10/111.2)
224
 
225
  # Edge case when there are way too many waveforms to process
226
- selection_indexes = np.random.choice(first_distances,
227
- np.min([len(first_distances), max_waveforms]),
228
- replace=False)
229
 
230
  waveforms = np.array(waveforms)[selection_indexes]
231
  distances = np.array(distances)[selection_indexes]
@@ -236,7 +328,7 @@ def predict_on_section(client_name, timestamp, eq_lat, eq_lon, radius_km, source
236
 
237
  waveforms = [torch.tensor(waveform) for waveform in waveforms]
238
 
239
- print('Starting to run predictions')
240
  with torch.no_grad():
241
  waveforms_torch = torch.vstack(waveforms)
242
  output = model(waveforms_torch)
@@ -244,129 +336,183 @@ def predict_on_section(client_name, timestamp, eq_lat, eq_lon, radius_km, source
244
  p_phases = output[:, 0]
245
  s_phases = output[:, 1]
246
 
247
- p_phases = p_phases.reshape(len(waveforms),-1)
248
- s_phases = s_phases.reshape(len(waveforms),-1)
249
 
250
- # Max confidence - min variance
251
  p_max_confidence = p_phases.std(axis=-1).min()
252
  s_max_confidence = s_phases.std(axis=-1).min()
253
 
254
  print(f"Starting plotting {len(waveforms)} waveforms")
255
  fig, ax = plt.subplots(ncols=3, figsize=(10, 3))
256
-
257
  # Plot topography
258
- print('Fetching topography')
259
  params = Topography.DEFAULT.copy()
260
  extra_window = 0.5
261
- params["south"] = np.min([st_lats.min(), eq_lat])-extra_window
262
- params["north"] = np.max([st_lats.max(), eq_lat])+extra_window
263
- params["west"] = np.min([st_lons.min(), eq_lon])-extra_window
264
- params["east"] = np.max([st_lons.max(), eq_lon])+extra_window
265
 
266
  topo_map = Topography(**params)
267
  topo_map.fetch()
268
  topo_map.load()
269
 
270
- print('Plotting topo')
271
  hillshade = es.hillshade(topo_map.da[0], altitude=10)
272
-
273
- topo_map.da.plot(ax = ax[1], cmap='Greys', add_colorbar=False, add_labels=False)
274
- topo_map.da.plot(ax = ax[2], cmap='Greys', add_colorbar=False, add_labels=False)
275
  ax[1].imshow(hillshade, cmap="Greys", alpha=0.5)
276
 
277
- output_picks = pd.DataFrame({'station_name' : [],
278
- 'st_lat' : [], 'st_lon' : [],
279
- 'starttime' : [],
280
- 'p_phase, s' : [], 'p_uncertainty, s' : [],
281
- 's_phase, s' : [], 's_uncertainty, s' : [],
282
- 'velocity_p, km/s' : [], 'velocity_s, km/s' : []})
283
-
 
 
 
 
 
 
 
 
284
  for i in range(len(waveforms)):
285
  print(f"Plotting waveform {i+1}/{len(waveforms)}")
286
  current_P = p_phases[i]
287
  current_S = s_phases[i]
288
-
289
- x = [t0s[i] + pd.Timedelta(seconds=k/100) for k in np.linspace(0,6000,6000)]
290
  x = mdates.date2num(x)
291
 
292
  # Normalize confidence for the plot
293
- p_conf = 1/(current_P.std()/p_max_confidence).item()
294
- s_conf = 1/(current_S.std()/s_max_confidence).item()
295
 
296
  delta_t = t0s[i].timestamp - obspy.UTCDateTime(timestamp).timestamp
297
 
298
- ax[0].plot(x, waveforms[i][0, 0]*10+distances[i]*111.2, color='black', alpha=0.5, lw=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- if (current_P.std().item()*60 < conf_thres_P) or (current_S.std().item()*60 < conf_thres_S):
301
- ax[0].scatter(x[int(current_P.mean()*waveforms[i][0].shape[-1])], waveforms[i][0, 0].mean()+distances[i]*111.2, color='r', alpha=p_conf, marker='|')
302
- ax[0].scatter(x[int(current_S.mean()*waveforms[i][0].shape[-1])], waveforms[i][0, 0].mean()+distances[i]*111.2, color='b', alpha=s_conf, marker='|')
303
-
304
- velocity_p = (distances[i]*111.2)/(delta_t+current_P.mean()*60).item()
305
- velocity_s = (distances[i]*111.2)/(delta_t+current_S.mean()*60).item()
306
 
307
  # Generate an array from st_lat to eq_lat and from st_lon to eq_lon
308
  x = np.linspace(st_lons[i], eq_lon, 50)
309
  y = np.linspace(st_lats[i], eq_lat, 50)
310
-
311
  # Plot the array
312
- ax[1].scatter(x, y, c=np.zeros_like(x)+velocity_p, alpha=0.1, vmin=0, vmax=8)
313
- ax[2].scatter(x, y, c=np.zeros_like(x)+velocity_s, alpha=0.1, vmin=0, vmax=8)
 
 
 
 
314
 
315
  else:
316
  velocity_p = np.nan
317
  velocity_s = np.nan
318
-
319
- ax[0].set_ylabel('Z')
320
- print(f"Station {st_lats[i]}, {st_lons[i]} has P velocity {velocity_p} and S velocity {velocity_s}")
321
-
322
- output_picks = output_picks.append(pd.DataFrame({'station_name': [names[i]],
323
- 'st_lat' : [st_lats[i]], 'st_lon' : [st_lons[i]],
324
- 'starttime' : [str(t0s[i])],
325
- 'p_phase, s' : [(delta_t+current_P.mean()*60).item()], 'p_uncertainty, s' : [current_P.std().item()*60],
326
- 's_phase, s' : [(delta_t+current_S.mean()*60).item()], 's_uncertainty, s' : [current_S.std().item()*60],
327
- 'velocity_p, km/s' : [velocity_p], 'velocity_s, km/s' : [velocity_s]}))
328
-
329
-
 
 
 
 
 
 
 
 
 
 
 
330
  # Add legend
331
- ax[0].scatter(None, None, color='r', marker='|', label='P')
332
- ax[0].scatter(None, None, color='b', marker='|', label='S')
333
- ax[0].xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
334
  ax[0].xaxis.set_major_locator(mdates.SecondLocator(interval=20))
335
  ax[0].legend()
336
 
337
- print('Plotting stations')
338
- for i in range(1,3):
339
- ax[i].scatter(st_lons, st_lats, color='b', label='Stations')
340
- ax[i].scatter(eq_lon, eq_lat, color='r', marker='*', label='Earthquake')
341
- ax[i].set_aspect('equal')
342
- ax[i].set_xticklabels(ax[i].get_xticks(), rotation = 50)
 
 
 
 
343
 
344
- fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8,
345
- wspace=0.02, hspace=0.02)
346
-
347
  cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8])
348
- cbar = fig.colorbar(ax[2].scatter(None, None, c=velocity_p, alpha=0.5, vmin=0, vmax=8), cax=cb_ax)
 
 
349
 
350
- cbar.set_label('Velocity (km/s)')
351
- ax[1].set_title('P Velocity')
352
- ax[2].set_title('S Velocity')
353
 
354
  for a in ax:
355
- a.tick_params(axis='both', which='major', labelsize=8)
356
-
357
- plt.subplots_adjust(hspace=0., wspace=0.5)
358
- fig.canvas.draw();
359
  image = np.array(fig.canvas.renderer.buffer_rgba())
360
  plt.close(fig)
361
- output_picks.to_csv(f'data/velocity/{eq_lat}_{eq_lon}_{timestamp}_{len(waveforms)}.csv', index=False)
362
- output_csv = f'data/velocity/{eq_lat}_{eq_lon}_{timestamp}_{len(waveforms)}.csv'
 
 
363
 
364
  return image, output_picks, output_csv
365
 
 
366
  model = torch.jit.load("model.pt")
367
 
368
  with gr.Blocks() as demo:
369
- gr.HTML("""
 
370
  <div style="padding: 20px; border-radius: 10px;">
371
  <h1 style="font-size: 30px; text-align: center; margin-bottom: 20px;">PhaseHunter <span style="animation: arrow-anim 10s linear infinite; display: inline-block; transform: rotate(45deg) translateX(-20px);">🏹</span>
372
 
@@ -394,132 +540,195 @@ with gr.Blocks() as demo:
394
  <li>Waveforms should be sampled at 100 samples/sec and have 3 (Z, N, E) or 1 (Z) channels. PhaseHunter analyzes the first 6000 samples of your file.</li>
395
  </ul>
396
  </div>
397
- """)
 
398
 
399
  with gr.Tab("Try on a single station"):
400
- with gr.Row():
401
  # Define the input and output types for Gradio
402
  inputs = gr.Dropdown(
403
- ["data/sample/sample_0.npy",
404
- "data/sample/sample_1.npy",
405
- "data/sample/sample_2.npy"],
406
- label="Sample waveform",
 
 
407
  info="Select one of the samples",
408
- value = "data/sample/sample_0.npy"
409
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
 
411
  upload = gr.File(label="Or upload your own waveform")
412
 
413
  button = gr.Button("Predict phases")
414
- outputs = gr.Image(label='Waveform with Phases Marked', type='numpy', interactive=False)
415
-
416
- button.click(mark_phases, inputs=[inputs, upload], outputs=outputs)
417
-
 
 
 
 
 
 
418
  with gr.Tab("Select earthquake from catalogue"):
419
 
420
- gr.HTML("""
 
421
  <div style="padding: 20px; border-radius: 10px; font-size: 16px;">
422
  <p style="font-weight: bold; font-size: 24px; margin-bottom: 20px;">Using PhaseHunter to Analyze Seismic Waveforms</p>
423
  <p>Select an earthquake from the global earthquake catalogue (e.g. <a href="https://earthquake.usgs.gov/earthquakes/map">USGS</a>) and the app will download the waveform from the FDSN client of your choice. The app will use a velocity model of your choice to select appropriate time windows for each station within a specified radius of the earthquake.</p>
424
  <p>The app will then analyze the waveforms and mark the detected phases on the waveform. Pick data for each waveform is reported in seconds from the start of the waveform.</p>
425
  <p>Velocities are derived from distance and travel time determined by PhaseHunter picks (<span style="font-style: italic;">v = distance/predicted_pick_time</span>). The background of the velocity plot is colored by DEM.</p>
426
  </div>
427
- """)
428
- with gr.Row():
 
429
  with gr.Column(scale=2):
430
  client_inputs = gr.Dropdown(
431
- choices = list(URL_MAPPINGS.keys()),
432
- label="FDSN Client",
433
  info="Select one of the available FDSN clients",
434
- value = "IRIS",
435
- interactive=True
436
  )
437
 
438
  velocity_inputs = gr.Dropdown(
439
- choices = ['1066a', '1066b', 'ak135',
440
- 'ak135f', 'herrin', 'iasp91',
441
- 'jb', 'prem', 'pwdk'],
442
- label="1D velocity model",
 
 
 
 
 
 
 
 
443
  info="Velocity model for station selection",
444
- value = "1066a",
445
- interactive=True
446
  )
447
 
448
  with gr.Column(scale=2):
449
- timestamp_inputs = gr.Textbox(value='2019-07-04 17:33:49',
450
- placeholder='YYYY-MM-DD HH:MM:SS',
451
- label="Timestamp",
452
- info="Timestamp of the earthquake",
453
- max_lines=1,
454
- interactive=True)
455
-
456
- source_depth_inputs = gr.Number(value=10,
 
 
 
457
  label="Source depth (km)",
458
  info="Depth of the earthquake",
459
- interactive=True)
460
-
 
461
  with gr.Column(scale=2):
462
- eq_lat_inputs = gr.Number(value=35.766,
463
- label="Latitude",
464
- info="Latitude of the earthquake",
465
- interactive=True)
466
-
467
- eq_lon_inputs = gr.Number(value=-117.605,
468
- label="Longitude",
469
- info="Longitude of the earthquake",
470
- interactive=True)
471
-
 
 
 
 
472
  with gr.Column(scale=2):
473
- radius_inputs = gr.Slider(minimum=1,
474
- maximum=200,
475
- value=50,
476
- label="Radius (km)",
477
- step=10,
478
- info="""Select the radius around the earthquake to download data from.\n
 
479
  Note that the larger the radius, the longer the app will take to run.""",
480
- interactive=True)
481
-
482
- max_waveforms_inputs = gr.Slider(minimum=1,
483
- maximum=100,
484
- value=10,
485
- label="Max waveforms per section",
486
- step=1,
487
- info="Maximum number of waveforms to show per section\n (to avoid long prediction times)",
488
- interactive=True,
489
- )
 
 
490
  with gr.Column(scale=2):
491
- P_thres_inputs = gr.Slider(minimum=0.01,
492
- maximum=1,
493
- value=0.1,
494
- label="P uncertainty threshold, s",
495
- step=0.01,
496
- info="Acceptable uncertainty for P picks expressed in std() seconds",
497
- interactive=True,
498
- )
499
- S_thres_inputs = gr.Slider(minimum=0.01,
500
- maximum=1,
501
- value=0.2,
502
- label="S uncertainty threshold, s",
503
- step=0.01,
504
- info="Acceptable uncertainty for S picks expressed in std() seconds",
505
- interactive=True,
506
- )
507
-
 
 
508
  button = gr.Button("Predict phases")
509
- output_image = gr.Image(label='Waveforms with Phases Marked', type='numpy', interactive=False)
 
 
510
 
511
  with gr.Row():
512
- output_picks = gr.Dataframe(label='Pick data',
513
- type='pandas',
514
- interactive=False)
515
  output_csv = gr.File(label="Output File", file_types=[".csv"])
516
 
517
- button.click(predict_on_section,
518
- inputs=[client_inputs, timestamp_inputs,
519
- eq_lat_inputs, eq_lon_inputs,
520
- radius_inputs, source_depth_inputs,
521
- velocity_inputs, max_waveforms_inputs,
522
- P_thres_inputs, S_thres_inputs],
523
- outputs=[output_image, output_picks, output_csv])
524
-
525
- demo.launch()
 
 
 
 
 
 
 
 
 
 
13
 
14
  import obspy
15
  from obspy.clients.fdsn import Client
16
+ from obspy.clients.fdsn.header import (
17
+ FDSNNoDataException,
18
+ FDSNTimeoutException,
19
+ FDSNInternalServerException,
20
+ )
21
  from obspy.geodetics.base import locations2degrees
22
  from obspy.taup import TauPyModel
23
  from obspy.taup.helper_classes import SlownessModelError
 
30
 
31
  from glob import glob
32
 
33
+
34
  def make_prediction(waveform):
35
  waveform = np.load(waveform)
36
+ if len(waveform.shape) == 1:
37
+ waveform = waveform.reshape(1, waveform.shape[0])
38
+
39
  processed_input = prepare_waveform(waveform)
40
+
41
  # Make prediction
42
  with torch.inference_mode():
43
  output = model(processed_input)
 
47
 
48
  return processed_input, p_phase, s_phase
49
 
50
+
51
+ def mark_phases(waveform, uploaded_file, p_thres, s_thres):
52
 
53
  if uploaded_file is not None:
54
  waveform = uploaded_file.name
 
56
  processed_input, p_phase, s_phase = make_prediction(waveform)
57
 
58
  # Create a plot of the waveform with the phases marked
59
+ if sum(processed_input[0][2] == 0): # if input is 1C
60
  fig, ax = plt.subplots(nrows=2, figsize=(10, 2), sharex=True)
61
 
62
+ ax[0].plot(processed_input[0][0], color="black", lw=1)
63
+ ax[0].set_ylabel("Norm. Ampl.")
64
 
65
+ else: # if input is 3C
66
  fig, ax = plt.subplots(nrows=4, figsize=(10, 6), sharex=True)
67
+ ax[0].plot(processed_input[0][0], color="black", lw=1)
68
+ ax[1].plot(processed_input[0][1], color="black", lw=1)
69
+ ax[2].plot(processed_input[0][2], color="black", lw=1)
70
+
71
+ ax[0].set_ylabel("Z")
72
+ ax[1].set_ylabel("N")
73
+ ax[2].set_ylabel("E")
74
+
75
+ print(p_phase.std().item() * 60)
76
+ do_we_have_p = p_phase.std().item() * 60 < p_thres
77
+ if do_we_have_p:
78
+ p_phase_plot = p_phase * processed_input.shape[-1]
79
+ p_kde = gaussian_kde(p_phase_plot)
80
+ p_dist_space = np.linspace(min(p_phase_plot) - 10, max(p_phase_plot) + 10, 500)
81
+ ax[-1].plot(p_dist_space, p_kde(p_dist_space), color="r")
82
+ else:
83
+ ax[-1].text(
84
+ 0.5,
85
+ 0.75,
86
+ "No P phase detected",
87
+ horizontalalignment="center",
88
+ verticalalignment="center",
89
+ transform=ax[-1].transAxes,
90
+ )
91
+
92
+ do_we_have_s = s_phase.std().item() * 60 < s_thres
93
+ if do_we_have_s:
94
+ s_phase_plot = s_phase * processed_input.shape[-1]
95
+ s_kde = gaussian_kde(s_phase_plot)
96
+ s_dist_space = np.linspace(min(s_phase_plot) - 10, max(s_phase_plot) + 10, 500)
97
+ ax[-1].plot(s_dist_space, s_kde(s_dist_space), color="b")
98
+
99
+ for a in ax:
100
+ a.axvline(
101
+ p_phase.mean() * processed_input.shape[-1],
102
+ color="r",
103
+ linestyle="--",
104
+ label="P",
105
+ alpha=do_we_have_p,
106
+ )
107
+ a.axvline(
108
+ s_phase.mean() * processed_input.shape[-1],
109
+ color="b",
110
+ linestyle="--",
111
+ label="S",
112
+ alpha=do_we_have_s,
113
+ )
114
+ else:
115
+ ax[-1].text(
116
+ 0.5,
117
+ 0.25,
118
+ "No S phase detected",
119
+ horizontalalignment="center",
120
+ verticalalignment="center",
121
+ transform=ax[-1].transAxes,
122
+ )
123
+
124
+ ax[-1].set_xlabel("Time, samples")
125
+ ax[-1].set_ylabel("Uncert., samples")
126
  ax[-1].legend()
127
 
128
+ plt.subplots_adjust(hspace=0.0, wspace=0.0)
129
 
130
  # Convert the plot to an image and return it
131
  fig.canvas.draw()
 
133
  plt.close(fig)
134
  return image
135
 
136
+
137
  def bin_distances(distances, bin_size=10):
138
  # Bin the distances into groups of `bin_size` kilometers
139
  binned_distances = {}
 
149
  for bin_index in binned_distances:
150
  first_distance, first_distance_index = binned_distances[bin_index]
151
  first_distances.append(first_distance_index)
152
+
153
  return first_distances
154
 
155
+
156
  def variance_coefficient(residuals):
157
  # calculate the variance of the residuals
158
  var = residuals.var()
 
160
  coeff = 1 - (var / (residuals.max() - residuals.min()))
161
  return coeff
162
 
163
+
164
+ def predict_on_section(
165
+ client_name,
166
+ timestamp,
167
+ eq_lat,
168
+ eq_lon,
169
+ radius_km,
170
+ source_depth_km,
171
+ velocity_model,
172
+ max_waveforms,
173
+ conf_thres_P,
174
+ conf_thres_S,
175
+ ):
176
  distances, t0s, st_lats, st_lons, waveforms, names = [], [], [], [], [], []
177
+
178
  taup_model = TauPyModel(model=velocity_model)
179
  client = Client(client_name)
180
 
 
188
  endtime = starttime + 120
189
 
190
  try:
191
+ print("Starting to download inventory")
192
+ inv = client.get_stations(
193
+ network="*",
194
+ station="*",
195
+ location="*",
196
+ channel="*H*",
197
+ starttime=starttime,
198
+ endtime=endtime,
199
+ minlatitude=(eq_lat - window),
200
+ maxlatitude=(eq_lat + window),
201
+ minlongitude=(eq_lon - window),
202
+ maxlongitude=(eq_lon + window),
203
+ level="station",
204
+ )
205
+ print("Finished downloading inventory")
206
+
207
+ except (
208
+ IndexError,
209
+ FDSNNoDataException,
210
+ FDSNTimeoutException,
211
+ FDSNInternalServerException,
212
+ ):
213
  fig, ax = plt.subplots()
214
+ ax.text(0.5, 0.5, "Something is wrong with the data provider, try another")
215
+ fig.canvas.draw()
216
  image = np.array(fig.canvas.renderer.buffer_rgba())
217
  plt.close(fig)
218
  return image
219
+
220
  waveforms = []
221
  cached_waveforms = glob("data/cached/*.mseed")
222
 
223
  for network in inv:
224
+ if network.code == "SY":
225
  continue
226
  for station in network:
227
  print(f"Processing {network.code}.{station.code}...")
228
+ distance = locations2degrees(
229
+ eq_lat, eq_lon, station.latitude, station.longitude
230
+ )
231
 
232
+ arrivals = taup_model.get_travel_times(
233
+ source_depth_in_km=source_depth_km,
234
+ distance_in_degree=distance,
235
+ phase_list=["P", "S"],
236
+ )
237
 
238
  if len(arrivals) > 0:
239
 
240
  starttime = obspy.UTCDateTime(timestamp) + arrivals[0].time - 15
241
  endtime = starttime + 60
242
  try:
243
+ filename = f"{network.code}_{station.code}_{starttime}"
244
  if f"data/cached/{filename}.mseed" not in cached_waveforms:
245
+ print(f"Downloading waveform for {filename}")
246
+ waveform = client.get_waveforms(
247
+ network=network.code,
248
+ station=station.code,
249
+ location="*",
250
+ channel="*",
251
+ starttime=starttime,
252
+ endtime=endtime,
253
+ )
254
+ waveform.write(
255
+ f"data/cached/{network.code}_{station.code}_{starttime}.mseed",
256
+ format="MSEED",
257
+ )
258
+ print("Finished downloading and caching waveform")
259
  else:
260
+ print("Reading cached waveform")
261
+ waveform = obspy.read(
262
+ f"data/cached/{network.code}_{station.code}_{starttime}.mseed"
263
+ )
264
+
265
+ except (
266
+ IndexError,
267
+ FDSNNoDataException,
268
+ FDSNTimeoutException,
269
+ FDSNInternalServerException,
270
+ ):
271
+ print(f"Skipping {network.code}_{station.code}_{starttime}")
272
  continue
273
+
274
  waveform = waveform.select(channel="H[BH][ZNE]")
275
  waveform = waveform.merge(fill_value=0)
276
+ waveform = waveform[:3].sort(keys=["channel"], reverse=True)
277
 
278
  len_check = [len(x.data) for x in waveform]
279
  if len(set(len_check)) > 1:
 
281
 
282
  if len(waveform) == 3:
283
  try:
284
+ waveform = prepare_waveform(
285
+ np.stack([x.data for x in waveform])
286
+ )
287
 
288
  distances.append(distance)
289
  t0s.append(starttime)
 
292
  waveforms.append(waveform)
293
  names.append(f"{network.code}.{station.code}")
294
 
295
+ print(
296
+ f"Added {network.code}.{station.code} to the list of waveforms"
297
+ )
298
 
299
  except:
300
  continue
301
+
 
302
  # If there are no waveforms, return an empty plot
303
  if len(waveforms) == 0:
304
+ print("No waveforms found")
305
  fig, ax = plt.subplots()
306
+ ax.text(0.5, 0.5, "No waveforms found")
307
+ fig.canvas.draw()
308
  image = np.array(fig.canvas.renderer.buffer_rgba())
309
  plt.close(fig)
310
  output_picks = pd.DataFrame()
311
+ output_picks.to_csv("data/picks.csv", index=False)
312
+ output_csv = "data/picks.csv"
313
  return image, output_picks, output_csv
 
314
 
315
+ first_distances = bin_distances(distances, bin_size=10 / 111.2)
316
 
317
  # Edge case when there are way too many waveforms to process
318
+ selection_indexes = np.random.choice(
319
+ first_distances, np.min([len(first_distances), max_waveforms]), replace=False
320
+ )
321
 
322
  waveforms = np.array(waveforms)[selection_indexes]
323
  distances = np.array(distances)[selection_indexes]
 
328
 
329
  waveforms = [torch.tensor(waveform) for waveform in waveforms]
330
 
331
+ print("Starting to run predictions")
332
  with torch.no_grad():
333
  waveforms_torch = torch.vstack(waveforms)
334
  output = model(waveforms_torch)
 
336
  p_phases = output[:, 0]
337
  s_phases = output[:, 1]
338
 
339
+ p_phases = p_phases.reshape(len(waveforms), -1)
340
+ s_phases = s_phases.reshape(len(waveforms), -1)
341
 
342
+ # Max confidence - min variance
343
  p_max_confidence = p_phases.std(axis=-1).min()
344
  s_max_confidence = s_phases.std(axis=-1).min()
345
 
346
  print(f"Starting plotting {len(waveforms)} waveforms")
347
  fig, ax = plt.subplots(ncols=3, figsize=(10, 3))
348
+
349
  # Plot topography
350
+ print("Fetching topography")
351
  params = Topography.DEFAULT.copy()
352
  extra_window = 0.5
353
+ params["south"] = np.min([st_lats.min(), eq_lat]) - extra_window
354
+ params["north"] = np.max([st_lats.max(), eq_lat]) + extra_window
355
+ params["west"] = np.min([st_lons.min(), eq_lon]) - extra_window
356
+ params["east"] = np.max([st_lons.max(), eq_lon]) + extra_window
357
 
358
  topo_map = Topography(**params)
359
  topo_map.fetch()
360
  topo_map.load()
361
 
362
+ print("Plotting topo")
363
  hillshade = es.hillshade(topo_map.da[0], altitude=10)
364
+
365
+ topo_map.da.plot(ax=ax[1], cmap="Greys", add_colorbar=False, add_labels=False)
366
+ topo_map.da.plot(ax=ax[2], cmap="Greys", add_colorbar=False, add_labels=False)
367
  ax[1].imshow(hillshade, cmap="Greys", alpha=0.5)
368
 
369
+ output_picks = pd.DataFrame(
370
+ {
371
+ "station_name": [],
372
+ "st_lat": [],
373
+ "st_lon": [],
374
+ "starttime": [],
375
+ "p_phase, s": [],
376
+ "p_uncertainty, s": [],
377
+ "s_phase, s": [],
378
+ "s_uncertainty, s": [],
379
+ "velocity_p, km/s": [],
380
+ "velocity_s, km/s": [],
381
+ }
382
+ )
383
+
384
  for i in range(len(waveforms)):
385
  print(f"Plotting waveform {i+1}/{len(waveforms)}")
386
  current_P = p_phases[i]
387
  current_S = s_phases[i]
388
+
389
+ x = [t0s[i] + pd.Timedelta(seconds=k / 100) for k in np.linspace(0, 6000, 6000)]
390
  x = mdates.date2num(x)
391
 
392
  # Normalize confidence for the plot
393
+ p_conf = 1 / (current_P.std() / p_max_confidence).item()
394
+ s_conf = 1 / (current_S.std() / s_max_confidence).item()
395
 
396
  delta_t = t0s[i].timestamp - obspy.UTCDateTime(timestamp).timestamp
397
 
398
+ ax[0].plot(
399
+ x,
400
+ waveforms[i][0, 0] * 10 + distances[i] * 111.2,
401
+ color="black",
402
+ alpha=0.5,
403
+ lw=1,
404
+ )
405
+
406
+ if (current_P.std().item() * 60 < conf_thres_P) or (
407
+ current_S.std().item() * 60 < conf_thres_S
408
+ ):
409
+ ax[0].scatter(
410
+ x[int(current_P.mean() * waveforms[i][0].shape[-1])],
411
+ waveforms[i][0, 0].mean() + distances[i] * 111.2,
412
+ color="r",
413
+ alpha=p_conf,
414
+ marker="|",
415
+ )
416
+ ax[0].scatter(
417
+ x[int(current_S.mean() * waveforms[i][0].shape[-1])],
418
+ waveforms[i][0, 0].mean() + distances[i] * 111.2,
419
+ color="b",
420
+ alpha=s_conf,
421
+ marker="|",
422
+ )
423
 
424
+ velocity_p = (distances[i] * 111.2) / (
425
+ delta_t + current_P.mean() * 60
426
+ ).item()
427
+ velocity_s = (distances[i] * 111.2) / (
428
+ delta_t + current_S.mean() * 60
429
+ ).item()
430
 
431
  # Generate an array from st_lat to eq_lat and from st_lon to eq_lon
432
  x = np.linspace(st_lons[i], eq_lon, 50)
433
  y = np.linspace(st_lats[i], eq_lat, 50)
434
+
435
  # Plot the array
436
+ ax[1].scatter(
437
+ x, y, c=np.zeros_like(x) + velocity_p, alpha=0.1, vmin=0, vmax=8
438
+ )
439
+ ax[2].scatter(
440
+ x, y, c=np.zeros_like(x) + velocity_s, alpha=0.1, vmin=0, vmax=8
441
+ )
442
 
443
  else:
444
  velocity_p = np.nan
445
  velocity_s = np.nan
446
+
447
+ ax[0].set_ylabel("Z")
448
+ print(
449
+ f"Station {st_lats[i]}, {st_lons[i]} has P velocity {velocity_p} and S velocity {velocity_s}"
450
+ )
451
+
452
+ output_picks = output_picks.append(
453
+ pd.DataFrame(
454
+ {
455
+ "station_name": [names[i]],
456
+ "st_lat": [st_lats[i]],
457
+ "st_lon": [st_lons[i]],
458
+ "starttime": [str(t0s[i])],
459
+ "p_phase, s": [(delta_t + current_P.mean() * 60).item()],
460
+ "p_uncertainty, s": [current_P.std().item() * 60],
461
+ "s_phase, s": [(delta_t + current_S.mean() * 60).item()],
462
+ "s_uncertainty, s": [current_S.std().item() * 60],
463
+ "velocity_p, km/s": [velocity_p],
464
+ "velocity_s, km/s": [velocity_s],
465
+ }
466
+ )
467
+ )
468
+
469
  # Add legend
470
+ ax[0].scatter(None, None, color="r", marker="|", label="P")
471
+ ax[0].scatter(None, None, color="b", marker="|", label="S")
472
+ ax[0].xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
473
  ax[0].xaxis.set_major_locator(mdates.SecondLocator(interval=20))
474
  ax[0].legend()
475
 
476
+ print("Plotting stations")
477
+ for i in range(1, 3):
478
+ ax[i].scatter(st_lons, st_lats, color="b", label="Stations")
479
+ ax[i].scatter(eq_lon, eq_lat, color="r", marker="*", label="Earthquake")
480
+ ax[i].set_aspect("equal")
481
+ ax[i].set_xticklabels(ax[i].get_xticks(), rotation=50)
482
+
483
+ fig.subplots_adjust(
484
+ bottom=0.1, top=0.9, left=0.1, right=0.8, wspace=0.02, hspace=0.02
485
+ )
486
 
 
 
 
487
  cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8])
488
+ cbar = fig.colorbar(
489
+ ax[2].scatter(None, None, c=velocity_p, alpha=0.5, vmin=0, vmax=8), cax=cb_ax
490
+ )
491
 
492
+ cbar.set_label("Velocity (km/s)")
493
+ ax[1].set_title("P Velocity")
494
+ ax[2].set_title("S Velocity")
495
 
496
  for a in ax:
497
+ a.tick_params(axis="both", which="major", labelsize=8)
498
+
499
+ plt.subplots_adjust(hspace=0.0, wspace=0.5)
500
+ fig.canvas.draw()
501
  image = np.array(fig.canvas.renderer.buffer_rgba())
502
  plt.close(fig)
503
+ output_picks.to_csv(
504
+ f"data/velocity/{eq_lat}_{eq_lon}_{timestamp}_{len(waveforms)}.csv", index=False
505
+ )
506
+ output_csv = f"data/velocity/{eq_lat}_{eq_lon}_{timestamp}_{len(waveforms)}.csv"
507
 
508
  return image, output_picks, output_csv
509
 
510
+
511
  model = torch.jit.load("model.pt")
512
 
513
  with gr.Blocks() as demo:
514
+ gr.HTML(
515
+ """
516
  <div style="padding: 20px; border-radius: 10px;">
517
  <h1 style="font-size: 30px; text-align: center; margin-bottom: 20px;">PhaseHunter <span style="animation: arrow-anim 10s linear infinite; display: inline-block; transform: rotate(45deg) translateX(-20px);">🏹</span>
518
 
 
540
  <li>Waveforms should be sampled at 100 samples/sec and have 3 (Z, N, E) or 1 (Z) channels. PhaseHunter analyzes the first 6000 samples of your file.</li>
541
  </ul>
542
  </div>
543
+ """
544
+ )
545
 
546
  with gr.Tab("Try on a single station"):
547
+ with gr.Row():
548
  # Define the input and output types for Gradio
549
  inputs = gr.Dropdown(
550
+ [
551
+ "data/sample/sample_0.npy",
552
+ "data/sample/sample_1.npy",
553
+ "data/sample/sample_2.npy",
554
+ ],
555
+ label="Sample waveform",
556
  info="Select one of the samples",
557
+ value="data/sample/sample_0.npy",
558
  )
559
+ with gr.Column(scale=1):
560
+ P_thres_inputs = gr.Slider(
561
+ minimum=0.01,
562
+ maximum=1,
563
+ value=0.1,
564
+ label="P uncertainty threshold, s",
565
+ step=0.01,
566
+ info="Acceptable uncertainty for P picks expressed in std() seconds",
567
+ interactive=True,
568
+ )
569
+
570
+ S_thres_inputs = gr.Slider(
571
+ minimum=0.01,
572
+ maximum=1,
573
+ value=0.2,
574
+ label="S uncertainty threshold, s",
575
+ step=0.01,
576
+ info="Acceptable uncertainty for S picks expressed in std() seconds",
577
+ interactive=True,
578
+ )
579
 
580
  upload = gr.File(label="Or upload your own waveform")
581
 
582
  button = gr.Button("Predict phases")
583
+ outputs = gr.Image(
584
+ label="Waveform with Phases Marked", type="numpy", interactive=False
585
+ )
586
+
587
+ button.click(
588
+ mark_phases,
589
+ inputs=[inputs, upload, P_thres_inputs, S_thres_inputs],
590
+ outputs=outputs,
591
+ )
592
+
593
  with gr.Tab("Select earthquake from catalogue"):
594
 
595
+ gr.HTML(
596
+ """
597
  <div style="padding: 20px; border-radius: 10px; font-size: 16px;">
598
  <p style="font-weight: bold; font-size: 24px; margin-bottom: 20px;">Using PhaseHunter to Analyze Seismic Waveforms</p>
599
  <p>Select an earthquake from the global earthquake catalogue (e.g. <a href="https://earthquake.usgs.gov/earthquakes/map">USGS</a>) and the app will download the waveform from the FDSN client of your choice. The app will use a velocity model of your choice to select appropriate time windows for each station within a specified radius of the earthquake.</p>
600
  <p>The app will then analyze the waveforms and mark the detected phases on the waveform. Pick data for each waveform is reported in seconds from the start of the waveform.</p>
601
  <p>Velocities are derived from distance and travel time determined by PhaseHunter picks (<span style="font-style: italic;">v = distance/predicted_pick_time</span>). The background of the velocity plot is colored by DEM.</p>
602
  </div>
603
+ """
604
+ )
605
+ with gr.Row():
606
  with gr.Column(scale=2):
607
  client_inputs = gr.Dropdown(
608
+ choices=list(URL_MAPPINGS.keys()),
609
+ label="FDSN Client",
610
  info="Select one of the available FDSN clients",
611
+ value="IRIS",
612
+ interactive=True,
613
  )
614
 
615
  velocity_inputs = gr.Dropdown(
616
+ choices=[
617
+ "1066a",
618
+ "1066b",
619
+ "ak135",
620
+ "ak135f",
621
+ "herrin",
622
+ "iasp91",
623
+ "jb",
624
+ "prem",
625
+ "pwdk",
626
+ ],
627
+ label="1D velocity model",
628
  info="Velocity model for station selection",
629
+ value="1066a",
630
+ interactive=True,
631
  )
632
 
633
  with gr.Column(scale=2):
634
+ timestamp_inputs = gr.Textbox(
635
+ value="2019-07-04 17:33:49",
636
+ placeholder="YYYY-MM-DD HH:MM:SS",
637
+ label="Timestamp",
638
+ info="Timestamp of the earthquake",
639
+ max_lines=1,
640
+ interactive=True,
641
+ )
642
+
643
+ source_depth_inputs = gr.Number(
644
+ value=10,
645
  label="Source depth (km)",
646
  info="Depth of the earthquake",
647
+ interactive=True,
648
+ )
649
+
650
  with gr.Column(scale=2):
651
+ eq_lat_inputs = gr.Number(
652
+ value=35.766,
653
+ label="Latitude",
654
+ info="Latitude of the earthquake",
655
+ interactive=True,
656
+ )
657
+
658
+ eq_lon_inputs = gr.Number(
659
+ value=-117.605,
660
+ label="Longitude",
661
+ info="Longitude of the earthquake",
662
+ interactive=True,
663
+ )
664
+
665
  with gr.Column(scale=2):
666
+ radius_inputs = gr.Slider(
667
+ minimum=1,
668
+ maximum=200,
669
+ value=50,
670
+ label="Radius (km)",
671
+ step=10,
672
+ info="""Select the radius around the earthquake to download data from.\n
673
  Note that the larger the radius, the longer the app will take to run.""",
674
+ interactive=True,
675
+ )
676
+
677
+ max_waveforms_inputs = gr.Slider(
678
+ minimum=1,
679
+ maximum=100,
680
+ value=10,
681
+ label="Max waveforms per section",
682
+ step=1,
683
+ info="Maximum number of waveforms to show per section\n (to avoid long prediction times)",
684
+ interactive=True,
685
+ )
686
  with gr.Column(scale=2):
687
+ P_thres_inputs = gr.Slider(
688
+ minimum=0.01,
689
+ maximum=1,
690
+ value=0.1,
691
+ label="P uncertainty threshold, s",
692
+ step=0.01,
693
+ info="Acceptable uncertainty for P picks expressed in std() seconds",
694
+ interactive=True,
695
+ )
696
+ S_thres_inputs = gr.Slider(
697
+ minimum=0.01,
698
+ maximum=1,
699
+ value=0.2,
700
+ label="S uncertainty threshold, s",
701
+ step=0.01,
702
+ info="Acceptable uncertainty for S picks expressed in std() seconds",
703
+ interactive=True,
704
+ )
705
+
706
  button = gr.Button("Predict phases")
707
+ output_image = gr.Image(
708
+ label="Waveforms with Phases Marked", type="numpy", interactive=False
709
+ )
710
 
711
  with gr.Row():
712
+ output_picks = gr.Dataframe(
713
+ label="Pick data", type="pandas", interactive=False
714
+ )
715
  output_csv = gr.File(label="Output File", file_types=[".csv"])
716
 
717
+ button.click(
718
+ predict_on_section,
719
+ inputs=[
720
+ client_inputs,
721
+ timestamp_inputs,
722
+ eq_lat_inputs,
723
+ eq_lon_inputs,
724
+ radius_inputs,
725
+ source_depth_inputs,
726
+ velocity_inputs,
727
+ max_waveforms_inputs,
728
+ P_thres_inputs,
729
+ S_thres_inputs,
730
+ ],
731
+ outputs=[output_image, output_picks, output_csv],
732
+ )
733
+
734
+ demo.launch()
data/.DS_Store CHANGED
Binary files a/data/.DS_Store and b/data/.DS_Store differ
 
data/velocity/35.766_-117.605_2019-07-04 17:33:49_3.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ station_name,st_lat,st_lon,starttime,"p_phase, s","p_uncertainty, s","s_phase, s","s_uncertainty, s","velocity_p, km/s","velocity_s, km/s"
2
+ CI.SRT,35.69235,-117.75051,2019-07-04T17:33:38.029990Z,4.52931022644043,0.014338254695758224,9.210612297058105,0.013868825335521251,3.417590792273917,1.6805971661817796
3
+ CI.JRC2,35.98249,-117.80885,2019-07-04T17:33:39.947494Z,7.320904731750488,0.018777401419356465,13.390795707702637,0.037158007035031915,4.136213094741051,2.261316106809097
4
+ CI.WMF,36.11758,-117.85486,2019-07-04T17:33:41.867962Z,9.504384994506836,0.01592034415807575,17.031570434570312,0.04673818708397448,4.745724852828504,2.6483286583912338
data/velocity/35.766_-117.605_2019-07-04 17:33:49_9.csv ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ station_name,st_lat,st_lon,starttime,"p_phase, s","p_uncertainty, s","s_phase, s","s_uncertainty, s","velocity_p, km/s","velocity_s, km/s"
2
+ LB.DAC,36.277,-117.593697,2019-07-04T17:33:43.387184Z,34.49410629272461,0.22159690037369728,41.63465881347656,0.886770598590374,,
3
+ CI.WMF,36.11758,-117.85486,2019-07-04T17:33:41.867962Z,9.505252838134766,0.01779856625944376,17.014007568359375,0.06349349627271295,4.7452915611377335,2.6510624200710167
4
+ CI.ISA,35.66278,-118.47403,2019-07-04T17:33:46.297658Z,14.401352882385254,0.04201323492452502,26.648784637451172,0.040058575104922056,5.506237698660711,2.9756431008588833
5
+ CI.JRC2,35.98249,-117.80885,2019-07-04T17:33:39.947494Z,7.321362495422363,0.018879775889217854,13.384379386901855,0.05111166858114302,4.135954480569838,2.2624001562934875
6
+ CI.EDW2,34.8811,-117.993881,2019-07-04T17:33:49.567241Z,17.187454223632812,0.025604498223401606,31.232393264770508,0.16874447464942932,6.08203947635048,3.3469985537104074
7
+ CI.RRX,34.875332,-116.996841,2019-07-04T17:33:50.712219Z,17.309837341308594,0.04084662417881191,30.36077117919922,0.091552734375,6.549756328808083,3.734266695918123
8
+ CI.SRT,35.69235,-117.75051,2019-07-04T17:33:38.029990Z,4.529020309448242,0.010629930475261062,9.221219062805176,0.019179217633791268,3.4178095631283902,1.6786640486259041
9
+ CI.CWC,36.439049,-118.080498,2019-07-04T17:33:47.189005Z,16.294597625732422,0.03656624467112124,28.510168075561523,0.047869981499388814,5.288708979140877,3.02268946806275
10
+ NN.QSM,35.965,-116.869102,2019-07-04T17:33:45.081547Z,10.827579498291016,0.05653449450619519,19.081382751464844,0.02382224891334772,6.456704830678366,3.663806012475838
phasehunter/__pycache__/data_preparation.cpython-311.pyc DELETED
Binary file (9.14 kB)
 
phasehunter/__pycache__/data_preparation.cpython-39.pyc ADDED
Binary file (4.57 kB). View file
 
phasehunter/__pycache__/model.cpython-311.pyc DELETED
Binary file (16.4 kB)
 
test.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:32166b7b4d6bd898ebce5da148b1080ffd3f50682a34ec877c14a905aa441d91
3
+ size 48128